Passed
Push — master ( aa21cd...9d2577 )
by Max
01:10
created

structured_data._descriptor.function   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 81
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 59
dl 0
loc 81
rs 10
c 0
b 0
f 0
wmc 18

5 Methods

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

2 Functions

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