Passed
Push — master ( dd2608...7a63ce )
by Max
49s
created

structured_data._destructure.key_destructure()   A

Complexity

Conditions 2

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nop 3
dl 0
loc 2
rs 10
c 0
b 0
f 0
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
def guarded_getattr(value, target_key):
48
    try:
49
        return getattr(value, target_key)
50
    except AttributeError:
51
        raise MatchFailure
52
53
54
def key_destructure(value, match_dict, getter):
55
    return [getter(value, target_key) for (target_key, _) in reversed(match_dict)]
56
57
58
class AttrPatternDestructurer(Destructurer):
59
60
    def destructure(self, value):
61
        if isinstance(value, AttrPattern):
62
            if len(value.match_dict) < len(self.target.match_dict):
63
                raise MatchFailure
64
            results = []
65
            for (target_key, _), (value_key, value_value) in zip(self.target.match_dict, value.match_dict):
66
                if target_key != value_key:
67
                    raise MatchFailure
68
                results.append(value_value)
69
            return reversed(results)
70
        return key_destructure(value, self.target.match_dict, guarded_getattr)
71
72
    type = AttrPattern
73
74
75
def guarded_getitem(value, target_key):
76
    try:
77
        return value[target_key]
78
    except KeyError:
79
        raise MatchFailure
80
81
82
class DictPatternDestructurer(Destructurer):
83
84
    def destructure(self, value):
85
        if isinstance(value, DictPattern):
86
            if len(value.match_dict) < len(self.target.match_dict):
87
                raise MatchFailure
88
            if self.target.exhaustive and len(value.match_dict) > len(self.target.match_dict):
89
                raise MatchFailure
90
            results = []
91
            for (target_key, _), (value_key, value_value) in zip(self.target.match_dict, value.match_dict):
92
                if target_key != value_key:
93
                    raise MatchFailure
94
                results.append(value_value)
95
            return reversed(results)
96
        if self.target.exhaustive and len(value) != len(self.target.match_dict):
97
            raise MatchFailure
98
        return key_destructure(value, self.target.match_dict, guarded_getitem)
99
100
    type = DictPattern
101
102
103
class TupleDestructurer(Destructurer):
104
105
    def destructure(self, value):
106
        if isinstance(value, self.target.__class__) and len(self.target) == len(value):
107
            return reversed(value)
108
        raise MatchFailure
109
110
    type = tuple
111
112
113
class DestructurerList(tuple):
114
115
    __slots__ = ()
116
117
    def __new__(cls, *destructurers):
118
        return super().__new__(cls, destructurers)
119
120
    def get_destructurer(self, item):
121
        for destructurer in self:
122
            if isinstance(item, destructurer.type):
123
                return destructurer(item)
124
        return None
125
126
    @classmethod
127
    def custom(cls, *destructurers):
128
        return cls(
129
            AsPatternDestructurer, ADTDestructurer, AttrPatternDestructurer, DictPatternDestructurer, *destructurers, TupleDestructurer)
130
131
    def names(self, target):
132
        name_list = []
133
        names_seen = set()
134
        to_process = [target]
135
        while to_process:
136
            item = to_process.pop()
137
            if isinstance(item, Pattern):
138
                not_in(names_seen, item.name)
139
                names_seen.add(item.name)
140
                name_list.append(item.name)
141
            else:
142
                destructurer = self.get_destructurer(item)
143
                if destructurer:
144
                    to_process.extend(destructurer(item))
145
        return name_list
146
147
148
DESTRUCTURERS = DestructurerList.custom()
149