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

structured_data.match   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 79
dl 0
loc 151
rs 10
c 0
b 0
f 0
wmc 16

5 Functions

Rating   Name   Duplication   Size   Complexity  
A _can_overwrite_kind() 0 5 3
A method() 0 5 2
A function() 0 5 2
A _make_args_positional() 0 11 3
A decorate_in_order() 0 9 2
1
"""Utilities for destructuring values using matchables and match targets.
2
3
Given a value to destructure, called ``value``:
4
5
- Construct a matchable: ``matchable = Matchable(value)``
6
- The matchable is initially falsy, but it will become truthy if it is passed a
7
  **match target** that matches ``value``:
8
  ``assert matchable(some_pattern_that_matches)`` (Matchable returns itself
9
  from the call, so you can put the calls in an if-elif block, and only make a
10
  given call at most once.)
11
- When the matchable is truthy, it can be indexed to access bindings created by
12
  the target.
13
"""
14
15
from __future__ import annotations
16
17
import inspect
18
import typing
19
20
from . import _attribute_constructor
21
from ._class_placeholder import placeholder
22
from ._match.descriptor import function as function_
23
from ._match.descriptor.property_ import Property
24
from ._match.destructure import names
25
from ._match.match_dict import MatchDict
26
from ._match.matchable import Matchable
27
from ._match.patterns.basic_patterns import Pattern
28
from ._match.patterns.bind import Bind
29
from ._match.patterns.mapping_match import AttrPattern
30
from ._match.patterns.mapping_match import DictPattern
31
32
# In lower-case for aesthetics.
33
pat = _attribute_constructor.AttributeConstructor(  # pylint: disable=invalid-name
34
    Pattern
35
)
36
37
38
def _can_overwrite_kind(parameter: inspect.Parameter) -> None:
39
    if parameter.kind is inspect.Parameter.POSITIONAL_ONLY:
40
        raise ValueError("Signature already contains positional-only arguments")
41
    if parameter.kind is not inspect.Parameter.POSITIONAL_OR_KEYWORD:
42
        raise ValueError("Cannot overwrite non-POSITIONAL_OR_KEYWORD kind")
43
44
45
def _make_args_positional(func: typing.Callable, positional_until: int) -> None:
46
    signature = inspect.signature(func)
47
    new_parameters = list(signature.parameters.values())
48
    for index, parameter in enumerate(new_parameters[:positional_until]):
49
        _can_overwrite_kind(parameter)
50
        new_parameters[index] = parameter.replace(
51
            kind=inspect.Parameter.POSITIONAL_ONLY
52
        )
53
    new_signature = signature.replace(parameters=new_parameters)
54
    if new_signature != signature:
55
        func.__signature__ = new_signature  # type: ignore
56
57
58
@typing.overload
59
def function(
60
    *, positional_until: int = 0
61
) -> typing.Callable[[typing.Callable], function_.Function]:
62
    """Can optionally specify a number of arguments to treat as positional."""
63
64
65
@typing.overload
66
def function(_func: typing.Callable) -> function_.Function:
67
    """Can directly decorate a function."""
68
69
70
# This wraps a function that, for reasons, can't be called directly by the code
71
# The function body should probably just be a docstring.
72
def function(
73
    _func: typing.Optional[typing.Callable] = None, *, positional_until: int = 0
74
) -> typing.Union[
75
    typing.Callable[[typing.Callable], function_.Function], function_.Function
76
]:
77
    """Convert a function to dispatch by value.
78
79
    The original function is not called when the dispatch function is invoked.
80
    """
81
82
    def wrap(func: typing.Callable) -> function_.Function:
83
        _make_args_positional(func, positional_until)
84
        return function_.Function(func)
85
86
    if _func is None:
87
        return wrap
88
89
    return wrap(_func)
90
91
92
@typing.overload
93
def method(
94
    *, positional_until: int = 1
95
) -> typing.Callable[[typing.Callable], function_.Method]:
96
    """Can optionally specify a number of arguments to treat as positional."""
97
98
99
@typing.overload
100
def method(_func: typing.Callable) -> function_.Method:
101
    """Can directly decorate a method."""
102
103
104
# This wraps a function that, for reasons, can't be called directly by the code
105
# The function body should probably just be a docstring.
106
def method(
107
    _func: typing.Optional[typing.Callable] = None, *, positional_until: int = 1
108
) -> typing.Union[
109
    typing.Callable[[typing.Callable], function_.Method], function_.Method
110
]:
111
    """Convert a function to dispatch by value.
112
113
    The original function is not called when the dispatch function is invoked.
114
    """
115
116
    def wrap(func: typing.Callable) -> function_.Method:
117
        _make_args_positional(func, positional_until)
118
        return function_.Method(func)
119
120
    if _func is None:
121
        return wrap
122
123
    return wrap(_func)
124
125
126
def decorate_in_order(*args):
127
    """Apply decorators in the order they're passed to the function."""
128
129
    def decorator(func):
130
        for arg in args:
131
            func = arg(func)
132
        return func
133
134
    return decorator
135
136
137
__all__ = [
138
    "AttrPattern",
139
    "Bind",
140
    "DictPattern",
141
    "MatchDict",
142
    "Matchable",
143
    "Pattern",
144
    "Property",
145
    "decorate_in_order",
146
    "function",
147
    "method",
148
    "placeholder",
149
    "names",
150
    "pat",
151
]
152