Passed
Push — master ( 735aba...47b96b )
by Max
46s
created

DictPatternDestructurer.destructure()   C

Complexity

Conditions 11

Size

Total Lines 18
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 18
nop 2
dl 0
loc 18
rs 5.4
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like structured_data._destructure.DictPatternDestructurer.destructure() 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
from ._adt_constructor import ADTConstructor
2
from ._match_failure import MatchFailure
3
from ._not_in import not_in
4
from ._patterns import AsPattern
5
from ._patterns import AttrPattern
6
from ._patterns import DictPattern
7
from ._patterns import Pattern
8
from ._unpack import unpack
9
10
11
class Destructurer:
12
13
    def __init__(self, target):
14
        self.target = target
15
16
    def __call__(self, value):
17
        return self.destructure(value)
18
19
    def destructure(self, value):
20
        raise NotImplementedError
21
22
    type = None
23
24
25
class AsPatternDestructurer(Destructurer):
26
27
    def destructure(self, value):
28
        if isinstance(value, AsPattern):
29
            if value is self.target:
30
                return reversed(value)
31
            return (value.match, value)
32
        return (value, value)
33
34
    type = AsPattern
35
36
37
class ADTDestructurer(Destructurer):
38
39
    def destructure(self, value):
40
        if value.__class__ is not self.target.__class__:
41
            raise MatchFailure
42
        return reversed(unpack(value))
43
44
    type = ADTConstructor
45
46
47
class AttrPatternDestructurer(Destructurer):
48
49
    def destructure(self, value):
50
        if isinstance(value, AttrPattern):
51
            if len(value.match_dict) < len(self.target.match_dict):
52
                raise MatchFailure
53
            results = []
54
            for (target_key, _), (value_key, value_value) in zip(self.target.match_dict, value.match_dict):
55
                if target_key != value_key:
56
                    raise MatchFailure
57
                results.append(value_value)
58
            return reversed(results)
59
        try:
60
            return [getattr(value, target_key) for (target_key, _) in reversed(self.target.match_dict)]
61
        except AttributeError:
62
            raise MatchFailure
63
64
    type = AttrPattern
65
66
67
class DictPatternDestructurer(Destructurer):
68
69
    def destructure(self, value):
70
        if isinstance(value, DictPattern):
71
            if len(value.match_dict) < len(self.target.match_dict):
72
                raise MatchFailure
73
            if self.target.exhaustive and len(value.match_dict) > len(self.target.match_dict):
74
                raise MatchFailure
75
            results = []
76
            for (target_key, _), (value_key, value_value) in zip(self.target.match_dict, value.match_dict):
77
                if target_key != value_key:
78
                    raise MatchFailure
79
                results.append(value_value)
80
            return reversed(results)
81
        if self.target.exhaustive and len(value) != len(self.target.match_dict):
82
            raise MatchFailure
83
        try:
84
            return [value[target_key] for (target_key, _) in reversed(self.target.match_dict)]
85
        except KeyError:
86
            raise MatchFailure
87
88
    type = DictPattern
89
90
91
class TupleDestructurer(Destructurer):
92
93
    def destructure(self, value):
94
        if isinstance(value, self.target.__class__) and len(self.target) == len(value):
95
            return reversed(value)
96
        raise MatchFailure
97
98
    type = tuple
99
100
101
class DestructurerList(tuple):
102
103
    __slots__ = ()
104
105
    def __new__(cls, *destructurers):
106
        return super().__new__(cls, destructurers)
107
108
    def get_destructurer(self, item):
109
        for destructurer in self:
110
            if isinstance(item, destructurer.type):
111
                return destructurer(item)
112
        return None
113
114
    @classmethod
115
    def custom(cls, *destructurers):
116
        return cls(
117
            AsPatternDestructurer, ADTDestructurer, AttrPatternDestructurer, DictPatternDestructurer, *destructurers, TupleDestructurer)
118
119
    def names(self, target):
120
        name_list = []
121
        names_seen = set()
122
        to_process = [target]
123
        while to_process:
124
            item = to_process.pop()
125
            if isinstance(item, Pattern):
126
                not_in(names_seen, item.name)
127
                names_seen.add(item.name)
128
                name_list.append(item.name)
129
            else:
130
                destructurer = self.get_destructurer(item)
131
                if destructurer:
132
                    to_process.extend(destructurer(item))
133
        return name_list
134
135
136
DESTRUCTURERS = DestructurerList.custom()
137