Passed
Push — master ( 34bb27...aa21cd )
by Max
59s
created

structured_data._descriptor._varargs()   A

Complexity

Conditions 3

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
import functools
2
import inspect
3
4
from . import _destructure
5
from . import _doc_wrapper
6
from . import _matchable
7
from . import _pep_570_when
8
from ._patterns import mapping_match
9
10
11
def _decorate(matchers, structure, func):
12
    matchers.append((structure, func))
13
    return func
14
15
16
class Descriptor:
17
    """Base class for decorator classes."""
18
19
    __wrapped__ = None
20
21
    def __new__(cls, func, *args, **kwargs):
22
        new = super().__new__(cls, *args, **kwargs)
23
        new.__doc__ = None
24
        if func is None:
25
            return new
26
        return functools.wraps(func)(new)
27
28
29
@_doc_wrapper.DocWrapper.wrap_class
30
class Property(Descriptor):
31
    """Decorator with value-based dispatch. Acts as a property."""
32
33
    fset = None
34
    fdel = None
35
36
    protected = False
37
38
    def __new__(cls, func=None, fset=None, fdel=None, doc=None, *args, **kwargs):
39
        del fset, fdel, doc
40
        return super().__new__(cls, func, *args, **kwargs)
41
42
    def __init__(self, func=None, fset=None, fdel=None, doc=None, *args, **kwargs):
43
        del func
44
        super().__init__(*args, **kwargs)
45
        self.fset = fset
46
        self.fdel = fdel
47
        if doc is not None:
48
            self.__doc__ = doc
49
        self.get_matchers = []
50
        self.set_matchers = []
51
        self.delete_matchers = []
52
        self.protected = True
53
54
    def __setattr__(self, name, value):
55
        if self.protected and name != "__doc__":
56
            raise AttributeError
57
        super().__setattr__(name, value)
58
59
    def __delattr__(self, name):
60
        if self.protected and name != "__doc__":
61
            raise AttributeError
62
        super().__delattr__(name)
63
64
    def getter(self, getter):
65
        """Return a copy of self with the getter replaced."""
66
        new = Property(getter, self.fset, self.fdel, self.__doc__)
67
        new.get_matchers.extend(self.get_matchers)
68
        new.set_matchers.extend(self.set_matchers)
69
        new.delete_matchers.extend(self.delete_matchers)
70
        return new
71
72
    def setter(self, setter):
73
        """Return a copy of self with the setter replaced."""
74
        new = Property(self.__wrapped__, setter, self.fdel, self.__doc__)
75
        new.get_matchers.extend(self.get_matchers)
76
        new.set_matchers.extend(self.set_matchers)
77
        new.delete_matchers.extend(self.delete_matchers)
78
        return new
79
80
    def deleter(self, deleter):
81
        """Return a copy of self with the deleter replaced."""
82
        new = Property(self.__wrapped__, self.fset, deleter, self.__doc__)
83
        new.get_matchers.extend(self.get_matchers)
84
        new.set_matchers.extend(self.set_matchers)
85
        new.delete_matchers.extend(self.delete_matchers)
86
        return new
87
88
    def __get__(self, instance, owner):
89
        if instance is None:
90
            return self
91
        matchable = _matchable.Matchable(instance)
92
        for (structure, func) in self.get_matchers:
93
            if matchable(structure):
94
                return func(**matchable.matches)
95
        if self.__wrapped__ is None:
96
            raise ValueError(self)
97
        return self.__wrapped__(instance)
98
99
    def __set__(self, instance, value):
100
        matchable = _matchable.Matchable((instance, value))
101
        for (structure, func) in self.set_matchers:
102
            if matchable(structure):
103
                func(**matchable.matches)
104
                return
105
        if self.fset is None:
106
            raise ValueError((instance, value))
107
        self.fset(instance, value)
108
109
    def __delete__(self, instance):
110
        matchable = _matchable.Matchable(instance)
111
        for (structure, func) in self.delete_matchers:
112
            if matchable(structure):
113
                func(**matchable.matches)
114
                return
115
        if self.fdel is None:
116
            raise ValueError(instance)
117
        self.fdel(instance)
118
119
    def get_when(self, instance):
120
        """Add a binding to the getter."""
121
        structure = instance
122
        _destructure.names(structure)  # Raise ValueError if there are duplicates
123
        return functools.partial(_decorate, self.get_matchers, structure)
124
125
    def set_when(self, instance, value):
126
        """Add a binding to the setter."""
127
        structure = (instance, value)
128
        _destructure.names(structure)  # Raise ValueError if there are duplicates
129
        return functools.partial(_decorate, self.set_matchers, structure)
130
131
    def delete_when(self, instance):
132
        """Add a binding to the deleter."""
133
        structure = instance
134
        _destructure.names(structure)  # Raise ValueError if there are duplicates
135
        return functools.partial(_decorate, self.delete_matchers, structure)
136
137
138
def _varargs(signature):
139
    for parameter in signature.parameters.values():
140
        if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
141
            yield parameter
142
143
144
def _dispatch(func, matches, bound_args, bound_kwargs):
145
    for key, value in matches.items():
146
        if key in bound_kwargs:
147
            raise TypeError
148
        bound_kwargs[key] = value
149
    function_sig = inspect.signature(func)
150
    function_args = function_sig.bind(**bound_kwargs)
151
    for parameter in _varargs(function_sig):
152
        function_args.arguments[parameter.name] = bound_args
153
    function_args.apply_defaults()
154
    return func(*function_args.args, **function_args.kwargs)
155
156
157
class Function(Descriptor):
158
    """Decorator with value-based dispatch. Acts as a function."""
159
160
    def __init__(self, func, *args, **kwargs):
161
        del func
162
        super().__init__(*args, **kwargs)
163
        self.matchers = []
164
165
    def __get__(self, instance, owner):
166
        if instance is None:
167
            return self
168
        return functools.partial(self, instance)
169
170
    def _bound_and_values(self, args, kwargs):
171
        # Then we figure out what signature we're giving the outside world.
172
        signature = inspect.signature(self)
173
        # The signature lets us regularize the call and apply any defaults
174
        bound_arguments = signature.bind(*args, **kwargs)
175
        bound_arguments.apply_defaults()
176
177
        # Extract the *args and **kwargs, if any.
178
        # These are never used in the matching, just passed to the underlying function
179
        bound_args = ()
180
        bound_kwargs = {}
181
        values = bound_arguments.arguments.copy()
182
        for parameter in signature.parameters.values():
183
            if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
184
                bound_args = values.pop(parameter.name)
185
            if parameter.kind is inspect.Parameter.VAR_KEYWORD:
186
                bound_kwargs = values.pop(parameter.name)
187
        return bound_args, bound_kwargs, values
188
189
    def __call__(*args, **kwargs):
190
        # Okay, so, this is a convoluted mess.
191
        # First, we extract self from the beginning of the argument list
192
        self, *args = args
193
194
        bound_args, bound_kwargs, values = self._bound_and_values(args, kwargs)
195
196
        matchable = _matchable.Matchable(values)
197
        for structure, func in self.matchers:
198
            if matchable(structure):
199
                return _dispatch(func, matchable.matches, bound_args, bound_kwargs)
200
        raise ValueError(values)
201
202
    @_pep_570_when.pep_570_when
203
    def when(self, kwargs):
204
        """Add a binding for this function."""
205
        structure = mapping_match.DictPattern(kwargs, exhaustive=True)
206
        _destructure.names(structure)  # Raise ValueError if there are duplicates
207
        return functools.partial(_decorate, self.matchers, structure)
208