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.
Files changed (148) hide show
  1. decision_graph/__infra__.pxd +1 -0
  2. decision_graph/__init__.pxd +1 -0
  3. decision_graph/__init__.py +39 -0
  4. decision_graph/decision_tree/__init__.pxd +2 -0
  5. decision_graph/decision_tree/__init__.py +85 -0
  6. decision_graph/decision_tree/bake/__infra__.pxd +502 -0
  7. decision_graph/decision_tree/bake/__init__.pxd +502 -0
  8. decision_graph/decision_tree/bake/__init__.py +29 -0
  9. decision_graph/decision_tree/bake/c_action.cp315t-win_amd64.pyd +0 -0
  10. decision_graph/decision_tree/bake/c_action.h +324 -0
  11. decision_graph/decision_tree/bake/c_action.pxd +54 -0
  12. decision_graph/decision_tree/bake/c_action.pyi +126 -0
  13. decision_graph/decision_tree/bake/c_action.pyx +65 -0
  14. decision_graph/decision_tree/bake/c_allocator_protocol.cp315t-win_amd64.pyd +0 -0
  15. decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
  16. decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
  17. decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
  18. decision_graph/decision_tree/bake/c_bake.cp315t-win_amd64.pyd +0 -0
  19. decision_graph/decision_tree/bake/c_bake.h +629 -0
  20. decision_graph/decision_tree/bake/c_bake.pxd +52 -0
  21. decision_graph/decision_tree/bake/c_bake.pyi +151 -0
  22. decision_graph/decision_tree/bake/c_bake.pyx +142 -0
  23. decision_graph/decision_tree/bake/c_collections.cp315t-win_amd64.pyd +0 -0
  24. decision_graph/decision_tree/bake/c_collections.h +692 -0
  25. decision_graph/decision_tree/bake/c_collections.pxd +52 -0
  26. decision_graph/decision_tree/bake/c_collections.pyi +193 -0
  27. decision_graph/decision_tree/bake/c_collections.pyx +161 -0
  28. decision_graph/decision_tree/bake/c_const.cp315t-win_amd64.pyd +0 -0
  29. decision_graph/decision_tree/bake/c_const.h +395 -0
  30. decision_graph/decision_tree/bake/c_const.pxd +56 -0
  31. decision_graph/decision_tree/bake/c_const.pyi +305 -0
  32. decision_graph/decision_tree/bake/c_const.pyx +278 -0
  33. decision_graph/decision_tree/bake/c_edge.cp315t-win_amd64.pyd +0 -0
  34. decision_graph/decision_tree/bake/c_edge.h +511 -0
  35. decision_graph/decision_tree/bake/c_edge.pxd +117 -0
  36. decision_graph/decision_tree/bake/c_edge.pyi +192 -0
  37. decision_graph/decision_tree/bake/c_edge.pyx +270 -0
  38. decision_graph/decision_tree/bake/c_eval.h +848 -0
  39. decision_graph/decision_tree/bake/c_eval.pxd +40 -0
  40. decision_graph/decision_tree/bake/c_expr.cp315t-win_amd64.pyd +0 -0
  41. decision_graph/decision_tree/bake/c_expr.h +1900 -0
  42. decision_graph/decision_tree/bake/c_expr.pxd +129 -0
  43. decision_graph/decision_tree/bake/c_expr.pyi +280 -0
  44. decision_graph/decision_tree/bake/c_expr.pyx +272 -0
  45. decision_graph/decision_tree/bake/c_hierarchy.cp315t-win_amd64.pyd +0 -0
  46. decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  47. decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
  48. decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
  49. decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
  50. decision_graph/decision_tree/bake/c_logic_group.cp315t-win_amd64.pyd +0 -0
  51. decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  52. decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
  53. decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
  54. decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
  55. decision_graph/decision_tree/bake/c_node.cp315t-win_amd64.pyd +0 -0
  56. decision_graph/decision_tree/bake/c_node.h +2660 -0
  57. decision_graph/decision_tree/bake/c_node.pxd +381 -0
  58. decision_graph/decision_tree/bake/c_node.pyi +438 -0
  59. decision_graph/decision_tree/bake/c_node.pyx +639 -0
  60. decision_graph/decision_tree/bake/c_reconstruct.cp315t-win_amd64.pyd +0 -0
  61. decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
  62. decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
  63. decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
  64. decision_graph/decision_tree/bake/c_var.cp315t-win_amd64.pyd +0 -0
  65. decision_graph/decision_tree/bake/c_var.h +2054 -0
  66. decision_graph/decision_tree/bake/c_var.pxd +205 -0
  67. decision_graph/decision_tree/bake/c_var.pyi +271 -0
  68. decision_graph/decision_tree/bake/c_var.pyx +228 -0
  69. decision_graph/decision_tree/capi/__infra__.pxd +33 -0
  70. decision_graph/decision_tree/capi/__init__.pxd +33 -0
  71. decision_graph/decision_tree/capi/__init__.py +57 -0
  72. decision_graph/decision_tree/capi/c_abc.cp315t-win_amd64.pyd +0 -0
  73. decision_graph/decision_tree/capi/c_abc.pxd +246 -0
  74. decision_graph/decision_tree/capi/c_abc.pyi +770 -0
  75. decision_graph/decision_tree/capi/c_abc.pyx +1532 -0
  76. decision_graph/decision_tree/capi/c_collection.cp315t-win_amd64.pyd +0 -0
  77. decision_graph/decision_tree/capi/c_collection.pxd +22 -0
  78. decision_graph/decision_tree/capi/c_collection.pyi +245 -0
  79. decision_graph/decision_tree/capi/c_collection.pyx +138 -0
  80. decision_graph/decision_tree/capi/c_node.cp315t-win_amd64.pyd +0 -0
  81. decision_graph/decision_tree/capi/c_node.pxd +95 -0
  82. decision_graph/decision_tree/capi/c_node.pyi +492 -0
  83. decision_graph/decision_tree/capi/c_node.pyx +551 -0
  84. decision_graph/decision_tree/exc.py +189 -0
  85. decision_graph/decision_tree/native/__init__.py +57 -0
  86. decision_graph/decision_tree/native/abc.py +1167 -0
  87. decision_graph/decision_tree/native/collection.py +134 -0
  88. decision_graph/decision_tree/native/node.py +521 -0
  89. decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
  90. decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
  91. decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
  92. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
  93. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
  94. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
  95. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
  96. decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
  97. decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
  98. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
  99. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
  100. decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
  101. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
  102. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
  103. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
  104. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  105. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
  106. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  107. decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
  108. decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
  109. decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
  110. decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
  111. decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
  112. decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59196 -0
  113. decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
  114. decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39229 -0
  115. decision_graph/logic_group/__init__.py +25 -0
  116. decision_graph/logic_group/base.py +100 -0
  117. decision_graph/logic_group/pending_request.py +384 -0
  118. decision_graph/webui/__init__.py +24 -0
  119. decision_graph/webui/bake/__init__.py +65 -0
  120. decision_graph/webui/bake/app.py +488 -0
  121. decision_graph/webui/bake/static/d3.v7.min.js +2 -0
  122. decision_graph/webui/bake/static/script.js +1440 -0
  123. decision_graph/webui/bake/static/style.css +920 -0
  124. decision_graph/webui/bake/templates/index.html +183 -0
  125. decision_graph/webui/bake/templates/offline.html +184 -0
  126. decision_graph/webui/capi/__init__.py +54 -0
  127. decision_graph/webui/capi/app.py +366 -0
  128. decision_graph/webui/capi/main.py +53 -0
  129. decision_graph/webui/capi/static/d3.v7.min.js +2 -0
  130. decision_graph/webui/capi/static/script.js +1026 -0
  131. decision_graph/webui/capi/static/style.css +656 -0
  132. decision_graph/webui/capi/templates/index.html +93 -0
  133. decision_graph/webui/capi/templates/offline.html +94 -0
  134. decision_graph/webui/native/__init__.py +54 -0
  135. decision_graph/webui/native/app.py +366 -0
  136. decision_graph/webui/native/main.py +53 -0
  137. decision_graph/webui/native/static/d3.v7.min.js +2 -0
  138. decision_graph/webui/native/static/script.js +1026 -0
  139. decision_graph/webui/native/static/style.css +656 -0
  140. decision_graph/webui/native/templates/index.html +93 -0
  141. decision_graph/webui/native/templates/offline.html +94 -0
  142. pydecisiongraph-0.3.2.post1.dist-info/METADATA +136 -0
  143. pydecisiongraph-0.3.2.post1.dist-info/RECORD +148 -0
  144. pydecisiongraph-0.3.2.post1.dist-info/WHEEL +5 -0
  145. pydecisiongraph-0.3.2.post1.dist-info/licenses/LICENSE +373 -0
  146. pydecisiongraph-0.3.2.post1.dist-info/top_level.txt +2 -0
  147. tests/bake_c_evaluator.cp315t-win_amd64.pyd +0 -0
  148. 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)