summaryrefslogtreecommitdiff
path: root/persistent.py
blob: 30e4ccbfcbed724ead5445b9674a2334d621497c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/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