networkx_query.definition.operator_factory()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nop 2
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
"""Define type and structure for query language."""
2
from enum import Enum, unique
3
from typing import Any, Callable, List, NamedTuple, Optional, Tuple, Type, Union
4
5
__all__ = [
6
    'Path',
7
    "Evaluator",
8
    "operator_factory",
9
    "OperatoryArity",
10
    "OperatorDefinition",
11
    "ItemAST",
12
    "NETWORKX_OPERATORS_REGISTERY",
13
    "register_operator",
14
]
15
16
NETWORKX_OPERATORS_REGISTERY = {}
17
"""Internal registry of operators."""
18
19
Path = Union[Tuple, str]
20
"""Node attribut path definition."""
21
22
Evaluator = Callable[[Any], bool]
23
"""Predicate function."""
24
25
26
def operator_factory(op_function: Callable, *args: Any) -> Evaluator:
27
    """Add context parameter to operator function.
28
29
    Arguments:
30
        op_function (Callable): any function with context evaluation has first parameter
31
        *args (Any): a list of parameter to enclose
32
33
    Returns:
34
        (Evaluator): evaluator function with single argument
35
36
    """
37
    return lambda context: op_function(context, *args)
38
39
40
@unique
41
class OperatoryArity(Enum):
42
    """Define operator arity constant."""
43
44
    UNARY = 1
45
    BINARY = 2
46
    TERNARY = 3
47
    NARY = 42
48
49
    @property
50
    def arity(self) -> int:
51
        """Returns awaiting arity of associated function."""
52
        return self.value if self != OperatoryArity.NARY else 1
53
54
    def is_compliant(self, parameters) -> Tuple[bool, int]:
55
        """Check if parameters cardinality is compliant with operator arity.
56
57
        Returns:
58
            (Tuple[bool, int]): (compliant, delta_paramaters)
59
                compliant is true if this is compliante
60
                delta_paramaters:
61
                    - 0 if compliante,
62
                    - < 0 if some parameters is missing,
63
                    - > 0 if too much parameters is given
64
65
        """
66
        # context parameter is ommited here
67
        count = len(parameters)
68
        if self.value == OperatoryArity.NARY.value:
69
            match = count > 0
70
            return (match, 0 if match else -1)
71
        match = (count + 1) == self.arity
72
        delta = (count + 1) - self.arity
73
        return (match, 0 if match else delta)
74
75
76
class OperatorDefinition(NamedTuple):
77
    """Operator definition.
78
79
    Attributes:
80
        name (str): operator shortname
81
        function (Callable): first argument must be a context to evaluate
82
        arity (OperatoryArity): arity of operator function
83
        combinator (Optional[bool]): Flag which indicate if this operator is a combination of other operator
84
        profile (Optional[List[Type]]): optional function profile
85
        alias (Optional[str]): optional alias name for this operator
86
87
    """
88
89
    name: str
90
    function: Callable
91
    arity: OperatoryArity
92
    combinator: Optional[bool] = False
93
    profile: Optional[List[Type]] = None
94
    alias: Optional[str] = None
95
96
    def __str__(self):
97
        return self.name
98
99
100
class ItemAST(NamedTuple):
101
    """Define our AST."""
102
103
    op: OperatorDefinition
104
    args: List[Any]
105
106
    def check_arity(self) -> Tuple[bool, int]:
107
        """Check arity of this item against operator definition.
108
109
        Utilities short cut to OperatorDefinition#OperatoryArity#is_compliant.
110
111
        Returns:
112
            (Tuple[bool, int]): (match, delta_parameters_count)
113
114
        """
115
        return self.op.arity.is_compliant(self.args)
116
117
    def check_profile(self) -> bool:  # pragma: no cover
118
        # TODO Fix: did not work with generic...
119
        if self.op.profile:
120
            if not self.op.combinator:
121
                # terminal operator
122
                for i in range(1, self.op.arity.value):  # we omit first parameters (not in args)
123
                    if not isinstance(self.args[i - 1], self.op.profile[i]):
124
                        return False
125
            else:
126
                for arg in self.args:
127
                    if not isinstance(arg, self.op.profile[1]):
128
                        return False
129
        return True
130
131
132
class ParserException(RuntimeError):
133
    """Define a parser exception with stack of expression."""
134
135
    def __init__(self, message, stack):
136
        super(RuntimeError, self).__init__(message)
137
        self.stack = stack
138
139
140
def register_operator(
141
    name: str,
142
    arity: OperatoryArity,
143
    combinator: Optional[bool] = False,
144
    profile: Optional[List[Any]] = None,
145
    alias: Optional[str] = None,
146
):
147
    """Register an operator."""
148
    global NETWORKX_OPERATORS_REGISTERY
149
150
    def decorate_function(f):
151
        _definition = OperatorDefinition(
152
            name=name, function=f, arity=arity, combinator=combinator, profile=profile, alias=alias
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable alias does not seem to be defined.
Loading history...
153
        )
154
        NETWORKX_OPERATORS_REGISTERY[_definition.name] = _definition
155
        if alias:
156
            NETWORKX_OPERATORS_REGISTERY[alias] = _definition
157
        return f
158
159
    return decorate_function
160