Completed
Pull Request — master (#41)
by Max
03:26
created

Method._matchers()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
import functools
2
import inspect
3
import typing
4
5
from ... import _class_placeholder
6
from ... import _pep_570_when
7
from .. import matchable
8
from ..patterns import mapping_match
9
from . import common
10
11
12
def _varargs(signature):
13
    for parameter in signature.parameters.values():
14
        if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
15
            yield parameter
16
17
18
def _dispatch(func, matches, bound_args, bound_kwargs):
19
    for key, value in matches.items():
20
        if key in bound_kwargs:
21
            raise TypeError
22
        bound_kwargs[key] = value
23
    function_sig = inspect.signature(func)
24
    function_args = function_sig.bind(**bound_kwargs)
25
    for parameter in _varargs(function_sig):
26
        function_args.arguments[parameter.name] = bound_args
27
    function_args.apply_defaults()
28
    return func(*function_args.args, **function_args.kwargs)
29
30
31
class Function(common.Decorator):
32
    """Decorator with value-based dispatch. Acts as a function."""
33
34
    def __init__(self, func: typing.Callable, *args, **kwargs) -> None:
35
        del func
36
        super().__init__(*args, **kwargs)  # type: ignore
37
        self.matchers: common.MatcherList[mapping_match.DictPattern] = []
38
39
    def _bound_and_values(self, args, kwargs):
40
        # Then we figure out what signature we're giving the outside world.
41
        signature = inspect.signature(self)
42
        # The signature lets us regularize the call and apply any defaults
43
        bound_arguments = signature.bind(*args, **kwargs)
44
        bound_arguments.apply_defaults()
45
46
        # Extract the *args and **kwargs, if any.
47
        # These are never used in the matching, just passed to the underlying function
48
        bound_args = ()
49
        bound_kwargs = {}
50
        values = bound_arguments.arguments.copy()
51
        for parameter in signature.parameters.values():
52
            if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
53
                bound_args = values.pop(parameter.name)
54
            if parameter.kind is inspect.Parameter.VAR_KEYWORD:
55
                bound_kwargs = values.pop(parameter.name)
56
        return bound_args, bound_kwargs, values
57
58
    def __call__(*args, **kwargs):
59
        # Okay, so, this is a convoluted mess.
60
        # First, we extract self from the beginning of the argument list
61
        self, *args = args
62
63
        bound_args, bound_kwargs, values = self._bound_and_values(args, kwargs)
64
65
        matchable_ = matchable.Matchable(values)
66
        for structure, func in self.matchers:
67
            if matchable_(structure):
68
                return _dispatch(func, matchable_.matches, bound_args, bound_kwargs)
69
        raise ValueError(values)
70
71
    @_pep_570_when.pep_570_when
72
    def when(self, kwargs: dict) -> typing.Callable[[typing.Callable], typing.Callable]:
73
        """Add a binding for this function."""
74
        return common.decorate(self.matchers, _placeholder_kwargs(kwargs))
75
76
77
class MethodProxy:
78
79
    def __init__(self, func):
80
        self.func = func
81
82
    def __call__(*args, **kwargs):
83
        self, *args = args
84
        return self.func(*args, **kwargs)
85
86
    def __get__(self, instance, owner):
87
        return self.func.__get__(instance, owner)
88
89
90
class Method(Function, common.Descriptor):
91
    """Decorator with value-based dispatch. Acts as a method."""
92
93
    def __get__(self, instance, owner):
94
        if instance is None:
95
            if owner is self.owner:
96
                return self
97
            return MethodProxy(self)
98
        return functools.partial(self, instance)
99
100
    def _matchers(self):
101
        yield self.matchers
102
103
104
def _kwarg_structure(kwargs: dict) -> mapping_match.DictPattern:
105
    return mapping_match.DictPattern(kwargs, exhaustive=True)
106
107
108
def _placeholder_kwargs(kwargs: typing.Dict) -> common.Matcher:
109
    if any(_class_placeholder.is_placeholder(kwarg) for kwarg in kwargs.values()):
110
111
        @_class_placeholder.placeholder
112
        def _placeholder(cls: type) -> mapping_match.DictPattern:
113
            return _kwarg_structure(
114
                {
115
                    name: (
116
                        kwarg(cls)
117
                        if _class_placeholder.is_placeholder(kwarg)
118
                        else kwarg
119
                    )
120
                    for (name, kwarg) in kwargs.items()
121
                }
122
            )
123
124
        return _placeholder
125
126
    return _kwarg_structure(kwargs)
127