PyDecisionGraph 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.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.
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.2"
1
+ __version__ = "0.2.0"
2
2
 
3
3
  import logging
4
4
  import sys
@@ -5,40 +5,40 @@ from .. import LOGGER
5
5
  LOGGER = LOGGER.getChild("DecisionTree")
6
6
 
7
7
  __all__ = [
8
- 'LOGGER', 'set_logger', 'activate_expression_model', 'activate_node_model',
8
+ 'LOGGER', 'set_logger',
9
9
  'NodeError', 'TooManyChildren', 'TooFewChildren', 'NodeNotFountError', 'NodeValueError', 'EdgeValueError', 'ResolutionError', 'ExpressFalse', 'ContextsNotFound',
10
- 'LGM', 'LogicGroup', 'SkipContextsBlock', 'LogicExpression', 'ExpressionCollection', 'LogicNode', 'ActionNode', 'ELSE_CONDITION',
10
+ 'LGM', 'LogicGroup', 'SkipContextsBlock', 'LogicExpression', 'LogicNode', 'ActionNode', 'ELSE_CONDITION',
11
11
  'NoAction', 'LongAction', 'ShortAction', 'RootLogicNode', 'ContextLogicExpression', 'AttrExpression', 'MathExpression', 'ComparisonExpression', 'LogicalExpression',
12
12
  'LogicMapping', 'LogicGenerator'
13
13
  ]
14
14
 
15
15
  from .exc import *
16
- from .abc import *
17
- from .node import *
18
- from .collection import *
19
16
 
17
+ _USING_CAPI = False
18
+ try:
19
+ # Attempt to import the C API module
20
+ from . import capi
21
+ from .capi.c_abc import *
22
+ from .capi.c_node import *
23
+ from .capi.c_collection import *
20
24
 
21
- def set_logger(logger: logging.Logger):
22
- global LOGGER
23
- LOGGER = logger
24
-
25
- abc.LOGGER = logger.getChild('abc')
25
+ _USING_CAPI = True
26
+ except Exception:
27
+ # Fallback to the python node model
28
+ from . import native
29
+ from .native.abc import *
30
+ from .native.node import *
31
+ from .native.collection import *
26
32
 
33
+ _USING_CAPI = False
27
34
 
28
- def activate_expression_model():
29
- import importlib
30
- importlib.import_module('decision_graph.decision_tree.expression')
31
- importlib.reload(collection)
32
- collection.LogicMapping.AttrExpression = AttrExpression
33
- collection.LogicGenerator.AttrExpression = AttrExpression
34
- # importlib.reload(logic_group)
35
35
 
36
+ def set_logger(logger: logging.Logger):
37
+ global LOGGER
38
+ LOGGER = logger
36
39
 
37
- def activate_node_model():
38
- import importlib
39
-
40
- importlib.import_module('decision_graph.decision_tree.node')
41
- importlib.reload(collection)
42
- collection.LogicMapping.AttrExpression = AttrExpression
43
- collection.LogicGenerator.AttrExpression = AttrExpression
44
- # importlib.reload(logic_group)
40
+ # ensure abc module (imported above) receives logger
41
+ if _USING_CAPI:
42
+ capi.set_logger(logger.getChild('CAPI'))
43
+ else:
44
+ native.set_logger(logger.getChild('Native'))
@@ -0,0 +1,52 @@
1
+ import logging
2
+
3
+ from .. import LOGGER
4
+
5
+ LOGGER = LOGGER.getChild('CAPI')
6
+
7
+ from ..exc import (
8
+ NodeError, TooManyChildren, TooFewChildren, NodeNotFountError, NodeValueError,
9
+ EdgeValueError, ResolutionError, ExpressFalse, ContextsNotFound
10
+ )
11
+
12
+ # import the module so we can set its LOGGER attribute
13
+ from . import c_abc
14
+
15
+ from .c_abc import (
16
+ Singleton,
17
+ NodeEdgeCondition, ConditionElse, ConditionAny, ConditionAuto, BinaryCondition, ConditionTrue, ConditionFalse,
18
+ NO_CONDITION, ELSE_CONDITION, AUTO_CONDITION, TRUE_CONDITION, FALSE_CONDITION,
19
+ SkipContextsBlock, LogicExpression, LogicNode,
20
+ LogicGroupManager, LGM, LogicGroup,
21
+ ActionNode, BreakpointNode, PlaceholderNode,
22
+ NoAction, LongAction, ShortAction,
23
+ )
24
+
25
+ from .c_node import (
26
+ RootLogicNode, ContextLogicExpression,
27
+ AttrExpression, AttrNestedExpression,
28
+ MathExpressionOperator, MathExpression,
29
+ ComparisonExpressionOperator, ComparisonExpression,
30
+ LogicalExpressionOperator, LogicalExpression,
31
+ )
32
+
33
+ from .c_collection import (
34
+ LogicMapping,
35
+ LogicSequence,
36
+ LogicGenerator,
37
+ )
38
+
39
+ __all__ = [
40
+ 'LOGGER', 'set_logger',
41
+ 'NodeError', 'TooManyChildren', 'TooFewChildren', 'NodeNotFountError', 'NodeValueError', 'EdgeValueError', 'ResolutionError', 'ExpressFalse', 'ContextsNotFound',
42
+ 'LGM', 'LogicGroup', 'SkipContextsBlock', 'LogicExpression', 'LogicNode', 'ActionNode', 'ELSE_CONDITION',
43
+ 'NoAction', 'LongAction', 'ShortAction', 'RootLogicNode', 'ContextLogicExpression', 'AttrExpression', 'MathExpression', 'ComparisonExpression', 'LogicalExpression',
44
+ 'LogicMapping', 'LogicGenerator'
45
+ ]
46
+
47
+
48
+ def set_logger(logger: logging.Logger):
49
+ global LOGGER
50
+ LOGGER = logger
51
+
52
+ c_abc.LOGGER = logger.getChild('abc')
@@ -0,0 +1,537 @@
1
+ import logging
2
+ from collections.abc import Iterable, Callable
3
+ from typing import Any, Never
4
+
5
+ from decision_graph.decision_tree.exc import NO_DEFAULT
6
+
7
+ LOGGER: logging.Logger
8
+
9
+
10
+ class Singleton(object):
11
+ """Lightweight base to mark extension types as singletons.
12
+
13
+ Used internally by Cython classes to ensure only one instance of certain
14
+ helper types is created per process. Users typically won't need this.
15
+ """
16
+
17
+
18
+ # Edge condition types
19
+ class NodeEdgeCondition(Singleton):
20
+ """Represents an edge condition in a decision graph.
21
+
22
+ This is the base type for all concrete condition markers. Most users
23
+ don't create these directly; instead, use the pre-created constants
24
+ like ``TRUE_CONDITION``, ``FALSE_CONDITION``, ``ELSE_CONDITION``, or
25
+ let conditions be inferred automatically when building graphs.
26
+ """
27
+
28
+ @property
29
+ def value(self) -> Any: ...
30
+
31
+
32
+ class ConditionElse(NodeEdgeCondition):
33
+ """Represents an explicit "else" branch in decision trees.
34
+
35
+ It matches when none of the other registered conditions match.
36
+ """
37
+
38
+
39
+ class ConditionAny(NodeEdgeCondition):
40
+ """Represents an unconditioned branch (always eligible as a fallback)."""
41
+
42
+
43
+ class ConditionAuto(NodeEdgeCondition):
44
+ """Marker used internally to request auto-inference of the edge condition."""
45
+
46
+
47
+ class BinaryCondition(NodeEdgeCondition):
48
+ """Base type for binary True/False conditions."""
49
+
50
+
51
+ class ConditionTrue(BinaryCondition):
52
+ """The boolean True branch condition.
53
+
54
+ Notes:
55
+ - Truthy when converted to ``bool``.
56
+ - ``int(ConditionTrue)`` equals ``1``.
57
+ - Unary negation or bitwise invert toggles to ``FALSE_CONDITION``.
58
+ """
59
+
60
+
61
+ class ConditionFalse(BinaryCondition):
62
+ """The boolean False branch condition.
63
+
64
+ Notes:
65
+ - Falsy when converted to ``bool``.
66
+ - ``int(ConditionFalse)`` equals ``0``.
67
+ - Unary negation or bitwise invert toggles to ``TRUE_CONDITION``.
68
+ """
69
+
70
+
71
+ # Pre-created condition singletons exposed by the module
72
+ NO_CONDITION: ConditionAny
73
+ ELSE_CONDITION: ConditionElse
74
+ AUTO_CONDITION: ConditionAuto
75
+ TRUE_CONDITION: ConditionTrue
76
+ FALSE_CONDITION: ConditionFalse
77
+
78
+
79
+ class SkipContextsBlock:
80
+ """Context manager that may skip executing the body of a with-block.
81
+
82
+ - If the entry check passes, ``__enter__`` returns ``self`` and normal
83
+ execution proceeds until ``__exit__`` is called.
84
+ - If the entry check fails, execution of the block is prevented via
85
+ tracing hooks and an internal control-flow exception; ``__exit__`` then
86
+ suppresses that exception so the program continues after the block.
87
+
88
+ Attributes:
89
+ default_entry_check (bool): If True, the block executes by default.
90
+ """
91
+
92
+ default_entry_check: bool
93
+
94
+ def __enter__(self) -> SkipContextsBlock: ...
95
+
96
+ def __exit__(
97
+ self,
98
+ exc_type: type[BaseException] | None,
99
+ exc_value: BaseException | None,
100
+ exc_traceback,
101
+ ) -> bool | None: ...
102
+
103
+
104
+ class LogicExpression(SkipContextsBlock):
105
+ """Represents a logical or mathematical expression with deferred eval.
106
+
107
+ The expression can be a static value, an exception to raise on evaluation,
108
+ or a callable returning a value. An optional ``dtype`` can be provided to
109
+ enforce or check the evaluated type.
110
+
111
+ This class supports boolean logic (``&``, ``|``, comparisons) and basic
112
+ arithmetic operators that produce new ``LogicExpression`` instances.
113
+
114
+ Attributes:
115
+ expression (object): The underlying expression (value, exception, or callable).
116
+ dtype (type | None): Optional type to enforce on evaluation, if requested.
117
+ repr (str): String representation for debugging and logging.
118
+ """
119
+
120
+ expression: object
121
+ dtype: type | None
122
+ repr: str
123
+
124
+ def __init__(
125
+ self,
126
+ *,
127
+ expression: float | int | bool | Exception | Callable[[], Any] = None,
128
+ dtype: type | None = ...,
129
+ repr: str | None = ...,
130
+ **kwargs,
131
+ ) -> None: ...
132
+
133
+ def eval(self, enforce_dtype: bool = ...) -> Any:
134
+ """Evaluate the expression and return the resulting value.
135
+
136
+ If ``enforce_dtype`` is True and a ``dtype`` was provided, the result
137
+ is cast using ``self.dtype(value)``.
138
+ """
139
+
140
+ @classmethod
141
+ def cast(
142
+ cls,
143
+ value: int | float | bool | Exception | LogicExpression | Callable[..., Any],
144
+ dtype: type | None = ...,
145
+ ) -> LogicExpression:
146
+ """
147
+ Cast a value into a LogicExpression.
148
+ If the value is already a LogicExpression, it is returned as-is (same instance).
149
+
150
+ Returns:
151
+ LogicExpression: The resulting LogicExpression instance.
152
+
153
+ Raises:
154
+ TypeError: If the value type is unsupported.
155
+ """
156
+
157
+ def __bool__(self) -> bool:
158
+ """Evaluate the expression and return its boolean value."""
159
+
160
+ def __and__(self, other: LogicExpression | bool) -> LogicExpression:
161
+ """Return a new LogicExpression representing logical AND with ``other``."""
162
+
163
+ def __eq__(self, other: object) -> LogicExpression:
164
+ """Return a new LogicExpression representing equality comparison to ``other``."""
165
+
166
+ def __or__(self, other: LogicExpression | bool) -> LogicExpression:
167
+ """Return a new LogicExpression representing logical OR with ``other``."""
168
+
169
+ def __add__(self, other: object) -> LogicExpression:
170
+ """Return a new LogicExpression representing addition with ``other``."""
171
+
172
+ def __sub__(self, other: object) -> LogicExpression:
173
+ """Return a new LogicExpression representing subtraction with ``other``."""
174
+
175
+ def __mul__(self, other: object) -> LogicExpression:
176
+ """Return a new LogicExpression representing multiplication with ``other``."""
177
+
178
+ def __truediv__(self, other: object) -> LogicExpression:
179
+ """Return a new LogicExpression representing true division with ``other``."""
180
+
181
+ def __floordiv__(self, other: object) -> LogicExpression:
182
+ """Return a new LogicExpression representing floor division with ``other``."""
183
+
184
+ def __pow__(self, other: object) -> LogicExpression:
185
+ """Return a new LogicExpression representing exponentiation with ``other``."""
186
+
187
+ def __lt__(self, other: object) -> LogicExpression:
188
+ """Return a new LogicExpression representing less-than comparison to ``other``."""
189
+
190
+ def __le__(self, other: object) -> LogicExpression:
191
+ """Return a new LogicExpression representing less-than-or-equal comparison to ``other``."""
192
+
193
+ def __gt__(self, other: object) -> LogicExpression:
194
+ """Return a new LogicExpression representing greater-than comparison to ``other``."""
195
+
196
+ def __ge__(self, other: object) -> LogicExpression:
197
+ """Return a new LogicExpression representing greater-than-or-equal comparison to ``other``."""
198
+
199
+ def __repr__(self) -> str:
200
+ """Return the string representation of the LogicExpression."""
201
+
202
+
203
+ class LogicGroupManager(Singleton):
204
+ """Singleton manager for LogicGroup instances and runtime expression context.
205
+
206
+ Handles caching and reuse of ``LogicGroup`` objects and manages runtime
207
+ stacks for active groups and nodes while building or evaluating decision
208
+ graphs.
209
+
210
+ Also supports shelving/unshelving state to create decision sub-graphs
211
+ (for example, across function calls) without interfering with the main
212
+ active state.
213
+
214
+ Attributes:
215
+ inspection_mode (bool): If True, generate layout without executing actions.
216
+ vigilant_mode (bool): If True, perform stricter validation and avoid auto-generated nodes.
217
+ """
218
+
219
+ inspection_mode: bool
220
+ vigilant_mode: bool
221
+
222
+ def __call__(self, name: str, cls: type[LogicGroup], **kwargs) -> LogicGroup:
223
+ """Get or create a cached LogicGroup instance with the given name.
224
+
225
+ Useful for closed-loop operations that need to reuse the same logic group.
226
+
227
+ Args:
228
+ name (str): The name of the logic group.
229
+ cls (type[LogicGroup]): The LogicGroup subclass to instantiate if not cached.
230
+ **kwargs: Additional keyword arguments to pass to the LogicGroup constructor.
231
+
232
+ Returns:
233
+ LogicGroup: The cached or newly created LogicGroup instance.
234
+ """
235
+
236
+ def __contains__(self, instance: LogicGroup) -> bool:
237
+ """Return True if the given LogicGroup instance is cached by this manager."""
238
+
239
+ def clear(self) -> None:
240
+ """Clear all cached LogicGroup instances and reset runtime stacks."""
241
+
242
+ @property
243
+ def active_group(self) -> LogicGroup | None:
244
+ """The currently active LogicGroup, or None if no group context is entered."""
245
+
246
+ @property
247
+ def active_node(self) -> LogicNode | None:
248
+ """The currently active LogicNode expression, or None if no expression context is entered."""
249
+
250
+
251
+ # Global instance of the manager
252
+ LGM: LogicGroupManager
253
+
254
+
255
+ class LogicGroup:
256
+ """A minimal context manager to scope logic groups and break operations.
257
+
258
+ A logic group is a lightweight context that records its name and an
259
+ optional parent, and it provides a ``Break`` exception type for orderly
260
+ early exit via ``LogicGroup.break_``.
261
+
262
+ In runtime mode, breaking from a logic group propagates through nested
263
+ groups and moves the execution cursor to the first line after the block;
264
+ during this process, on-exit hooks of nested groups and nodes run.
265
+
266
+ In inspection mode, breaking does not raise immediately. Instead, missing
267
+ branches are auto-filled with ``NoAction`` to allow layout evaluation to
268
+ continue, especially when a break occurs before the other branch is built.
269
+
270
+ Attributes:
271
+ name (str): The name of the logic group.
272
+ parent (LogicGroup | None): The parent logic group, if any.
273
+ Break (type[BaseException]): The exception type used for breaks.
274
+ contexts (dict[str, Any]): Context-specific storage for the group.
275
+ """
276
+
277
+ name: str
278
+ parent: LogicGroup | None
279
+ Break: type[BaseException]
280
+ contexts: dict[str, Any]
281
+
282
+ def __init__(self, *, name: str, parent: LogicGroup = None, contexts: dict = None, **kwargs):
283
+ """Initialize a LogicGroup with the given name, parent, and contexts.
284
+
285
+ Args:
286
+ name (str): The name of the logic group.
287
+ parent (LogicGroup | None): The parent logic group, if any.
288
+ contexts (dict[str, Any] | None): Optional context-specific storage.
289
+ kwargs: __cinit__ extra kwargs guardian of for subclassing support, not used is this base class.
290
+ """
291
+
292
+ def __repr__(self) -> str: ...
293
+
294
+ def __enter__(self) -> LogicGroup:
295
+ """Enter the logic group context and mark it as active."""
296
+ ...
297
+
298
+ def __exit__(
299
+ self,
300
+ exc_type: type[BaseException] | None,
301
+ exc_value: BaseException | None,
302
+ exc_traceback,
303
+ ) -> bool | None:
304
+ """Exit the logic group context, handling Break exceptions gracefully."""
305
+ ...
306
+
307
+ @classmethod
308
+ def break_(cls, scope: LogicGroup | None = ...) -> None:
309
+ """Break out from the given ``scope`` (or the active scope if None).
310
+
311
+ In inspection mode, the break is recorded to be connected to the next
312
+ entered node. In runtime mode, this propagates break through nested
313
+ groups until the target scope is exited.
314
+ """
315
+ ...
316
+
317
+ def break_active(self) -> None:
318
+ """Break out from the currently active logic group only (top of stack)."""
319
+
320
+ def break_inspection(self) -> None:
321
+ """Record a break while in inspection mode without raising immediately."""
322
+
323
+ def break_runtime(self) -> None:
324
+ """Propagate a break through nested groups until the target scope is exited."""
325
+
326
+
327
+ class LogicNode(LogicExpression):
328
+ """A decision node that branches to children based on an evaluated value.
329
+
330
+ Each child is registered against an edge condition, and the node supports
331
+ auto-inference of binary conditions for succinct graph construction.
332
+
333
+ Attributes:
334
+ parent (LogicNode | None): The parent node, if any.
335
+ condition_to_parent (NodeEdgeCondition): The edge condition leading to this node from its parent.
336
+ children (dict[NodeEdgeCondition, LogicNode]): Mapping of edge conditions to child nodes.
337
+ labels (list[str]): LogicGroup names this node belongs to.
338
+ autogen (bool): Whether this node was auto-generated to fill a missing branch.
339
+ """
340
+
341
+ parent: LogicNode | None
342
+ condition_to_parent: NodeEdgeCondition
343
+ children: dict[NodeEdgeCondition, LogicNode]
344
+ labels: list[str]
345
+ autogen: bool
346
+
347
+ def __init__(self, *, expression: object = None, dtype: type = None, repr: str = None, **kwargs):
348
+ """
349
+ Initialize the LogicExpression.
350
+
351
+ Args:
352
+ expression (Union[Any, Callable[[], Any]]): A callable or static value.
353
+ dtype (type, optional): The expected type of the evaluated value (float, int, or bool).
354
+ repr (str, optional): A string representation of the expression.
355
+ kwargs: __cinit__ extra kwargs guardian of for subclassing support, not used is this base class.
356
+ """
357
+
358
+ def __rshift__(self, other: LogicNode) -> LogicNode:
359
+ """
360
+ Convenience for ``append`` to support chaining, e.g.::
361
+
362
+ >>> node1 = LogicNode(expression=...)
363
+ >>> node2 = LogicNode(expression=...)
364
+ >>> node1 >> node2
365
+
366
+ Returns the ``other`` node so calls can be chained.
367
+ """
368
+
369
+ def __call__(self, default: Any | None = ...) -> Any:
370
+ """Evaluate the tree from this node and return the final action/value.
371
+
372
+ If ``default`` is not provided, a ``NoAction`` node will be used as the
373
+ fallback terminal.
374
+
375
+ You can pass ``NO_DEFAULT`` to explicitly require a matching branch;
376
+ if no edge matches, a ``ValueError`` will be raised.
377
+ See ``eval_recursively`` for details.
378
+ """
379
+ ...
380
+
381
+ def append(self, child: LogicNode, condition: NodeEdgeCondition = ...) -> None:
382
+ """Append a child node with the given edge condition.
383
+
384
+ If ``condition`` is ``AUTO_CONDITION``, the condition is inferred
385
+ automatically based on existing children (for binary branching).
386
+
387
+ Raises:
388
+ ValueError: If an invalid condition is provided (e.g., ``None``).
389
+ KeyError: If the condition is already used by another child.
390
+ EdgeValueError: If condition inference fails due to incompatible existing children.
391
+ TooManyChildren: If inference fails due to too many existing children.
392
+ """
393
+
394
+ def overwrite(self, new_node: LogicNode, condition: NodeEdgeCondition) -> None:
395
+ """Overwrite the child node for the given edge condition.
396
+
397
+ If ``condition`` is ``AUTO_CONDITION``, the condition is inferred
398
+ automatically based on existing children (for binary branching).
399
+
400
+ Raises:
401
+ ValueError: If an invalid condition is provided (e.g., ``None``).
402
+ KeyError: If there is no existing child for the given condition.
403
+ EdgeValueError: If condition inference fails due to incompatible existing children.
404
+ TooManyChildren: If inference fails due to too many existing children.
405
+ """
406
+
407
+ def replace(self, original_node: LogicNode, new_node: LogicNode) -> None:
408
+ """Replace an existing child node with a new node.
409
+
410
+ Raises:
411
+ RuntimeError: If the original node is currently active.
412
+ LookupError: If the stack is out of sync with the node's children.
413
+ NodeNotFountError: If the original node is not a child of this node.
414
+ """
415
+
416
+ def eval_recursively(
417
+ self,
418
+ path: list[LogicNode] | None = ...,
419
+ default: Any = NO_DEFAULT,
420
+ ) -> tuple[Any, list[LogicNode]]:
421
+ """Evaluate the decision tree recursively from this node.
422
+
423
+ Args:
424
+ path (list[LogicNode] | None): If provided, a list to record the
425
+ sequence of nodes traversed during evaluation.
426
+ default (Any): The default value or action to use if no matching
427
+ child is found. Use ``NO_DEFAULT`` to request an error when no
428
+ branch matches.
429
+ Returns:
430
+ tuple[Any, list[LogicNode]]: The resulting value/action and the
431
+ path list of nodes traversed during evaluation.
432
+ """
433
+
434
+ def list_labels(self) -> dict[str, list[LogicNode]]:
435
+ """List all LogicGroup names in the subtree rooted at this node.
436
+
437
+ Returns:
438
+ dict[str, list[LogicNode]]: A mapping from label strings (logic group names)
439
+ to lists of nodes that have that label.
440
+ """
441
+
442
+ @property
443
+ def leaves(self) -> Iterable[LogicNode]:
444
+ """An iterable of all leaf nodes in the subtree rooted at this node."""
445
+
446
+ @property
447
+ def is_leaf(self) -> bool:
448
+ """True if this node has no children; otherwise False."""
449
+
450
+ @property
451
+ def child_stack(self) -> Iterable[LogicNode]:
452
+ """An iterable of all child nodes in the subtree rooted at this node."""
453
+
454
+
455
+ class BreakpointNode(LogicNode):
456
+ """A logic node that represents a breakpoint in the decision tree, used for breaking out of logic groups.
457
+
458
+ This node is auto-generated and can connect to at most one child node.
459
+
460
+ During evaluation, if connected, it delegates to the child's evaluation; otherwise, it returns its default expression (NoAction) in vigilant mode.
461
+
462
+ Attributes:
463
+ break_from (LogicGroup): The logic group from which this breakpoint breaks.
464
+ await_connection (bool): Whether to wait for a connection to a child node during inspection.
465
+ """
466
+
467
+ break_from: LogicGroup
468
+ await_connection: bool
469
+
470
+
471
+ class PlaceholderNode(ActionNode):
472
+ """An action node that serves as a placeholder in the decision tree.
473
+
474
+ This node is auto-generated and during evaluation returns itself in vigilant mode, otherwise returns a NoAction instance.
475
+ """
476
+
477
+
478
+ class ActionNode(LogicNode):
479
+ """A terminal node that can execute an optional ``action`` upon selection."""
480
+
481
+ action: Callable[[], Any] | None
482
+
483
+ def __init__(
484
+ self,
485
+ *,
486
+ action: Callable[[], Any] | None = ...,
487
+ expression: object | None = ...,
488
+ dtype: type | None = ...,
489
+ repr: str | None = ...,
490
+ auto_connect: bool = True,
491
+ **kwargs,
492
+ ) -> None: ...
493
+
494
+ def __enter__(self) -> Never:
495
+ """
496
+ ActionNode does not support the context manager protocol.
497
+
498
+ Raises:
499
+ NodeContextError: Using ``with ActionNode()`` is invalid.
500
+ """
501
+
502
+ def append(self, child: LogicNode, condition: NodeEdgeCondition = ...) -> Never:
503
+ """
504
+ Appending children to an ActionNode is not supported.
505
+
506
+ Since ActionNodes are terminal, ``replace`` and ``overwrite`` are also
507
+ not applicable and will fail naturally if attempted.
508
+
509
+ Raises:
510
+ TooManyChildren: Always raised to signal invalid operation.
511
+ """
512
+
513
+
514
+ class NoAction(ActionNode):
515
+ """An action node whose evaluation returns itself and performs no action."""
516
+
517
+ sig: int = 0
518
+
519
+
520
+ class LongAction(ActionNode):
521
+ """An action node variant carrying a positive ``sig`` marker.
522
+
523
+ Attributes:
524
+ sig (int): The signature marker for the long action. Defaults to ``1``.
525
+ """
526
+
527
+ sig: int
528
+
529
+
530
+ class ShortAction(ActionNode):
531
+ """An action node variant carrying a negative ``sig`` marker.
532
+
533
+ Attributes:
534
+ sig (int): The signature marker for the long action. Defaults to ``-1``.
535
+ """
536
+
537
+ sig: int