#!/usr/bin/env python3 from abc import ABC, abstractmethod import atexit import functools import logging import dill import file_utils logger = logging.getLogger(__name__) class Persistent(ABC): @abstractmethod def save(self): pass @abstractmethod def load(self): pass class persistent_autoload_singleton(Persistent): def __init__(self, filename: str, *, max_age_sec: int = 0): self.filename = filename self.max_age_sec = max_age_sec self.instance = None def __call__(self, cls): @functools.wraps(cls) def _load(*args, **kwargs): # If class has already been loaded, act like a singleton # and return a reference to the one and only instance in # memory. if self.instance is not None: logger.debug( f'Returning already instantiated singleton instance of {cls.__name__}.' ) return self.instance if not self.load(): assert self.instance is None logger.debug( f'Instantiating {cls.__name__} directly.' ) self.instance = cls(*args, **kwargs) # On program exit, save state to disk. atexit.register(self.save) assert self.instance is not None return self.instance return _load def load(self) -> bool: if ( file_utils.does_file_exist(self.filename) and ( self.max_age_sec == 0 or file_utils.get_file_mtime_age_seconds(self.filename) <= self.max_age_sec ) ): logger.debug( f'Attempting to load from file {self.filename}' ) try: with open(self.filename, 'rb') as f: self.instance = dill.load(f) return True except Exception: self.instance = None return False return False def save(self) -> bool: if self.instance is not None: logger.debug( f'Attempting to save {type(self.instance).__name__} to file {self.filename}' ) try: with open(self.filename, 'wb') as f: dill.dump(self.instance, f, dill.HIGHEST_PROTOCOL) return True except Exception: return False return False