yorm.bases.mappable   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 187
Duplicated Lines 25.13 %

Test Coverage

Coverage 93.48%

Importance

Changes 0
Metric Value
eloc 129
dl 47
loc 187
ccs 86
cts 92
cp 0.9348
rs 9.52
c 0
b 0
f 0
wmc 36

4 Functions

Rating   Name   Duplication   Size   Complexity  
B load_before() 24 24 6
A save_after() 23 23 5
A _private_call() 0 7 2
B patch_methods() 0 23 7

16 Methods

Rating   Name   Duplication   Size   Complexity  
A Mappable.insert() 0 3 1
A Mappable.clear() 0 3 1
A Mappable.update() 0 3 1
A Mappable.remove() 0 3 1
A Mappable.reverse() 0 3 1
A Mappable.extend() 0 3 1
A Mappable.sort() 0 3 1
A Mappable.append() 0 3 1
A Mappable.__delitem__() 0 3 1
A Mappable.__getitem__() 0 3 1
A Mappable.popitem() 0 3 1
A Mappable.__getattribute__() 0 3 1
A Mappable.__setitem__() 0 3 1
A Mappable.__iter__() 0 3 1
A Mappable.pop() 0 3 1
A Mappable.__setattr__() 0 3 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
"""Base classes for mapping."""
2
3 1
import abc
4 1
import functools
5 1
import logging
6
7 1
from .. import common
8
9 1
log = logging.getLogger(__name__)
10
11
12 1 View Code Duplication
def load_before(method):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
13
    """Decorate methods that should load before call."""
14
15 1
    if getattr(method, '_load_before', False):
16 1
        return method
17
18 1
    @functools.wraps(method)
19
    def wrapped(self, *args, **kwargs):
20
        __tracebackhide__ = True  # pylint: disable=unused-variable
21 1
22
        if not _private_call(method, args):
23 1
            mapper = common.get_mapper(self)
24 1
            if mapper and mapper.modified:
25 1
                log.debug("Loading before call: %s", method.__name__)
26 1
                mapper.load()
27 1
                if mapper.auto_save_after_load:
28 1
                    mapper.save()
29 1
                    mapper.modified = False
30 1
31
        return method(self, *args, **kwargs)
32 1
33
    setattr(wrapped, '_load_before', True)
34 1
35
    return wrapped
36 1
37
38 View Code Duplication
def save_after(method):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
39 1
    """Decorate methods that should save after call."""
40
41
    if getattr(method, '_save_after', False):
42 1
        return method
43 1
44
    @functools.wraps(method)
45 1
    def wrapped(self, *args, **kwargs):
46
        __tracebackhide__ = True  # pylint: disable=unused-variable
47
48 1
        result = method(self, *args, **kwargs)
49
50 1
        if not _private_call(method, args):
51
            mapper = common.get_mapper(self)
52 1
            if mapper and mapper.auto_save:
53 1
                log.debug("Saving after call: %s", method.__name__)
54 1
                mapper.save()
55 1
56 1
        return result
57
58 1
    setattr(wrapped, '_save_after', True)
59
60 1
    return wrapped
61
62 1
63
def _private_call(method, args, prefix='_'):
64
    """Determine if a call's first argument is a private variable name."""
65 1
    if method.__name__ in ('__getattribute__', '__setattr__'):
66
        assert isinstance(args[0], str)
67 1
        return args[0].startswith(prefix)
68 1
    else:
69 1
        return False
70
71 1
72
class Mappable(metaclass=abc.ABCMeta):
73
    """Base class for objects with attributes mapped to file."""
74 1
75
    # pylint: disable=no-member
76
77
    @load_before
78
    def __getattribute__(self, name):
79 1
        return object.__getattribute__(self, name)
80
81 1
    @save_after
82
    def __setattr__(self, name, value):
83 1
        super().__setattr__(name, value)
84
85 1
    @load_before
86
    def __iter__(self):
87 1
        return super().__iter__()
88
89 1
    @load_before
90
    def __getitem__(self, key):
91 1
        return super().__getitem__(key)
92
93 1
    @save_after
94
    def __setitem__(self, key, value):
95 1
        super().__setitem__(key, value)
96
97 1
    @save_after
98
    def __delitem__(self, key):
99 1
        super().__delitem__(key)
100
101 1
    @save_after
102
    def append(self, *args, **kwargs):
103 1
        super().append(*args, **kwargs)
104
105 1
    @save_after
106
    def extend(self, *args, **kwargs):
107 1
        super().extend(*args, **kwargs)
108
109
    @save_after
110
    def insert(self, *args, **kwargs):
111 1
        super().insert(*args, **kwargs)
112
113 1
    @save_after
114
    def remove(self, *args, **kwargs):
115 1
        super().remove(*args, **kwargs)
116
117
    @save_after
118
    def pop(self, *args, **kwargs):
119 1
        super().pop(*args, **kwargs)
120
121
    @save_after
122
    def clear(self, *args, **kwargs):
123 1
        super().clear(*args, **kwargs)
124
125 1
    @save_after
126
    def sort(self, *args, **kwargs):
127 1
        super().sort(*args, **kwargs)
128
129
    @save_after
130
    def reverse(self, *args, **kwargs):
131 1
        super().reverse(*args, **kwargs)
132
133
    @save_after
134
    def popitem(self, *args, **kwargs):
135 1
        super().popitem(*args, **kwargs)
136
137
    @save_after
138
    def update(self, *args, **kwargs):
139 1
        super().update(*args, **kwargs)
140
141 1
142
_LOAD_BEFORE_METHODS = [
143
    '__getattribute__',
144 1
    '__iter__',
145
    '__getitem__',
146
]
147
_SAVE_AFTER_METHODS = [
148
    '__setattr__',
149 1
    '__setitem__',
150
    '__delitem__',
151
    'append',
152
    'extend',
153
    'insert',
154
    'remove',
155
    'pop',
156
    'clear',
157
    'sort',
158
    'reverse',
159
    'popitem',
160
    'update',
161
]
162
163
164
def patch_methods(instance):
165
    log.debug("Patching methods on: %r", instance)
166 1
    cls = instance.__class__
167 1
168 1
    for name in _LOAD_BEFORE_METHODS:
169
        try:
170 1
            method = getattr(cls, name)
171 1
        except AttributeError:
172 1
            log.trace("No method: %s", name)
173 1
        else:
174 1
            modified_method = load_before(method)
175
            setattr(cls, name, modified_method)
176 1
            log.trace("Patched to load before call: %s", name)
177 1
178 1
    for name in _SAVE_AFTER_METHODS:
179
        try:
180 1
            method = getattr(cls, name)
181 1
        except AttributeError:
182 1
            log.trace("No method: %s", name)
183 1
        else:
184 1
            modified_method = save_after(method)
185
            setattr(cls, name, modified_method)
186
            log.trace("Patched to save after call: %s", name)
187