Completed
Push — develop ( 47068b...177319 )
by Jace
04:16
created

yorm.bases.Mappable.__setattr__()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
cc 1
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
1
"""Base classes for mapping."""
2
3 1
import abc
4 1
import functools
5
6 1
from .. import common
7 1
from ..mapper import get_mapper
8
9
10 1
log = common.logger(__name__)
11
12
13 1
def fetch_before(method):
14
    """Decorator for methods that should fetch before call."""
15
16 1
    @functools.wraps(method)
17
    def wrapped(self, *args, **kwargs):
18
        """Decorated method."""
19
20 1
        if not _private_name(args):
21 1
            mapper = get_mapper(self)
22 1
            if mapper and mapper.modified:
23 1
                log.debug("fetch before call: %s", method.__name__)
24 1
                mapper.fetch()
25 1
                if mapper.auto_store:
26 1
                    mapper.store()
27 1
                    mapper.modified = False
28
29 1
        return method(self, *args, **kwargs)
30
31 1
    return wrapped
32
33
34 1
def store_after(method):
35
    """Decorator for methods that should store after call."""
36
37 1
    @functools.wraps(method)
38
    def wrapped(self, *args, **kwargs):
39
        """Decorated method."""
40 1
        result = method(self, *args, **kwargs)
41
42 1
        if not _private_name(args):
43 1
            mapper = get_mapper(self)
44 1
            if mapper and mapper.auto:
45 1
                log.debug("store after call: %s", method.__name__)
46 1
                mapper.store()
47
48 1
        return result
49
50 1
    return wrapped
51
52
53 1
def _private_name(args, prefix='_'):
54
    """Determine if a call's first argument is a private variable name."""
55 1
    try:
56 1
        return args[0].startswith(prefix)
57 1
    except (IndexError, AttributeError):
58 1
        return False
59
60
61 1
class Mappable(metaclass=abc.ABCMeta):
62
    """Base class for objects with attributes mapped to file."""
63
64
    # pylint: disable=no-member
65
66 1
    @fetch_before
67
    def __getattribute__(self, name):
68
        """Trigger object update when reading attributes."""
69 1
        return object.__getattribute__(self, name)
70
71 1
    @store_after
72
    def __setattr__(self, name, value):
73
        """Trigger file update when setting attributes."""
74 1
        super().__setattr__(name, value)
75
76 1
    @fetch_before
77
    def __iter__(self):
78
        """Trigger object update when iterating."""
79 1
        return super().__iter__()
80
81 1
    @fetch_before
82
    def __getitem__(self, key):
83
        """Trigger object update when reading an index."""
84 1
        return super().__getitem__(key)
85
86 1
    @store_after
87
    def __setitem__(self, key, value):
88
        """Trigger file update when setting an index."""
89 1
        super().__setitem__(key, value)
90
91 1
    @store_after
92
    def __delitem__(self, key):
93
        """Trigger file update when deleting an index."""
94 1
        super().__delitem__(key)
95
96 1
    @store_after
97
    def append(self, value):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
98
        """Trigger file update when appending items."""
99
        super().append(value)
100