This repository has been archived by the owner on Oct 3, 2019. It is now read-only.
/
mappable.py
103 lines (80 loc) · 3.03 KB
/
mappable.py
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""Base classes for mapping."""
import abc
import functools
from .. import common
from ..mapper import MAPPER, get_mapper
log = common.logger(__name__)
def fetch_before(method):
"""Decorator for methods that should fetch before call."""
@functools.wraps(method)
def fetch_before(self, *args, **kwargs): # pylint: disable=W0621
"""Decorated method."""
mapper = get_mapper(self)
if mapper and mapper.modified:
log.debug("fetch before call: %s", method.__name__)
mapper.fetch()
return method(self, *args, **kwargs)
return fetch_before
def store_after(method):
"""Decorator for methods that should store after call."""
@functools.wraps(method)
def store_after(self, *args, **kwargs): # pylint: disable=W0621
"""Decorated method."""
result = method(self, *args, **kwargs)
mapper = get_mapper(self)
if mapper:
log.debug("store after call: %s", method.__name__)
mapper.store()
return result
return store_after
class Mappable(metaclass=abc.ABCMeta): # pylint: disable=R0201
"""Base class for objects with attributes mapped to file."""
def __getattribute__(self, name):
"""Trigger object update when reading attributes."""
# TODO: remove MAPPER once renamed to '__mapper__'
if name.startswith('__') or name == MAPPER:
# avoid infinite recursion (attribute requested in this function)
return object.__getattribute__(self, name)
mapper = get_mapper(self)
# Get the attribute's current value
try:
value = object.__getattribute__(self, name)
except AttributeError as exc:
missing = True
if not mapper:
raise exc from None
else:
missing = False
# Fetch a new value from disk if the attribute is mapped or missing
if mapper and (missing or name in mapper.attrs):
mapper.fetch()
value = object.__getattribute__(self, name)
return value
def __setattr__(self, name, value):
"""Trigger file update when setting attributes."""
super().__setattr__(name, value)
if name.startswith('__'):
return
mapper = get_mapper(self)
if mapper and name in mapper.attrs:
mapper.store()
@fetch_before
def __iter__(self):
"""Trigger object update when iterating."""
return super().__iter__()
@fetch_before
def __getitem__(self, key):
"""Trigger object update when reading an index."""
return super().__getitem__(key)
@store_after
def __setitem__(self, key, value):
"""Trigger file update when setting an index."""
super().__setitem__(key, value)
@store_after
def __delitem__(self, key):
"""Trigger file update when deleting an index."""
super().__delitem__(key)
@store_after
def append(self, value):
"""Trigger file update when appending items."""
super().append(value)