Passed
Push — master ( 47b96b...dd2608 )
by Max
55s
created

structured_data._destructure.guarded_getattr()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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