Passed
Push — master ( 68b1f8...bbdf06 )
by Max
01:03
created

structured_data._patterns   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 147
dl 0
loc 215
rs 6
c 0
b 0
f 0
wmc 55

27 Methods

Rating   Name   Duplication   Size   Complexity  
A AsPattern.__new__() 0 4 2
A DictPattern.exhaustive() 0 3 1
A DictPattern.__new__() 0 3 1
A Pattern.__getitem__() 0 2 1
A AttrPattern.__new__() 0 5 2
A Bind.bindings() 0 3 1
A AttrPattern.destructure() 0 13 5
A CompoundMatch.destructure() 0 2 1
A Bind.destructure() 0 7 4
A Bind.__new__() 0 4 1
A Bind.structure() 0 3 1
A DictPattern.destructure() 0 16 5
A AsPattern.matcher() 0 4 1
A Pattern.__new__() 0 8 4
A AsPattern.match() 0 4 1
A AttrPattern.match_dict() 0 3 1
A Pattern.name() 0 4 1
A AsPattern.destructure() 0 6 3
A DictPattern.match_dict() 0 3 1
A AsGuard.guard() 0 3 1
A Guard.__new__() 0 4 2
A AsGuard.structure() 0 3 1
A AsGuard.destructure() 0 6 3
A Guard.__getitem__() 0 2 1
A AsGuard.__new__() 0 2 1
A Guard.guard() 0 3 1
A Guard.destructure() 0 4 3

2 Functions

Rating   Name   Duplication   Size   Complexity  
A exhaustive_length_must_match() 0 3 3
A value_cant_be_smaller() 0 3 2

How to fix   Complexity   

Complexity

Complex classes like structured_data._patterns 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 keyword
2
3
from ._match_failure import MatchFailure
4
from ._not_in import not_in
5
6
DISCARD = object()
7
8
9
class CompoundMatch:
10
11
    __slots__ = ()
12
13
    def destructure(self, value):
14
        raise NotImplementedError
15
16
17
class Pattern(tuple):
18
    """A matcher that binds a value to a name."""
19
20
    __slots__ = ()
21
22
    def __new__(cls, name: str):
23
        if name == "_":
24
            return DISCARD
25
        if not name.isidentifier():
26
            raise ValueError
27
        if keyword.iskeyword(name):
28
            raise ValueError
29
        return super().__new__(cls, (name,))
30
31
    @property
32
    def name(self):
33
        """Return the name of the matcher."""
34
        return tuple.__getitem__(self, 0)
35
36
    def __getitem__(self, other):
37
        return AsPattern(self, other)
38
39
40
class AsPattern(CompoundMatch, tuple):
41
    """A matcher that contains further bindings."""
42
43
    __slots__ = ()
44
45
    def __new__(cls, matcher: Pattern, match):
46
        if match is DISCARD:
47
            return matcher
48
        return super().__new__(cls, (matcher, match))
49
50
    @property
51
    def matcher(self):
52
        """Return the left-hand-side of the as-match."""
53
        return self[0]
54
55
    @property
56
    def match(self):
57
        """Return the right-hand-side of the as-match."""
58
        return self[1]
59
60
    def destructure(self, value):
61
        if isinstance(value, AsPattern):
62
            if value is self:
63
                return (self.match, self.matcher)
64
            return (value.match, value)
65
        return (value, value)
66
67
68
def value_cant_be_smaller(target_match_dict, value_match_dict):
69
    if len(value_match_dict) < len(target_match_dict):
70
        raise MatchFailure
71
72
73
def exhaustive_length_must_match(target, value_match_dict):
74
    if target.exhaustive and len(value_match_dict) != len(target.match_dict):
75
        raise MatchFailure
76
77
78
class AttrPattern(CompoundMatch, tuple):
79
    """A matcher that destructures an object using attribute access."""
80
81
    __slots__ = ()
82
83
    def __new__(*args, **kwargs):
84
        cls, *args = args
85
        if args:
86
            raise ValueError(args)
87
        return super(AttrPattern, cls).__new__(cls, (tuple(kwargs.items()),))
88
89
    @property
90
    def match_dict(self):
91
        return self[0]
92
93
    def destructure(self, value):
94
        if isinstance(value, AttrPattern):
95
            value_cant_be_smaller(self.match_dict, value.match_dict)
96
            if value.match_dict:
97
                first_match, *remainder = value.match_dict
98
                return (AttrPattern(**dict(remainder)), first_match[1])
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable remainder does not seem to be defined.
Loading history...
99
        elif self.match_dict:
100
            first_match = self.match_dict[0]
101
            try:
102
                return (value, getattr(value, first_match[0]))
103
            except AttributeError:
104
                raise MatchFailure
105
        return ()
106
107
108
class DictPattern(CompoundMatch, tuple):
109
    """A matcher that destructures a dictionary by key."""
110
111
    __slots__ = ()
112
113
    def __new__(cls, match_dict, *, exhaustive=False):
114
        return super(DictPattern, cls).__new__(
115
            cls, (tuple(match_dict.items()), exhaustive)
116
        )
117
118
    @property
119
    def match_dict(self):
120
        return self[0]
121
122
    @property
123
    def exhaustive(self):
124
        return self[1]
125
126
    def destructure(self, value):
127
        if isinstance(value, DictPattern):
128
            value_cant_be_smaller(self.match_dict, value.match_dict)
129
            exhaustive_length_must_match(self, value.match_dict)
130
            if value.match_dict:
131
                first_match, *remainder = value.match_dict
132
                return (DictPattern(dict(remainder)), first_match[1])
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable remainder does not seem to be defined.
Loading history...
133
        elif self.match_dict:
134
            exhaustive_length_must_match(self, value)
135
            first_match = self.match_dict[0]
136
            try:
137
                return (value, value[first_match[0]])
138
            except KeyError:
139
                raise MatchFailure
140
        exhaustive_length_must_match(self, value)
141
        return ()
142
143
144
class Bind(CompoundMatch, tuple):
145
    """A wrapper that adds additional bindings to a successful match."""
146
147
    __slots__ = ()
148
149
    def __new__(*args, **kwargs):
150
        cls, structure = args
151
        not_in(kwargs, "_")
152
        return super(Bind, cls).__new__(cls, (structure, tuple(kwargs.items())))
153
154
    @property
155
    def structure(self):
156
        return self[0]
157
158
    @property
159
    def bindings(self):
160
        return self[1]
161
162
    def destructure(self, value):
163
        if value is self:
164
            return [Pattern(name) for (name, _) in reversed(self.bindings)] + [
165
                self.structure
166
            ]
167
        return [binding_value for (_, binding_value) in reversed(self.bindings)] + [
168
            value
169
        ]
170
171
172
class Guard(CompoundMatch, tuple):
173
174
    __slots__ = ()
175
176
    def __new__(cls, guard, structure=DISCARD):
177
        if structure is not DISCARD:
178
            return AsGuard(guard, structure)
179
        return super().__new__(cls, (guard,))
180
181
    def __getitem__(self, key):
182
        return Guard(self.guard, key)
183
184
    @property
185
    def guard(self):
186
        return tuple.__getitem__(self, 0)
187
188
    def destructure(self, value):
189
        if value is self or self.guard(value):
190
            return ()
191
        raise MatchFailure
192
193
194
class AsGuard(CompoundMatch, tuple):
195
196
    __slots__ = ()
197
198
    def __new__(cls, guard, structure):
199
        return super().__new__(cls, (guard, structure))
200
201
    @property
202
    def guard(self):
203
        return self[0]
204
205
    @property
206
    def structure(self):
207
        return self[1]
208
209
    def destructure(self, value):
210
        if value is self:
211
            return (self.structure,)
212
        if self.guard(value):
213
            return (value,)
214
        raise MatchFailure
215