Completed
Push — master ( 24a7f2...39d343 )
by Max
24s queued 10s
created

structured_data._match.descriptor.property_   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 38
eloc 150
dl 0
loc 223
rs 9.36
c 0
b 0
f 0

20 Methods

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

4 Functions

Rating   Name   Duplication   Size   Complexity  
A _snd_placeholder() 0 8 1
A _fst_placeholder() 0 8 1
A _placeholder_tuple2() 0 15 2
A _both_placeholder() 0 8 1
1
"""Property-like descriptors that expose decorators for value-based dispatch."""
2
3
from __future__ import annotations
4
5
import typing
6
7
from ... import _class_placeholder
8
from ... import _doc_wrapper
9
from .. import matchable
10
from . import common
11
12
OptionalSetter = typing.Optional[typing.Callable[[typing.Any, typing.Any], None]]
13
OptionalDeleter = typing.Optional[typing.Callable[[typing.Any], None]]
14
15
T = typing.TypeVar("T")
16
U = typing.TypeVar("U")
17
18
19
@_doc_wrapper.ProxyWrapper.wrap_class("prop")
20
class PropertyProxy:
21
    """Wrapper for Property that doesn't expose the when methods."""
22
23
    def __init__(self, prop: Property) -> None:
24
        self.prop = prop
25
26
    def getter(self, getter):
27
        """Return a copy of the wrapped property with the getter replaced."""
28
        return self.prop.getter(getter)
29
30
    def setter(self, setter):
31
        """Return a copy of the wrapped property with the setter replaced."""
32
        return self.prop.setter(setter)
33
34
    def deleter(self, deleter):
35
        """Return a copy of the wrapped property with the deleter replaced."""
36
        return self.prop.deleter(deleter)
37
38
    def __get__(self, instance, owner):
39
        return self.prop.__get__(instance, owner)
40
41
    def __set__(self, instance, value):
42
        self.prop.__set__(instance, value)
43
44
    def __delete__(self, instance):
45
        self.prop.__delete__(instance)
46
47
48
@_doc_wrapper.DocWrapper.wrap_class
49
class Property(common.Descriptor):
50
    """Decorator with value-based dispatch. Acts as a property."""
51
52
    fset: OptionalSetter = None
53
    fdel: OptionalDeleter = None
54
55
    protected = False
56
57
    def __new__(
58
        cls,
59
        func: typing.Optional[typing.Callable] = None,
60
        fset: typing.Optional[typing.Callable] = None,
61
        fdel: typing.Optional[typing.Callable] = None,
62
        doc: typing.Optional[str] = None,
63
    ):
64
        del fset, fdel, doc
65
        return super().__new__(cls, func)
66
67
    def __init__(
68
        self,
69
        func: typing.Optional[typing.Callable] = None,
70
        fset: typing.Optional[typing.Callable] = None,
71
        fdel: typing.Optional[typing.Callable] = None,
72
        doc: typing.Optional[str] = None,
73
    ) -> None:
74
        del func
75
        super().__init__()
76
        self.fset = fset
77
        self.fdel = fdel
78
        if doc is not None:
79
            self.__doc__ = doc
80
        # A more specific annotation would be good, but that's waiting on
81
        # further development.
82
        self.get_matchers: common.MatchTemplate[typing.Any] = common.MatchTemplate()
83
        self.set_matchers: common.MatchTemplate[typing.Any] = common.MatchTemplate()
84
        self.delete_matchers: common.MatchTemplate[typing.Any] = common.MatchTemplate()
85
        self.protected = True
86
87
    def __setattr__(self, name: str, value: typing.Any) -> None:
88
        if self.protected and name != "__doc__":
89
            raise AttributeError
90
        super().__setattr__(name, value)
91
92
    def __delattr__(self, name: str) -> None:
93
        if self.protected and name != "__doc__":
94
            raise AttributeError
95
        super().__delattr__(name)
96
97
    def getter(self, getter) -> Property:
98
        """Return a copy of self with the getter replaced."""
99
        new = Property(getter, self.fset, self.fdel, self.__doc__)
100
        self.get_matchers.copy_into(new.get_matchers)
101
        self.set_matchers.copy_into(new.set_matchers)
102
        self.delete_matchers.copy_into(new.delete_matchers)
103
        return new
104
105
    def setter(self, setter) -> Property:
106
        """Return a copy of self with the setter replaced."""
107
        new = Property(self.__wrapped__, setter, self.fdel, self.__doc__)
108
        self.get_matchers.copy_into(new.get_matchers)
109
        self.set_matchers.copy_into(new.set_matchers)
110
        self.delete_matchers.copy_into(new.delete_matchers)
111
        return new
112
113
    def deleter(self, deleter) -> Property:
114
        """Return a copy of self with the deleter replaced."""
115
        new = Property(self.__wrapped__, self.fset, deleter, self.__doc__)
116
        self.get_matchers.copy_into(new.get_matchers)
117
        self.set_matchers.copy_into(new.set_matchers)
118
        self.delete_matchers.copy_into(new.delete_matchers)
119
        return new
120
121
    def __get__(self, instance, owner):
122
        if instance is None:
123
            if common.owns(self, owner):
124
                return self
125
            return PropertyProxy(self)
126
        matchable_ = matchable.Matchable(instance)
127
        for func in self.get_matchers.match_instance(matchable_, instance):
128
            return func(**typing.cast(typing.Mapping, matchable_.matches))
129
        if self.__wrapped__ is None:
130
            raise ValueError(self)
131
        # Yes it is.
132
        return self.__wrapped__(instance)  # pylint: disable=not-callable
133
134
    def __set__(self, instance, value) -> None:
135
        matchable_ = matchable.Matchable((instance, value))
136
        for func in self.set_matchers.match_instance(matchable_, instance):
137
            func(**typing.cast(typing.Mapping, matchable_.matches))
138
            return
139
        if self.fset is None:
140
            raise ValueError((instance, value))
141
        self.fset(instance, value)
142
143
    def __delete__(self, instance) -> None:
144
        matchable_ = matchable.Matchable(instance)
145
        for func in self.delete_matchers.match_instance(matchable_, instance):
146
            func(**typing.cast(typing.Mapping, matchable_.matches))
147
            return
148
        if self.fdel is None:
149
            raise ValueError(instance)
150
        self.fdel(instance)
151
152
    def get_when(self, instance):
153
        """Add a binding to the getter."""
154
        return common.decorate(self.get_matchers, instance)
155
156
    def set_when(self, instance, value):
157
        """Add a binding to the setter."""
158
        return common.decorate(self.set_matchers, _placeholder_tuple2(instance, value))
159
160
    def delete_when(self, instance):
161
        """Add a binding to the deleter."""
162
        return common.decorate(self.delete_matchers, instance)
163
164
165
def _fst_placeholder(
166
    fst: _class_placeholder.Placeholder[T], snd: U
167
) -> _class_placeholder.Placeholder[typing.Tuple[T, U]]:
168
    @_class_placeholder.Placeholder
169
    def _placeholder(cls: type) -> typing.Tuple[T, U]:
170
        return (fst.func(cls), snd)
171
172
    return _placeholder
173
174
175
def _snd_placeholder(
176
    fst: T, snd: _class_placeholder.Placeholder[U]
177
) -> _class_placeholder.Placeholder[typing.Tuple[T, U]]:
178
    @_class_placeholder.Placeholder
179
    def _placeholder(cls: type) -> typing.Tuple[T, U]:
180
        return (fst, snd.func(cls))
181
182
    return _placeholder
183
184
185
def _both_placeholder(
186
    fst: _class_placeholder.Placeholder[T], snd: _class_placeholder.Placeholder[U]
187
) -> _class_placeholder.Placeholder[typing.Tuple[T, U]]:
188
    @_class_placeholder.Placeholder
189
    def _placeholder(cls: type) -> typing.Tuple[T, U]:
190
        return (fst.func(cls), snd.func(cls))
191
192
    return _placeholder
193
194
195
_PLACEHOLDERS: typing.Dict[
196
    typing.Tuple[bool, bool],
197
    typing.Callable[
198
        [typing.Any, typing.Any],
199
        _class_placeholder.Placeholder[typing.Tuple[typing.Any, typing.Any]],
200
    ],
201
] = {
202
    (True, False): _fst_placeholder,
203
    (False, True): _snd_placeholder,
204
    (True, True): _both_placeholder,
205
}
206
207
208
def _placeholder_tuple2(
209
    fst: typing.Any, snd: typing.Any
210
) -> typing.Union[
211
    _class_placeholder.Placeholder[typing.Tuple[typing.Any, typing.Any]],
212
    typing.Tuple[typing.Any, typing.Any],
213
]:
214
    _placeholder = _PLACEHOLDERS.get(
215
        (
216
            isinstance(fst, _class_placeholder.Placeholder),
217
            isinstance(snd, _class_placeholder.Placeholder),
218
        )
219
    )
220
    if _placeholder:
221
        return _placeholder(fst, snd)
222
    return (fst, snd)
223