Completed
Push — develop ( b517c8...2e9112 )
by Jace
02:23
created

yorm.bases.wrapped()   B

Complexity

Conditions 5

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

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