PyDecisionGraph 0.3.2.post1__cp315-cp315t-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- decision_graph/__infra__.pxd +1 -0
- decision_graph/__init__.pxd +1 -0
- decision_graph/__init__.py +39 -0
- decision_graph/decision_tree/__init__.pxd +2 -0
- decision_graph/decision_tree/__init__.py +85 -0
- decision_graph/decision_tree/bake/__infra__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.py +29 -0
- decision_graph/decision_tree/bake/c_action.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/decision_tree/bake/c_action.pxd +54 -0
- decision_graph/decision_tree/bake/c_action.pyi +126 -0
- decision_graph/decision_tree/bake/c_action.pyx +65 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
- decision_graph/decision_tree/bake/c_bake.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/decision_tree/bake/c_bake.pxd +52 -0
- decision_graph/decision_tree/bake/c_bake.pyi +151 -0
- decision_graph/decision_tree/bake/c_bake.pyx +142 -0
- decision_graph/decision_tree/bake/c_collections.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/decision_tree/bake/c_collections.pxd +52 -0
- decision_graph/decision_tree/bake/c_collections.pyi +193 -0
- decision_graph/decision_tree/bake/c_collections.pyx +161 -0
- decision_graph/decision_tree/bake/c_const.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/decision_tree/bake/c_const.pxd +56 -0
- decision_graph/decision_tree/bake/c_const.pyi +305 -0
- decision_graph/decision_tree/bake/c_const.pyx +278 -0
- decision_graph/decision_tree/bake/c_edge.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/decision_tree/bake/c_edge.pxd +117 -0
- decision_graph/decision_tree/bake/c_edge.pyi +192 -0
- decision_graph/decision_tree/bake/c_edge.pyx +270 -0
- decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/decision_tree/bake/c_eval.pxd +40 -0
- decision_graph/decision_tree/bake/c_expr.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/decision_tree/bake/c_expr.pxd +129 -0
- decision_graph/decision_tree/bake/c_expr.pyi +280 -0
- decision_graph/decision_tree/bake/c_expr.pyx +272 -0
- decision_graph/decision_tree/bake/c_hierarchy.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
- decision_graph/decision_tree/bake/c_logic_group.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
- decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
- decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
- decision_graph/decision_tree/bake/c_node.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/decision_tree/bake/c_node.pxd +381 -0
- decision_graph/decision_tree/bake/c_node.pyi +438 -0
- decision_graph/decision_tree/bake/c_node.pyx +639 -0
- decision_graph/decision_tree/bake/c_reconstruct.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
- decision_graph/decision_tree/bake/c_var.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/decision_tree/bake/c_var.pxd +205 -0
- decision_graph/decision_tree/bake/c_var.pyi +271 -0
- decision_graph/decision_tree/bake/c_var.pyx +228 -0
- decision_graph/decision_tree/capi/__infra__.pxd +33 -0
- decision_graph/decision_tree/capi/__init__.pxd +33 -0
- decision_graph/decision_tree/capi/__init__.py +57 -0
- decision_graph/decision_tree/capi/c_abc.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_abc.pxd +246 -0
- decision_graph/decision_tree/capi/c_abc.pyi +770 -0
- decision_graph/decision_tree/capi/c_abc.pyx +1532 -0
- decision_graph/decision_tree/capi/c_collection.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_collection.pxd +22 -0
- decision_graph/decision_tree/capi/c_collection.pyi +245 -0
- decision_graph/decision_tree/capi/c_collection.pyx +138 -0
- decision_graph/decision_tree/capi/c_node.cp315t-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_node.pxd +95 -0
- decision_graph/decision_tree/capi/c_node.pyi +492 -0
- decision_graph/decision_tree/capi/c_node.pyx +551 -0
- decision_graph/decision_tree/exc.py +189 -0
- decision_graph/decision_tree/native/__init__.py +57 -0
- decision_graph/decision_tree/native/abc.py +1167 -0
- decision_graph/decision_tree/native/collection.py +134 -0
- decision_graph/decision_tree/native/node.py +521 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59196 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39229 -0
- decision_graph/logic_group/__init__.py +25 -0
- decision_graph/logic_group/base.py +100 -0
- decision_graph/logic_group/pending_request.py +384 -0
- decision_graph/webui/__init__.py +24 -0
- decision_graph/webui/bake/__init__.py +65 -0
- decision_graph/webui/bake/app.py +488 -0
- decision_graph/webui/bake/static/d3.v7.min.js +2 -0
- decision_graph/webui/bake/static/script.js +1440 -0
- decision_graph/webui/bake/static/style.css +920 -0
- decision_graph/webui/bake/templates/index.html +183 -0
- decision_graph/webui/bake/templates/offline.html +184 -0
- decision_graph/webui/capi/__init__.py +54 -0
- decision_graph/webui/capi/app.py +366 -0
- decision_graph/webui/capi/main.py +53 -0
- decision_graph/webui/capi/static/d3.v7.min.js +2 -0
- decision_graph/webui/capi/static/script.js +1026 -0
- decision_graph/webui/capi/static/style.css +656 -0
- decision_graph/webui/capi/templates/index.html +93 -0
- decision_graph/webui/capi/templates/offline.html +94 -0
- decision_graph/webui/native/__init__.py +54 -0
- decision_graph/webui/native/app.py +366 -0
- decision_graph/webui/native/main.py +53 -0
- decision_graph/webui/native/static/d3.v7.min.js +2 -0
- decision_graph/webui/native/static/script.js +1026 -0
- decision_graph/webui/native/static/style.css +656 -0
- decision_graph/webui/native/templates/index.html +93 -0
- decision_graph/webui/native/templates/offline.html +94 -0
- pydecisiongraph-0.3.2.post1.dist-info/METADATA +136 -0
- pydecisiongraph-0.3.2.post1.dist-info/RECORD +148 -0
- pydecisiongraph-0.3.2.post1.dist-info/WHEEL +5 -0
- pydecisiongraph-0.3.2.post1.dist-info/licenses/LICENSE +373 -0
- pydecisiongraph-0.3.2.post1.dist-info/top_level.txt +2 -0
- tests/bake_c_evaluator.cp315t-win_amd64.pyd +0 -0
- tests/bake_eval_probe.cp315t-win_amd64.pyd +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from collections.abc import Mapping, Sequence, Generator
|
|
2
|
+
|
|
3
|
+
from . import LOGGER
|
|
4
|
+
from .abc import LogicGroup
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LogicMapping(LogicGroup):
|
|
8
|
+
def __init__(self, *, name: str, data: dict | None = None, parent: LogicGroup | None = None, contexts: dict | None = None, **kwargs):
|
|
9
|
+
super().__init__(name=name, parent=parent, contexts=contexts, **kwargs)
|
|
10
|
+
if data is None:
|
|
11
|
+
ctx_data = self.contexts.setdefault('data', {})
|
|
12
|
+
if isinstance(ctx_data, dict):
|
|
13
|
+
self.data = ctx_data
|
|
14
|
+
elif isinstance(ctx_data, Mapping):
|
|
15
|
+
LOGGER.info(f'Using non-dict mapping for {self} data, unlinking and converting to dict...')
|
|
16
|
+
self.data = dict(ctx_data)
|
|
17
|
+
else:
|
|
18
|
+
raise TypeError("The 'data' parameter must be a Mapping!")
|
|
19
|
+
else:
|
|
20
|
+
self.data = data
|
|
21
|
+
|
|
22
|
+
def _get(self, key: str):
|
|
23
|
+
return self.data[key]
|
|
24
|
+
|
|
25
|
+
def __bool__(self) -> bool:
|
|
26
|
+
return bool(self.data)
|
|
27
|
+
|
|
28
|
+
def __len__(self) -> int:
|
|
29
|
+
return len(self.data)
|
|
30
|
+
|
|
31
|
+
def __getitem__(self, key: str):
|
|
32
|
+
from .node import AttrExpression
|
|
33
|
+
return AttrExpression(attr=key, logic_group=self)
|
|
34
|
+
|
|
35
|
+
def __getattr__(self, key: str):
|
|
36
|
+
from .node import AttrExpression
|
|
37
|
+
return AttrExpression(attr=key, logic_group=self)
|
|
38
|
+
|
|
39
|
+
def __contains__(self, key: str) -> bool:
|
|
40
|
+
return key in self.data
|
|
41
|
+
|
|
42
|
+
def update(self, *args, **kwargs) -> None:
|
|
43
|
+
self.data.update(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
def clear(self) -> None:
|
|
46
|
+
self.data.clear()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LogicSequence(LogicGroup):
|
|
50
|
+
def __init__(self, *, name: str=None, data: list | None = None, parent: LogicGroup | None = None, contexts: dict | None = None, **kwargs):
|
|
51
|
+
super().__init__(name=name, parent=parent, contexts=contexts, **kwargs)
|
|
52
|
+
if data is None:
|
|
53
|
+
ctx_data = self.contexts.setdefault('data', [])
|
|
54
|
+
if isinstance(ctx_data, list):
|
|
55
|
+
self.data = ctx_data
|
|
56
|
+
elif isinstance(ctx_data, Sequence) and not isinstance(ctx_data, (str, bytes)):
|
|
57
|
+
LOGGER.info(f'Using non-list sequence for {self} data, converting to list...')
|
|
58
|
+
self.data = list(ctx_data)
|
|
59
|
+
else:
|
|
60
|
+
raise TypeError("The 'data' parameter must be a Sequence!")
|
|
61
|
+
else:
|
|
62
|
+
self.data = data
|
|
63
|
+
|
|
64
|
+
def _get(self, index: int):
|
|
65
|
+
return self.data[index]
|
|
66
|
+
|
|
67
|
+
def __iter__(self):
|
|
68
|
+
from .node import AttrExpression
|
|
69
|
+
for index in range(len(self.data)):
|
|
70
|
+
yield AttrExpression(attr=index, logic_group=self)
|
|
71
|
+
|
|
72
|
+
def __len__(self) -> int:
|
|
73
|
+
return len(self.data)
|
|
74
|
+
|
|
75
|
+
def __getitem__(self, index: int):
|
|
76
|
+
from .node import AttrExpression
|
|
77
|
+
return AttrExpression(attr=index, logic_group=self)
|
|
78
|
+
|
|
79
|
+
def __contains__(self, item) -> bool:
|
|
80
|
+
return item in self.data
|
|
81
|
+
|
|
82
|
+
def append(self, value) -> None:
|
|
83
|
+
self.data.append(value)
|
|
84
|
+
|
|
85
|
+
def extend(self, iterable) -> None:
|
|
86
|
+
self.data.extend(iterable)
|
|
87
|
+
|
|
88
|
+
def insert(self, index: int, value) -> None:
|
|
89
|
+
self.data.insert(index, value)
|
|
90
|
+
|
|
91
|
+
def remove(self, value) -> None:
|
|
92
|
+
self.data.remove(value)
|
|
93
|
+
|
|
94
|
+
def pop(self, index: int = -1):
|
|
95
|
+
return self.data.pop(index)
|
|
96
|
+
|
|
97
|
+
def clear(self) -> None:
|
|
98
|
+
self.data.clear()
|
|
99
|
+
|
|
100
|
+
def __bool__(self) -> bool:
|
|
101
|
+
return bool(self.data)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class LogicGenerator(LogicGroup):
|
|
105
|
+
def __init__(self, *, name: str, data: Generator | None = None, parent: LogicGroup | None = None, contexts: dict | None = None, **kwargs):
|
|
106
|
+
super().__init__(name=name, parent=parent, contexts=contexts, **kwargs)
|
|
107
|
+
if data is None:
|
|
108
|
+
data = self.contexts.get('data')
|
|
109
|
+
|
|
110
|
+
if isinstance(data, Generator):
|
|
111
|
+
self.data = data
|
|
112
|
+
else:
|
|
113
|
+
raise TypeError("The 'data' parameter must be a Generator!")
|
|
114
|
+
|
|
115
|
+
def _next(self):
|
|
116
|
+
# Simulates c_next
|
|
117
|
+
return next(self.data)
|
|
118
|
+
|
|
119
|
+
# === Python generator protocol ===
|
|
120
|
+
|
|
121
|
+
def __iter__(self):
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
def __next__(self):
|
|
125
|
+
return self._next()
|
|
126
|
+
|
|
127
|
+
def send(self, value):
|
|
128
|
+
return self.data.send(value)
|
|
129
|
+
|
|
130
|
+
def throw(self, typ, val=None, tb=None):
|
|
131
|
+
return self.data.throw(typ, val, tb)
|
|
132
|
+
|
|
133
|
+
def close(self):
|
|
134
|
+
return self.data.close()
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
import json
|
|
5
|
+
import operator
|
|
6
|
+
import traceback
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .abc import LGM, LogicNode, LogicGroup, NO_CONDITION, AUTO_CONDITION, NodeEdgeCondition, PlaceholderNode, BreakpointNode, ActionNode
|
|
11
|
+
from .collection import LogicMapping
|
|
12
|
+
from ..exc import NO_DEFAULT, TooManyChildren, TooFewChildren, EdgeValueError, ContextsNotFound, ExpressEvaluationError
|
|
13
|
+
|
|
14
|
+
UNARY_OP_FUNC = Callable[[Any], Any]
|
|
15
|
+
BINARY_OP_FUNC = Callable[[Any, Any], Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NodeEvalPath(list):
|
|
19
|
+
def to_clipboard(self):
|
|
20
|
+
from pyperclip import copy as clipboard_copy
|
|
21
|
+
path = []
|
|
22
|
+
for node in self:
|
|
23
|
+
path.append(str(node.uid))
|
|
24
|
+
payload = json.dumps(path)
|
|
25
|
+
clipboard_copy(payload)
|
|
26
|
+
return payload
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RootLogicNode(LogicNode):
|
|
30
|
+
def __init__(self, *, name: str = 'Entry Point', expression=True, dtype=bool, repr: str = None, inherit_contexts: bool = False, **kwargs):
|
|
31
|
+
super().__init__(expression=expression, dtype=dtype, repr=name or repr, **kwargs)
|
|
32
|
+
self.inherit_contexts = inherit_contexts
|
|
33
|
+
self.eval_path: list = []
|
|
34
|
+
|
|
35
|
+
def _entry_check(self) -> bool:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
def _on_enter(self) -> None:
|
|
39
|
+
self._append(PlaceholderNode(auto_connect=False), NO_CONDITION)
|
|
40
|
+
LGM._ln_enter(self)
|
|
41
|
+
if self.inherit_contexts:
|
|
42
|
+
active_group = LGM.active_group
|
|
43
|
+
LGM.shelve()
|
|
44
|
+
LGM.active_group = active_group.copy()
|
|
45
|
+
LGM.shelve()
|
|
46
|
+
LGM.inspection_mode = True
|
|
47
|
+
LGM._ln_enter(self)
|
|
48
|
+
|
|
49
|
+
def _on_exit(self) -> None:
|
|
50
|
+
self._consolidate_placeholder()
|
|
51
|
+
LGM._ln_exit(self)
|
|
52
|
+
LGM.unshelve()
|
|
53
|
+
LGM._ln_exit(self)
|
|
54
|
+
|
|
55
|
+
def _append(self, child: LogicNode, condition: NodeEdgeCondition) -> None:
|
|
56
|
+
if self.subordinates:
|
|
57
|
+
raise TooManyChildren()
|
|
58
|
+
if condition is not AUTO_CONDITION and condition is not NO_CONDITION:
|
|
59
|
+
raise EdgeValueError()
|
|
60
|
+
super()._append(child, NO_CONDITION)
|
|
61
|
+
|
|
62
|
+
def __call__(self, default=None):
|
|
63
|
+
# clear cached eval path and evaluate, returning only the value
|
|
64
|
+
self.eval_path.clear()
|
|
65
|
+
value = self._eval_recursively(self.eval_path, default)[0]
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
def eval_recursively(self, path: list | None = None, default: Any = NO_DEFAULT):
|
|
69
|
+
# keep a cached eval_path similar to the C implementation
|
|
70
|
+
self.eval_path.clear()
|
|
71
|
+
|
|
72
|
+
if path is None:
|
|
73
|
+
v, p = self._eval_recursively(self.eval_path, default)
|
|
74
|
+
else:
|
|
75
|
+
v, p = self._eval_recursively(path, default)
|
|
76
|
+
# accumulate path into the root's cached eval_path
|
|
77
|
+
self.eval_path.extend(p)
|
|
78
|
+
return v, p
|
|
79
|
+
|
|
80
|
+
def dry_run(self, enforce_dtype: bool = False):
|
|
81
|
+
for child in self.descendants:
|
|
82
|
+
if isinstance(child, ActionNode):
|
|
83
|
+
continue
|
|
84
|
+
try:
|
|
85
|
+
child._eval(enforce_dtype)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
raise ExpressEvaluationError(f"Failed to evaluate {self}, {traceback.format_exc()}") from e
|
|
88
|
+
|
|
89
|
+
def get_breakpoint(self) -> BreakpointNode | None:
|
|
90
|
+
for leaf in self.leaves:
|
|
91
|
+
if isinstance(leaf, BreakpointNode):
|
|
92
|
+
return leaf
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def to_html(self, file_name: str = None, with_eval: bool = True):
|
|
96
|
+
from ...webui.native import to_html
|
|
97
|
+
to_html(self, file_name or f'{self.repr}.html', with_eval)
|
|
98
|
+
|
|
99
|
+
def show(self, **kwargs):
|
|
100
|
+
from ...webui.native import show
|
|
101
|
+
show(self, **kwargs)
|
|
102
|
+
|
|
103
|
+
def watch(self, **kwargs):
|
|
104
|
+
from ...webui.native import watch
|
|
105
|
+
watch(self, **kwargs)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def child(self) -> LogicNode:
|
|
109
|
+
if self.subordinates:
|
|
110
|
+
return self.subordinates[0]
|
|
111
|
+
raise TooFewChildren()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ContextLogicExpression(LogicNode):
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
expression: float | int | bool | Exception | Callable[[], Any],
|
|
119
|
+
logic_group: LogicGroup | None = None,
|
|
120
|
+
dtype: type = None,
|
|
121
|
+
repr: str = None
|
|
122
|
+
):
|
|
123
|
+
super().__init__(expression=expression, dtype=dtype, repr=repr)
|
|
124
|
+
|
|
125
|
+
if logic_group is None:
|
|
126
|
+
logic_group = LGM.active_group
|
|
127
|
+
if logic_group is None:
|
|
128
|
+
raise ContextsNotFound(
|
|
129
|
+
f'Must assign a logic group or initialize {self.__class__.__name__} within a LogicGroup context!'
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self.logic_group = logic_group
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _safe_eval(v: Any) -> Any:
|
|
136
|
+
if isinstance(v, LogicNode):
|
|
137
|
+
return v._eval(False)
|
|
138
|
+
return v
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _safe_alias(v: Any) -> str:
|
|
142
|
+
if isinstance(v, LogicNode):
|
|
143
|
+
return v.repr
|
|
144
|
+
return str(v)
|
|
145
|
+
|
|
146
|
+
# --- Attribute access ---
|
|
147
|
+
def __getitem__(self, key: str) -> AttrExpression:
|
|
148
|
+
return AttrExpression(attr=key, logic_group=self.logic_group)
|
|
149
|
+
|
|
150
|
+
def __getattr__(self, key: str) -> AttrExpression:
|
|
151
|
+
if key.startswith('_'):
|
|
152
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{key}'")
|
|
153
|
+
return AttrExpression(attr=key, logic_group=self.logic_group)
|
|
154
|
+
|
|
155
|
+
# --- Math ---
|
|
156
|
+
def __add__(self, other):
|
|
157
|
+
return MathExpression(left=self, op=MathExpressionOperator.add, right=other, logic_group=self.logic_group)
|
|
158
|
+
|
|
159
|
+
def __sub__(self, other):
|
|
160
|
+
return MathExpression(left=self, op=MathExpressionOperator.sub, right=other, logic_group=self.logic_group)
|
|
161
|
+
|
|
162
|
+
def __mul__(self, other):
|
|
163
|
+
return MathExpression(left=self, op=MathExpressionOperator.mul, right=other, logic_group=self.logic_group)
|
|
164
|
+
|
|
165
|
+
def __truediv__(self, other):
|
|
166
|
+
return MathExpression(left=self, op=MathExpressionOperator.truediv, right=other, logic_group=self.logic_group)
|
|
167
|
+
|
|
168
|
+
def __floordiv__(self, other):
|
|
169
|
+
return MathExpression(left=self, op=MathExpressionOperator.floordiv, right=other, logic_group=self.logic_group)
|
|
170
|
+
|
|
171
|
+
def __pow__(self, other):
|
|
172
|
+
return MathExpression(left=self, op=MathExpressionOperator.pow, right=other, logic_group=self.logic_group)
|
|
173
|
+
|
|
174
|
+
def __neg__(self):
|
|
175
|
+
return MathExpression(left=self, op=MathExpressionOperator.neg, repr=f'-{self.repr}', logic_group=self.logic_group)
|
|
176
|
+
|
|
177
|
+
# --- Comparisons ---
|
|
178
|
+
def __eq__(self, other):
|
|
179
|
+
return ComparisonExpression(left=self, op=ComparisonExpressionOperator.eq, right=other, logic_group=self.logic_group)
|
|
180
|
+
|
|
181
|
+
def __ne__(self, other):
|
|
182
|
+
return ComparisonExpression(left=self, op=ComparisonExpressionOperator.ne, right=other, logic_group=self.logic_group)
|
|
183
|
+
|
|
184
|
+
def __gt__(self, other):
|
|
185
|
+
return ComparisonExpression(left=self, op=ComparisonExpressionOperator.gt, right=other, logic_group=self.logic_group)
|
|
186
|
+
|
|
187
|
+
def __ge__(self, other):
|
|
188
|
+
return ComparisonExpression(left=self, op=ComparisonExpressionOperator.ge, right=other, logic_group=self.logic_group)
|
|
189
|
+
|
|
190
|
+
def __lt__(self, other):
|
|
191
|
+
return ComparisonExpression(left=self, op=ComparisonExpressionOperator.lt, right=other, logic_group=self.logic_group)
|
|
192
|
+
|
|
193
|
+
def __le__(self, other):
|
|
194
|
+
return ComparisonExpression(left=self, op=ComparisonExpressionOperator.le, right=other, logic_group=self.logic_group)
|
|
195
|
+
|
|
196
|
+
# --- Logical ---
|
|
197
|
+
def __and__(self, other):
|
|
198
|
+
return LogicalExpression(left=self, op=LogicalExpressionOperator.and_, right=other, logic_group=self.logic_group)
|
|
199
|
+
|
|
200
|
+
def __or__(self, other):
|
|
201
|
+
return LogicalExpression(left=self, op=LogicalExpressionOperator.or_, right=other, logic_group=self.logic_group)
|
|
202
|
+
|
|
203
|
+
def __invert__(self):
|
|
204
|
+
return LogicalExpression(left=self, op=LogicalExpressionOperator.not_, repr=f'~{self.repr}', logic_group=self.logic_group)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class AttrExpression(ContextLogicExpression):
|
|
208
|
+
def __init__(
|
|
209
|
+
self,
|
|
210
|
+
*,
|
|
211
|
+
attr: str,
|
|
212
|
+
expression: float | int | bool | Exception | Callable[[], Any] = None,
|
|
213
|
+
logic_group: LogicGroup | None = None,
|
|
214
|
+
dtype: type = None,
|
|
215
|
+
repr: str = None
|
|
216
|
+
):
|
|
217
|
+
self.attr = attr
|
|
218
|
+
|
|
219
|
+
super().__init__(
|
|
220
|
+
expression=self.eval if expression is None else expression,
|
|
221
|
+
logic_group=logic_group,
|
|
222
|
+
dtype=dtype,
|
|
223
|
+
repr=f'{logic_group.name}.{attr}' if repr is None else repr
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def _eval(self, enforce_dtype: bool) -> Any:
|
|
227
|
+
if isinstance(self.logic_group, LogicMapping):
|
|
228
|
+
return self.logic_group._get(self.attr)
|
|
229
|
+
else:
|
|
230
|
+
if self.attr in self.logic_group.contexts:
|
|
231
|
+
return self.logic_group.contexts[self.attr]
|
|
232
|
+
raise AttributeError(f'Attribute {self.attr} does not exist in {self.logic_group}')
|
|
233
|
+
|
|
234
|
+
def __getitem__(self, key: str) -> AttrNestedExpression:
|
|
235
|
+
return AttrNestedExpression(attrs=[self.attr, key], logic_group=self.logic_group)
|
|
236
|
+
|
|
237
|
+
def __getattr__(self, key: str) -> AttrNestedExpression:
|
|
238
|
+
if key.startswith('_'):
|
|
239
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{key}'")
|
|
240
|
+
return AttrNestedExpression(attrs=[self.attr, key], logic_group=self.logic_group)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class AttrNestedExpression(ContextLogicExpression):
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
*,
|
|
247
|
+
attrs: list[str],
|
|
248
|
+
expression: float | int | bool | Exception | Callable[[], Any] = None,
|
|
249
|
+
logic_group: LogicGroup | None = None,
|
|
250
|
+
dtype: type = None,
|
|
251
|
+
repr: str = None
|
|
252
|
+
):
|
|
253
|
+
self.attrs = attrs
|
|
254
|
+
|
|
255
|
+
super().__init__(
|
|
256
|
+
expression=self.eval if expression is None else expression,
|
|
257
|
+
logic_group=logic_group,
|
|
258
|
+
dtype=dtype,
|
|
259
|
+
repr=f'{logic_group.name}.{".".join(attrs)}' if repr is None else repr
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def _eval(self, enforce_dtype: bool) -> Any:
|
|
263
|
+
if isinstance(self.logic_group, LogicMapping):
|
|
264
|
+
mapping = self.logic_group.data
|
|
265
|
+
else:
|
|
266
|
+
mapping = self.logic_group.contexts
|
|
267
|
+
|
|
268
|
+
for attr in self.attrs:
|
|
269
|
+
mapping = mapping[attr]
|
|
270
|
+
return mapping
|
|
271
|
+
|
|
272
|
+
def __getitem__(self, key: str) -> AttrNestedExpression:
|
|
273
|
+
return AttrNestedExpression(attrs=self.attrs + [key], logic_group=self.logic_group)
|
|
274
|
+
|
|
275
|
+
def __getattr__(self, key: str) -> AttrNestedExpression:
|
|
276
|
+
if key.startswith('_'):
|
|
277
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{key}'")
|
|
278
|
+
return AttrNestedExpression(attrs=self.attrs + [key], logic_group=self.logic_group)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# Python does not enforce strict typing. So the Getter*Expression classes are just aliases.
|
|
282
|
+
GetterExpression = AttrExpression
|
|
283
|
+
GetterNestedExpression = AttrNestedExpression
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class MathExpressionOperator(enum.StrEnum):
|
|
287
|
+
add = '+'
|
|
288
|
+
sub = '-'
|
|
289
|
+
mul = '*'
|
|
290
|
+
truediv = '/'
|
|
291
|
+
floordiv = '//'
|
|
292
|
+
pow = '**'
|
|
293
|
+
neg = '-'
|
|
294
|
+
|
|
295
|
+
def to_func(self) -> Callable:
|
|
296
|
+
return getattr(operator, self.name)
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def from_str(cls, op_str: str) -> MathExpressionOperator:
|
|
300
|
+
for op in list(cls):
|
|
301
|
+
op: MathExpressionOperator
|
|
302
|
+
if op.name == op_str or op.value == op_str:
|
|
303
|
+
return op
|
|
304
|
+
raise ValueError(f'Unknown MathExpressionOperator: {op_str}')
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class ComparisonExpressionOperator(enum.StrEnum):
|
|
308
|
+
eq = '=='
|
|
309
|
+
ne = '!='
|
|
310
|
+
gt = '>'
|
|
311
|
+
ge = '>='
|
|
312
|
+
lt = '<'
|
|
313
|
+
le = '<='
|
|
314
|
+
|
|
315
|
+
def to_func(self) -> Callable:
|
|
316
|
+
return getattr(operator, self.name)
|
|
317
|
+
|
|
318
|
+
@classmethod
|
|
319
|
+
def from_str(cls, op_str: str) -> ComparisonExpressionOperator:
|
|
320
|
+
for op in list(cls):
|
|
321
|
+
op: ComparisonExpressionOperator
|
|
322
|
+
if op.name == op_str or op.value == op_str:
|
|
323
|
+
return op
|
|
324
|
+
raise ValueError(f'Unknown ComparisonExpressionOperator: {op_str}')
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class LogicalExpressionOperator(enum.StrEnum):
|
|
328
|
+
and_ = '&'
|
|
329
|
+
or_ = '|'
|
|
330
|
+
not_ = '~'
|
|
331
|
+
|
|
332
|
+
def to_func(self) -> Callable:
|
|
333
|
+
return getattr(operator, self.name)
|
|
334
|
+
|
|
335
|
+
@classmethod
|
|
336
|
+
def from_str(cls, op_str: str) -> LogicalExpressionOperator:
|
|
337
|
+
for op in list(cls):
|
|
338
|
+
op: LogicalExpressionOperator
|
|
339
|
+
if op.name == op_str or op.value == op_str:
|
|
340
|
+
return op
|
|
341
|
+
raise ValueError(f'Unknown LogicalExpressionOperator: {op_str}')
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class MathExpression(ContextLogicExpression):
|
|
345
|
+
def __init__(
|
|
346
|
+
self,
|
|
347
|
+
*,
|
|
348
|
+
left: Any,
|
|
349
|
+
op: UNARY_OP_FUNC | BINARY_OP_FUNC | MathExpressionOperator | str,
|
|
350
|
+
right: Any = NO_DEFAULT,
|
|
351
|
+
expression: float | int | bool | Exception | Callable[[], Any] = NO_DEFAULT,
|
|
352
|
+
logic_group: LogicGroup | None = None,
|
|
353
|
+
dtype: type = None,
|
|
354
|
+
repr: str = None,
|
|
355
|
+
**kwargs
|
|
356
|
+
):
|
|
357
|
+
self.left = left
|
|
358
|
+
self.right = right
|
|
359
|
+
|
|
360
|
+
if isinstance(op, MathExpressionOperator):
|
|
361
|
+
self.op_name = kwargs.get('op_name', op.name)
|
|
362
|
+
self.op_repr = kwargs.get('op_repr', op.value)
|
|
363
|
+
self.op_func = op.to_func()
|
|
364
|
+
repr = repr or self._op_style_repr()
|
|
365
|
+
elif isinstance(op, str):
|
|
366
|
+
op_obj = MathExpressionOperator.from_str(op)
|
|
367
|
+
self.op_name = kwargs.get('op_name', op_obj.name)
|
|
368
|
+
self.op_repr = kwargs.get('op_repr', op_obj.value)
|
|
369
|
+
self.op_func = op_obj.to_func()
|
|
370
|
+
repr = repr or self._op_style_repr()
|
|
371
|
+
elif callable(op):
|
|
372
|
+
self.op_name = op.__name__
|
|
373
|
+
self.op_repr = op.__name__
|
|
374
|
+
self.op_func = op
|
|
375
|
+
repr = repr or self._func_style_repr()
|
|
376
|
+
else:
|
|
377
|
+
raise TypeError(f'Expected op to be MathExpressionOperator, str or callable, got {type(op).__name__}.')
|
|
378
|
+
|
|
379
|
+
super().__init__(
|
|
380
|
+
expression=self.eval if expression is NO_DEFAULT else expression,
|
|
381
|
+
logic_group=logic_group,
|
|
382
|
+
dtype=float if dtype is None else dtype,
|
|
383
|
+
repr=repr,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def _op_style_repr(self) -> str:
|
|
387
|
+
if self.right is NO_DEFAULT:
|
|
388
|
+
return f'{self.op_repr}{self._safe_alias(self.left)}'
|
|
389
|
+
return f'{self._safe_alias(self.left)} {self.op_repr} {self._safe_alias(self.right)}'
|
|
390
|
+
|
|
391
|
+
def _func_style_repr(self) -> str:
|
|
392
|
+
if self.right is NO_DEFAULT:
|
|
393
|
+
return f'{self.op_repr}({ContextLogicExpression.c_safe_alias(self.left)})'
|
|
394
|
+
return f'{self.op_repr}({ContextLogicExpression.c_safe_alias(self.left)}, {ContextLogicExpression.c_safe_alias(self.right)})'
|
|
395
|
+
|
|
396
|
+
def _eval(self, enforce_dtype: bool) -> Any:
|
|
397
|
+
left_val = self._safe_eval(self.left)
|
|
398
|
+
if self.right is NO_DEFAULT:
|
|
399
|
+
return self.op_func(left_val)
|
|
400
|
+
right_val = self._safe_eval(self.right)
|
|
401
|
+
return self.op_func(left_val, right_val)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class ComparisonExpression(ContextLogicExpression):
|
|
405
|
+
def __init__(
|
|
406
|
+
self,
|
|
407
|
+
*,
|
|
408
|
+
left: Any,
|
|
409
|
+
op: UNARY_OP_FUNC | BINARY_OP_FUNC | ComparisonExpressionOperator | str,
|
|
410
|
+
right: Any = NO_DEFAULT,
|
|
411
|
+
expression: float | int | bool | Exception | Callable[[], Any] = NO_DEFAULT,
|
|
412
|
+
logic_group: LogicGroup | None = None,
|
|
413
|
+
dtype: type = None,
|
|
414
|
+
repr: str = None,
|
|
415
|
+
**kwargs
|
|
416
|
+
):
|
|
417
|
+
self.left = left
|
|
418
|
+
self.right = right
|
|
419
|
+
|
|
420
|
+
if isinstance(op, ComparisonExpressionOperator):
|
|
421
|
+
self.op_name = kwargs.get('op_name', op.name)
|
|
422
|
+
self.op_repr = kwargs.get('op_repr', op.value)
|
|
423
|
+
self.op_func = op.to_func()
|
|
424
|
+
repr = repr or self._op_style_repr()
|
|
425
|
+
elif isinstance(op, str):
|
|
426
|
+
op_obj = ComparisonExpressionOperator.from_str(op)
|
|
427
|
+
self.op_name = kwargs.get('op_name', op_obj.name)
|
|
428
|
+
self.op_repr = kwargs.get('op_repr', op_obj.value)
|
|
429
|
+
self.op_func = op_obj.to_func()
|
|
430
|
+
repr = repr or self._op_style_repr()
|
|
431
|
+
elif callable(op):
|
|
432
|
+
self.op_name = op.__name__
|
|
433
|
+
self.op_repr = op.__name__
|
|
434
|
+
self.op_func = op
|
|
435
|
+
repr = repr or self._func_style_repr()
|
|
436
|
+
else:
|
|
437
|
+
raise TypeError(f'Expected op to be ComparisonExpressionOperator, str or callable, got {type(op).__name__}.')
|
|
438
|
+
|
|
439
|
+
super().__init__(
|
|
440
|
+
expression=self.eval if expression is NO_DEFAULT else expression,
|
|
441
|
+
logic_group=logic_group,
|
|
442
|
+
dtype=bool if dtype is None else dtype,
|
|
443
|
+
repr=repr,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
def _op_style_repr(self) -> str:
|
|
447
|
+
if self.right is NO_DEFAULT:
|
|
448
|
+
return f'{self.op_repr}{self._safe_alias(self.left)}'
|
|
449
|
+
return f'{self._safe_alias(self.left)} {self.op_repr} {self._safe_alias(self.right)}'
|
|
450
|
+
|
|
451
|
+
def _func_style_repr(self) -> str:
|
|
452
|
+
if self.right is NO_DEFAULT:
|
|
453
|
+
return f'{self.op_repr}({ContextLogicExpression.c_safe_alias(self.left)})'
|
|
454
|
+
return f'{self.op_repr}({ContextLogicExpression.c_safe_alias(self.left)}, {ContextLogicExpression.c_safe_alias(self.right)})'
|
|
455
|
+
|
|
456
|
+
def _eval(self, enforce_dtype: bool) -> bool:
|
|
457
|
+
left_val = self._safe_eval(self.left)
|
|
458
|
+
if self.right is NO_DEFAULT:
|
|
459
|
+
return self.op_func(left_val)
|
|
460
|
+
right_val = self._safe_eval(self.right)
|
|
461
|
+
return self.op_func(left_val, right_val)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class LogicalExpression(ContextLogicExpression):
|
|
465
|
+
def __init__(
|
|
466
|
+
self,
|
|
467
|
+
*,
|
|
468
|
+
left: Any,
|
|
469
|
+
op: UNARY_OP_FUNC | BINARY_OP_FUNC | LogicalExpressionOperator | str,
|
|
470
|
+
right: Any = NO_DEFAULT,
|
|
471
|
+
expression: float | int | bool | Exception | Callable[[], Any] = NO_DEFAULT,
|
|
472
|
+
logic_group: LogicGroup | None = None,
|
|
473
|
+
dtype: type = None,
|
|
474
|
+
repr: str = None,
|
|
475
|
+
**kwargs
|
|
476
|
+
):
|
|
477
|
+
self.left = left
|
|
478
|
+
self.right = right
|
|
479
|
+
|
|
480
|
+
if isinstance(op, LogicalExpressionOperator):
|
|
481
|
+
self.op_name = kwargs.get('op_name', op.name)
|
|
482
|
+
self.op_repr = kwargs.get('op_repr', op.value)
|
|
483
|
+
self.op_func = op.to_func()
|
|
484
|
+
repr = repr or self._op_style_repr()
|
|
485
|
+
elif isinstance(op, str):
|
|
486
|
+
op_obj = LogicalExpressionOperator.from_str(op)
|
|
487
|
+
self.op_name = kwargs.get('op_name', op_obj.name)
|
|
488
|
+
self.op_repr = kwargs.get('op_repr', op_obj.value)
|
|
489
|
+
self.op_func = op_obj.to_func()
|
|
490
|
+
repr = repr or self._op_style_repr()
|
|
491
|
+
elif callable(op):
|
|
492
|
+
self.op_name = op.__name__
|
|
493
|
+
self.op_repr = op.__name__
|
|
494
|
+
self.op_func = op
|
|
495
|
+
repr = repr or self._func_style_repr()
|
|
496
|
+
else:
|
|
497
|
+
raise TypeError(f'Expected op to be LogicalExpressionOperator, str or callable, got {type(op).__name__}.')
|
|
498
|
+
|
|
499
|
+
super().__init__(
|
|
500
|
+
expression=self.eval if expression is NO_DEFAULT else expression,
|
|
501
|
+
logic_group=logic_group,
|
|
502
|
+
dtype=bool if dtype is None else dtype,
|
|
503
|
+
repr=repr,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
def _op_style_repr(self) -> str:
|
|
507
|
+
if self.right is NO_DEFAULT:
|
|
508
|
+
return f'{self.op_repr}{self._safe_alias(self.left)}'
|
|
509
|
+
return f'{self._safe_alias(self.left)} {self.op_repr} {self._safe_alias(self.right)}'
|
|
510
|
+
|
|
511
|
+
def _func_style_repr(self) -> str:
|
|
512
|
+
if self.right is NO_DEFAULT:
|
|
513
|
+
return f'{self.op_repr}({ContextLogicExpression.c_safe_alias(self.left)})'
|
|
514
|
+
return f'{self.op_repr}({ContextLogicExpression.c_safe_alias(self.left)}, {ContextLogicExpression.c_safe_alias(self.right)})'
|
|
515
|
+
|
|
516
|
+
def _eval(self, enforce_dtype: bool) -> bool:
|
|
517
|
+
left_val = self._safe_eval(self.left)
|
|
518
|
+
if self.right is NO_DEFAULT:
|
|
519
|
+
return self.op_func(left_val)
|
|
520
|
+
right_val = self._safe_eval(self.right)
|
|
521
|
+
return self.op_func(left_val, right_val)
|