Completed
Pull Request — master (#41)
by Max
03:26
created

structured_data._match.descriptor.property_   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 187
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 135
dl 0
loc 187
rs 8.8798
c 0
b 0
f 0
wmc 44

21 Methods

Rating   Name   Duplication   Size   Complexity  
A Property.setter() 0 7 1
A Property.__init__() 0 11 2
A Property.set_when() 0 3 1
A Property.deleter() 0 7 1
A Property.__delattr__() 0 4 3
A Property.__delete__() 0 9 4
A PropertyProxy.__init__() 0 2 1
B Property.__get__() 0 12 6
A PropertyProxy.setter() 0 2 1
A Property.__new__() 0 3 1
A Property.__setattr__() 0 7 5
A PropertyProxy.deleter() 0 2 1
A PropertyProxy.__delete__() 0 2 1
A PropertyProxy.__set__() 0 2 1
A PropertyProxy.__get__() 0 2 1
A Property._matchers() 0 4 1
A Property.get_when() 0 3 1
A Property.delete_when() 0 3 1
A PropertyProxy.getter() 0 2 1
A Property.getter() 0 7 1
A Property.__set__() 0 9 4

4 Functions

Rating   Name   Duplication   Size   Complexity  
A _snd_placeholder() 0 6 1
A _fst_placeholder() 0 6 1
A _placeholder_tuple2() 0 7 2
A _both_placeholder() 0 6 1

How to fix   Complexity   

Complexity

Complex classes like structured_data._match.descriptor.property_ often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import typing
2
3
from ... import _class_placeholder
4
from ... import _doc_wrapper
5
from .. import matchable
6
from . import common
7
8
OptionalSetter = typing.Optional[typing.Callable[[typing.Any, typing.Any], None]]
9
OptionalDeleter = typing.Optional[typing.Callable[[typing.Any], None]]
10
11
12
class PropertyProxy:
13
14
    def __init__(self, prop):
15
        self.prop = prop
16
17
    def getter(self, getter):
18
        return self.prop.getter(getter)
19
20
    def setter(self, setter):
21
        return self.prop.setter(setter)
22
23
    def deleter(self, deleter):
24
        return self.prop.deleter(deleter)
25
26
    def __get__(self, instance, owner):
27
        return self.prop.__get__(instance, owner)
28
29
    def __set__(self, instance, value):
30
        self.prop.__set__(instance, value)
31
32
    def __delete__(self, instance):
33
        self.prop.__delete__(instance)
34
35
36
@_doc_wrapper.DocWrapper.wrap_class
37
class Property(common.Descriptor):
38
    """Decorator with value-based dispatch. Acts as a property."""
39
40
    fset: OptionalSetter = None
41
    fdel: OptionalDeleter = None
42
43
    protected = False
44
45
    def __new__(cls, func=None, fset=None, fdel=None, doc=None, *args, **kwargs):
46
        del fset, fdel, doc
47
        return super().__new__(cls, func, *args, **kwargs)
48
49
    def __init__(self, func=None, fset=None, fdel=None, doc=None, *args, **kwargs):
50
        del func
51
        super().__init__(*args, **kwargs)
52
        self.fset = fset
53
        self.fdel = fdel
54
        if doc is not None:
55
            self.__doc__ = doc
56
        self.get_matchers = []
57
        self.set_matchers = []
58
        self.delete_matchers = []
59
        self.protected = True
60
61
    def _matchers(self):
62
        yield self.get_matchers
63
        yield self.set_matchers
64
        yield self.delete_matchers
65
66
    def __setattr__(self, name, value):
67
        if self.protected and (
68
            name not in {"__doc__", "owner"}
69
            or (name == "owner" and getattr(self, "owner", value) is not value)
70
        ):
71
            raise AttributeError
72
        super().__setattr__(name, value)
73
74
    def __delattr__(self, name):
75
        if self.protected and name != "__doc__":
76
            raise AttributeError
77
        super().__delattr__(name)
78
79
    def getter(self, getter):
80
        """Return a copy of self with the getter replaced."""
81
        new = Property(getter, self.fset, self.fdel, self.__doc__)
82
        new.get_matchers.extend(self.get_matchers)
83
        new.set_matchers.extend(self.set_matchers)
84
        new.delete_matchers.extend(self.delete_matchers)
85
        return new
86
87
    def setter(self, setter):
88
        """Return a copy of self with the setter replaced."""
89
        new = Property(self.__wrapped__, setter, self.fdel, self.__doc__)
90
        new.get_matchers.extend(self.get_matchers)
91
        new.set_matchers.extend(self.set_matchers)
92
        new.delete_matchers.extend(self.delete_matchers)
93
        return new
94
95
    def deleter(self, deleter):
96
        """Return a copy of self with the deleter replaced."""
97
        new = Property(self.__wrapped__, self.fset, deleter, self.__doc__)
98
        new.get_matchers.extend(self.get_matchers)
99
        new.set_matchers.extend(self.set_matchers)
100
        new.delete_matchers.extend(self.delete_matchers)
101
        return new
102
103
    def __get__(self, instance, owner):
104
        if instance is None:
105
            if owner is self.owner:
106
                return self
107
            return PropertyProxy(self)
108
        matchable_ = matchable.Matchable(instance)
109
        for (structure, func) in self.get_matchers:
110
            if matchable_(structure):
111
                return func(**matchable_.matches)
112
        if self.__wrapped__ is None:
113
            raise ValueError(self)
114
        return self.__wrapped__(instance)
115
116
    def __set__(self, instance, value):
117
        matchable_ = matchable.Matchable((instance, value))
118
        for (structure, func) in self.set_matchers:
119
            if matchable_(structure):
120
                func(**matchable_.matches)
121
                return
122
        if self.fset is None:
123
            raise ValueError((instance, value))
124
        self.fset(instance, value)
125
126
    def __delete__(self, instance):
127
        matchable_ = matchable.Matchable(instance)
128
        for (structure, func) in self.delete_matchers:
129
            if matchable_(structure):
130
                func(**matchable_.matches)
131
                return
132
        if self.fdel is None:
133
            raise ValueError(instance)
134
        self.fdel(instance)
135
136
    def get_when(self, instance):
137
        """Add a binding to the getter."""
138
        return common.decorate(self.get_matchers, instance)
139
140
    def set_when(self, instance, value):
141
        """Add a binding to the setter."""
142
        return common.decorate(self.set_matchers, _placeholder_tuple2(instance, value))
143
144
    def delete_when(self, instance):
145
        """Add a binding to the deleter."""
146
        return common.decorate(self.delete_matchers, instance)
147
148
149
def _fst_placeholder(fst, snd):
150
    @_class_placeholder.placeholder
151
    def _placeholder(cls):
152
        return (fst(cls), snd)
153
154
    return _placeholder
155
156
157
def _snd_placeholder(fst, snd):
158
    @_class_placeholder.placeholder
159
    def _placeholder(cls):
160
        return (fst, snd(cls))
161
162
    return _placeholder
163
164
165
def _both_placeholder(fst, snd):
166
    @_class_placeholder.placeholder
167
    def _placeholder(cls):
168
        return (fst(cls), snd(cls))
169
170
    return _placeholder
171
172
173
_PLACEHOLDERS = {
174
    (True, False): _fst_placeholder,
175
    (False, True): _snd_placeholder,
176
    (True, True): _both_placeholder,
177
}
178
179
180
def _placeholder_tuple2(fst, snd):
181
    _placeholder = _PLACEHOLDERS.get(
182
        (_class_placeholder.is_placeholder(fst), _class_placeholder.is_placeholder(snd))
183
    )
184
    if _placeholder:
185
        return _placeholder(fst, snd)
186
    return (fst, snd)
187