Passed
Push — master ( bcf14c...4a1f1b )
by Max
57s
created

structured_data._match.descriptor.function   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 79
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 57
dl 0
loc 79
rs 10
c 0
b 0
f 0
wmc 18

5 Methods

Rating   Name   Duplication   Size   Complexity  
A Function.__init__() 0 4 1
A Function.__get__() 0 4 2
A Function.__call__() 0 12 3
A Function._bound_and_values() 0 18 4
A Function.when() 0 5 1

2 Functions

Rating   Name   Duplication   Size   Complexity  
A _dispatch() 0 11 4
A _varargs() 0 4 3
1
import functools
2
import inspect
3
4
from ... import _pep_570_when
5
from .. import matchable
6
from ..patterns import mapping_match
7
from . import common
8
9
10
def _varargs(signature):
11
    for parameter in signature.parameters.values():
12
        if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
13
            yield parameter
14
15
16
def _dispatch(func, matches, bound_args, bound_kwargs):
17
    for key, value in matches.items():
18
        if key in bound_kwargs:
19
            raise TypeError
20
        bound_kwargs[key] = value
21
    function_sig = inspect.signature(func)
22
    function_args = function_sig.bind(**bound_kwargs)
23
    for parameter in _varargs(function_sig):
24
        function_args.arguments[parameter.name] = bound_args
25
    function_args.apply_defaults()
26
    return func(*function_args.args, **function_args.kwargs)
27
28
29
class Function(common.Descriptor):
30
    """Decorator with value-based dispatch. Acts as a function."""
31
32
    def __init__(self, func, *args, **kwargs):
33
        del func
34
        super().__init__(*args, **kwargs)
35
        self.matchers = []
36
37
    def __get__(self, instance, owner):
38
        if instance is None:
39
            return self
40
        return functools.partial(self, instance)
41
42
    def _bound_and_values(self, args, kwargs):
43
        # Then we figure out what signature we're giving the outside world.
44
        signature = inspect.signature(self)
45
        # The signature lets us regularize the call and apply any defaults
46
        bound_arguments = signature.bind(*args, **kwargs)
47
        bound_arguments.apply_defaults()
48
49
        # Extract the *args and **kwargs, if any.
50
        # These are never used in the matching, just passed to the underlying function
51
        bound_args = ()
52
        bound_kwargs = {}
53
        values = bound_arguments.arguments.copy()
54
        for parameter in signature.parameters.values():
55
            if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
56
                bound_args = values.pop(parameter.name)
57
            if parameter.kind is inspect.Parameter.VAR_KEYWORD:
58
                bound_kwargs = values.pop(parameter.name)
59
        return bound_args, bound_kwargs, values
60
61
    def __call__(*args, **kwargs):
62
        # Okay, so, this is a convoluted mess.
63
        # First, we extract self from the beginning of the argument list
64
        self, *args = args
65
66
        bound_args, bound_kwargs, values = self._bound_and_values(args, kwargs)
67
68
        matchable_ = matchable.Matchable(values)
69
        for structure, func in self.matchers:
70
            if matchable_(structure):
71
                return _dispatch(func, matchable_.matches, bound_args, bound_kwargs)
72
        raise ValueError(values)
73
74
    @_pep_570_when.pep_570_when
75
    def when(self, kwargs):
76
        """Add a binding for this function."""
77
        return common.decorate(
78
            self.matchers, mapping_match.DictPattern(kwargs, exhaustive=True)
79
        )
80