simplibs-actions 0.1.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.
Files changed (92) hide show
  1. simplibs/actions/__init__.py +103 -0
  2. simplibs/actions/base_class/Action.py +188 -0
  3. simplibs/actions/base_class/__init__.py +17 -0
  4. simplibs/actions/base_class/_validations/__init__.py +16 -0
  5. simplibs/actions/base_class/_validations/raise_invalid_action_target.py +54 -0
  6. simplibs/actions/containers/__init__.py +88 -0
  7. simplibs/actions/containers/_helpers/__init__.py +18 -0
  8. simplibs/actions/containers/_helpers/as_action.py +31 -0
  9. simplibs/actions/containers/_helpers/as_predicate.py +58 -0
  10. simplibs/actions/containers/_helpers/validations/__init__.py +16 -0
  11. simplibs/actions/containers/_helpers/validations/raise_invalid_predicate.py +42 -0
  12. simplibs/actions/containers/flow_control/__init__.py +21 -0
  13. simplibs/actions/containers/flow_control/_validations/__init__.py +18 -0
  14. simplibs/actions/containers/flow_control/_validations/raise_guard_failed.py +42 -0
  15. simplibs/actions/containers/flow_control/_validations/raise_not_iterable.py +39 -0
  16. simplibs/actions/containers/flow_control/aliases/__init__.py +24 -0
  17. simplibs/actions/containers/flow_control/aliases/apply_to_each.py +19 -0
  18. simplibs/actions/containers/flow_control/aliases/conditional_action.py +24 -0
  19. simplibs/actions/containers/flow_control/aliases/guarded_action.py +20 -0
  20. simplibs/actions/containers/flow_control/branch.py +77 -0
  21. simplibs/actions/containers/flow_control/for_each.py +58 -0
  22. simplibs/actions/containers/flow_control/guard.py +96 -0
  23. simplibs/actions/containers/operators/__init__.py +22 -0
  24. simplibs/actions/containers/operators/aliases/__init__.py +24 -0
  25. simplibs/actions/containers/operators/aliases/run_in_parallel.py +20 -0
  26. simplibs/actions/containers/operators/aliases/run_in_sequence.py +20 -0
  27. simplibs/actions/containers/operators/aliases/try_or_fallback.py +27 -0
  28. simplibs/actions/containers/operators/compose_class/ParallelCompose.py +53 -0
  29. simplibs/actions/containers/operators/compose_class/SequenceCompose.py +59 -0
  30. simplibs/actions/containers/operators/compose_class/__init__.py +23 -0
  31. simplibs/actions/containers/operators/fallback.py +88 -0
  32. simplibs/actions/containers/operators/parallel.py +66 -0
  33. simplibs/actions/containers/operators/sequence.py +74 -0
  34. simplibs/actions/containers/primitives/__init__.py +19 -0
  35. simplibs/actions/containers/primitives/aliases/__init__.py +22 -0
  36. simplibs/actions/containers/primitives/aliases/pass_through.py +19 -0
  37. simplibs/actions/containers/primitives/aliases/replace_with.py +19 -0
  38. simplibs/actions/containers/primitives/constant.py +46 -0
  39. simplibs/actions/containers/primitives/identity.py +53 -0
  40. simplibs/actions/containers/wrappers/__init__.py +23 -0
  41. simplibs/actions/containers/wrappers/_validations/__init__.py +16 -0
  42. simplibs/actions/containers/wrappers/_validations/raise_invalid_attempts.py +40 -0
  43. simplibs/actions/containers/wrappers/aliases/__init__.py +26 -0
  44. simplibs/actions/containers/wrappers/aliases/callable_action.py +21 -0
  45. simplibs/actions/containers/wrappers/aliases/log_step.py +21 -0
  46. simplibs/actions/containers/wrappers/aliases/retry_on_failure.py +23 -0
  47. simplibs/actions/containers/wrappers/aliases/run_side_effect.py +21 -0
  48. simplibs/actions/containers/wrappers/lambda_action.py +55 -0
  49. simplibs/actions/containers/wrappers/log_action.py +58 -0
  50. simplibs/actions/containers/wrappers/retry.py +82 -0
  51. simplibs/actions/containers/wrappers/tap.py +55 -0
  52. simplibs/actions/creator/__init__.py +26 -0
  53. simplibs/actions/creator/_helpers/__init__.py +16 -0
  54. simplibs/actions/creator/_helpers/constants/EMPTY.py +42 -0
  55. simplibs/actions/creator/_helpers/constants/__init__.py +16 -0
  56. simplibs/actions/creator/_helpers/creators/__init__.py +20 -0
  57. simplibs/actions/creator/_helpers/creators/_helpers/__init__.py +24 -0
  58. simplibs/actions/creator/_helpers/creators/_helpers/_describe_annotation.py +53 -0
  59. simplibs/actions/creator/_helpers/creators/_helpers/build_call_docstring.py +68 -0
  60. simplibs/actions/creator/_helpers/creators/_helpers/build_init_docstring.py +56 -0
  61. simplibs/actions/creator/_helpers/creators/create_act.py +145 -0
  62. simplibs/actions/creator/_helpers/creators/create_call.py +102 -0
  63. simplibs/actions/creator/_helpers/creators/create_init_and_slots.py +103 -0
  64. simplibs/actions/creator/_helpers/resolvers/__init__.py +23 -0
  65. simplibs/actions/creator/_helpers/resolvers/resolve_class_name.py +54 -0
  66. simplibs/actions/creator/_helpers/resolvers/resolve_param.py +77 -0
  67. simplibs/actions/creator/_helpers/resolvers/unwrap_log_this_and_validate_call.py +72 -0
  68. simplibs/actions/creator/_helpers/resolvers/validations/__init__.py +18 -0
  69. simplibs/actions/creator/_helpers/resolvers/validations/raise_main_param_not_found.py +39 -0
  70. simplibs/actions/creator/_helpers/resolvers/validations/raise_no_parameters.py +37 -0
  71. simplibs/actions/creator/_helpers/validations/__init__.py +23 -0
  72. simplibs/actions/creator/_helpers/validations/_verify_type_is_validatable.py +48 -0
  73. simplibs/actions/creator/_helpers/validations/raise_action_instance_error.py +32 -0
  74. simplibs/actions/creator/_helpers/validations/validate_param_kinds.py +46 -0
  75. simplibs/actions/creator/_helpers/validations/validate_params_annotations.py +45 -0
  76. simplibs/actions/creator/_helpers/validations/validate_return_annotations.py +42 -0
  77. simplibs/actions/creator/create_action.py +229 -0
  78. simplibs/actions/decorator/__init__.py +26 -0
  79. simplibs/actions/decorator/to_action.py +109 -0
  80. simplibs/actions/testing/__init__.py +50 -0
  81. simplibs/actions/testing/assert_action.py +195 -0
  82. simplibs/actions/testing/assert_action_alias.py +102 -0
  83. simplibs/actions/testing/asserts/__init__.py +31 -0
  84. simplibs/actions/testing/asserts/assert_action_construction.py +105 -0
  85. simplibs/actions/testing/asserts/assert_action_io_types.py +80 -0
  86. simplibs/actions/testing/asserts/assert_action_output.py +74 -0
  87. simplibs/actions/testing/asserts/assert_action_raises.py +80 -0
  88. simplibs_actions-0.1.0.dist-info/METADATA +374 -0
  89. simplibs_actions-0.1.0.dist-info/RECORD +92 -0
  90. simplibs_actions-0.1.0.dist-info/WHEEL +5 -0
  91. simplibs_actions-0.1.0.dist-info/licenses/LICENSE +21 -0
  92. simplibs_actions-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,103 @@
1
+ # 1. Base Class
2
+ from .base_class import Action
3
+
4
+ # 2. Composition Classes
5
+ from .containers.operators.compose_class import ParallelCompose, SequenceCompose
6
+
7
+ # 3. Primary Containers & Aggregated Aliases Namespace
8
+ from .containers import (
9
+ aliases,
10
+ branch,
11
+ constant,
12
+ fallback,
13
+ for_each,
14
+ guard,
15
+ identity,
16
+ lambda_action,
17
+ log_action,
18
+ parallel,
19
+ retry,
20
+ sequence,
21
+ tap,
22
+ )
23
+
24
+ # 4. Creators & Decorators
25
+ from .creator import create_action
26
+ from .decorator import to_action
27
+
28
+
29
+ __all__ = [
30
+ # Base
31
+ "Action",
32
+ # Composition Classes
33
+ "ParallelCompose",
34
+ "SequenceCompose",
35
+ # Flow Control Containers
36
+ "branch",
37
+ "for_each",
38
+ "guard",
39
+ # Operator Containers
40
+ "fallback",
41
+ "parallel",
42
+ "sequence",
43
+ # Primitive Containers
44
+ "constant",
45
+ "identity",
46
+ # Wrapper Containers
47
+ "lambda_action",
48
+ "log_action",
49
+ "retry",
50
+ "tap",
51
+ # Factory & Decorator
52
+ "create_action",
53
+ "to_action",
54
+ # Aggregated Aliases Namespace
55
+ "aliases",
56
+ ]
57
+
58
+
59
+ _DESIGN_NOTES = """
60
+ # Main Simplibs Actions Package
61
+
62
+ ## Purpose
63
+ Root public entry point for `simplibs-actions`. Exposes the complete high-level API
64
+ surface for constructing, composing, decorating, and executing data transformation pipelines.
65
+
66
+ ## Public Components Registry
67
+
68
+ | Component | Type | Origin Sub-Package | Description |
69
+ | :---------------- | :------- | :----------------- | :------------------------------------------------------------------------ |
70
+ | `Action` | Class | `base_class` | Core abstract base class defining operator composition and execution. |
71
+ | `SequenceCompose` | Class | `containers` | Intermediate lazy composition wrapper created via `>>` / `+` operators. |
72
+ | `ParallelCompose` | Class | `containers` | Intermediate lazy composition wrapper created via `&` operator. |
73
+ | `branch` | Function | `containers` | Soft conditional branching container (`if/then/else`). |
74
+ | `for_each` | Function | `containers` | Collection-processing container over input iterables. |
75
+ | `guard` | Function | `containers` | Hard gate container validating data against a rule before proceeding. |
76
+ | `fallback` | Function | `containers` | Exception mitigation container providing alternative branch execution. |
77
+ | `parallel` | Function | `containers` | Parallel branch execution container returning tuple of outputs. |
78
+ | `sequence` | Function | `containers` | Sequential step execution container feeding results left-to-right. |
79
+ | `constant` | Function | `containers` | Primitive container ignoring input and returning a fixed value. |
80
+ | `identity` | Function | `containers` | Primitive container returning input data unchanged. |
81
+ | `lambda_action` | Function | `containers` | Wrapper transforming raw single-argument callables into Actions. |
82
+ | `log_action` | Function | `containers` | Wrapper logging pipeline data without modifying the payload. |
83
+ | `retry` | Function | `containers` | Wrapper executing action multiple times on specified exception catches. |
84
+ | `tap` | Function | `containers` | Wrapper executing side-effect functions while passing input through. |
85
+ | `create_action` | Function | `creator` | Low-level factory function synthesizing typed `Action` classes/instances. |
86
+ | `to_action` | Function | `decorator` | High-level decorator turning standard callables into two-phase Actions. |
87
+ | `aliases` | Instance | `containers` | Aggregated namespace offering descriptive naming alternatives for containers|
88
+
89
+
90
+ ## Architectural Design Decisions
91
+
92
+ 1. **Single Source of Truth for Aliases**: The `aliases` namespace instance is instantiated strictly
93
+ once within `containers/__init__.py` and simply re-exported here. This guarantees object identity
94
+ (`actions.aliases is actions.containers.aliases`) and eliminates duplication.
95
+ 2. **Flattened Public API**: All primary building blocks are re-exported at the package root
96
+ so downstream code can import directly from `simplibs.actions`.
97
+ 3. **Explicit Alias Namespacing**: Aliases are intentionally contained inside the `aliases`
98
+ object namespace to prevent polluting the top-level namespace while providing auto-complete
99
+ and clear intent (`actions.aliases.run_in_sequence`).
100
+ 4. **Isolation of Testing Tools**: Testing utilities (`assert_action`, `assert_action_alias`)
101
+ are strictly excluded from this root package to keep production imports light and avoid
102
+ unnecessary test-framework dependencies in non-testing environments.
103
+ """
@@ -0,0 +1,188 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+ from simplibs.rules import Rule
4
+ # Inners
5
+ from ._validations import raise_invalid_action_target
6
+
7
+
8
+ class Action(ABC):
9
+ """Abstract base class for all operative actions and transformations.
10
+
11
+ `__call__` is the sole abstract method — unlike `Rule` (where
12
+ `is_valid` is the answer to a question and `__call__` is just
13
+ convenient sugar over it), for an `Action` being callable IS the
14
+ whole point, so no second method (`execute`/`act`) is needed at the
15
+ base level. Concrete subclasses arise in two ways: hand-written
16
+ containers implementing `__call__` directly (e.g. `SequenceCompose`/
17
+ `ParallelCompose`, which need bespoke flattening logic no generic
18
+ factory can derive on its own), or generated from an ordinary
19
+ function via `create_action`/`to_action` (e.g. `guard`, `branch`,
20
+ `sequence`, `fallback` — nearly everything else).
21
+ """
22
+
23
+ # Hook signaling to Rule that this object is not meant to be silently
24
+ # absorbed by `Rule.__and__`/`__or__` as a plain predicate — see
25
+ # Rule.py's own design notes for the full rationale.
26
+ __not_rule__ = True
27
+
28
+ # ----------------------------------------------------------------------
29
+ # 1) Executable Interface
30
+ # ----------------------------------------------------------------------
31
+
32
+ @abstractmethod
33
+ def __call__(self, data: Any, /) -> Any:
34
+ """Run this action over `data` and return the result."""
35
+ raise NotImplementedError
36
+
37
+ # ----------------------------------------------------------------------
38
+ # 2) Operator-Based Composition — Forward Direction (self on the left)
39
+ # ----------------------------------------------------------------------
40
+
41
+ def then(self, next_step: Any) -> "Action":
42
+ """Chains this action with a subsequent action, rule, or callable."""
43
+ from ..containers.operators.compose_class import SequenceCompose
44
+ return SequenceCompose.compose(first=self, second=self.resolve_action(next_step))
45
+
46
+ def __rshift__(self, next_step: Any) -> "Action":
47
+ """Chains this action with a subsequent step via the `>>` operator."""
48
+ return self.then(next_step)
49
+
50
+ def __and__(self, other: Any) -> "Action":
51
+ """Executes actions in parallel over identical input data: `actionA & actionB`."""
52
+ from ..containers.operators.compose_class import ParallelCompose
53
+ return ParallelCompose.compose(first=self, second=self.resolve_action(other))
54
+
55
+ def __or__(self, other: Any) -> "Action":
56
+ """Fallback handling: executes `other` if this action fails: `actionA | actionB`."""
57
+ from ..containers.operators import fallback
58
+ return fallback(action=self, on_error=self.resolve_action(other))
59
+
60
+ # ----------------------------------------------------------------------
61
+ # 3) Operator-Based Composition — Reflected Direction (self on the right)
62
+ # ----------------------------------------------------------------------
63
+ #
64
+ # Triggered when `Rule.__and__`/`__or__` declines the combination
65
+ # because of `__not_rule__` (see Rule.py's own design notes) and
66
+ # Python falls back to `other.__rand__`/`__ror__`/`__rrshift__` — or
67
+ # when `other` is a plain callable/Rule with no operator of its own
68
+ # at all. Each method simply wraps `other` via `resolve_action` and
69
+ # hands off to the forward operator — one place of truth, no
70
+ # duplicated container-construction logic.
71
+
72
+ def __rand__(self, other: Any) -> "Action":
73
+ """Supports `rule & action` — verify AND execute in parallel."""
74
+ return self.resolve_action(other) & self
75
+
76
+ def __ror__(self, other: Any) -> "Action":
77
+ """Supports `rule | action` — verify, or run a fallback action on failure."""
78
+ return self.resolve_action(other) | self
79
+
80
+ def __rrshift__(self, other: Any) -> "Action":
81
+ """Supports `rule >> action` — verify the rule, then continue with the action."""
82
+ return self.resolve_action(other) >> self
83
+
84
+ # ----------------------------------------------------------------------
85
+ # 4) Resolution Helper
86
+ # ----------------------------------------------------------------------
87
+
88
+ @staticmethod
89
+ def resolve_action(obj: Any) -> "Action":
90
+ """Convert an Action, a Rule, or a plain callable into an Action instance.
91
+
92
+ Args:
93
+ obj: An `Action` instance (returned unchanged), a `Rule`
94
+ instance (wrapped into `guard`), or any other callable
95
+ (wrapped into `lambda_action`).
96
+
97
+ Returns:
98
+ An `Action` instance ready to be composed.
99
+
100
+ Raises:
101
+ TypeError: If `obj` is neither an `Action`, a `Rule`, nor callable.
102
+ """
103
+
104
+ # 1. Already an Action instance — return it unchanged
105
+ if isinstance(obj, Action):
106
+ return obj
107
+
108
+ # 2. A Rule instance — wrap it into the hard-gate container
109
+ if isinstance(obj, Rule):
110
+ from ..containers.flow_control import guard
111
+ return guard(rule=obj)
112
+
113
+ # 3. Any other callable — wrap it into the plain-transformer adapter
114
+ if callable(obj):
115
+ from ..containers.wrappers import lambda_action
116
+ return lambda_action(func=obj)
117
+
118
+ # 4. Anything else is not a valid Action target
119
+ return raise_invalid_action_target(obj)
120
+
121
+
122
+ _DESIGN_NOTES = """
123
+ # Action — base abstract class (redesigned)
124
+
125
+ ## Why only `__call__`, no `execute`/`act` at the base level
126
+
127
+ An earlier version had `execute` as the abstract method and `__call__` as
128
+ a thin wrapper over it — mirroring `Rule.is_valid`/`__call__`. That
129
+ mirroring turned out to be a mistake: for `Rule`, `is_valid` is the
130
+ *answer to a question* and callability is a convenience on top; for
131
+ `Action`, callability itself IS the substance — an action *is* a
132
+ function with one main input. The second method was never actually
133
+ needed, and once caused a real bug (a class generated by `create_action`
134
+ had `__call__`, but the base `Action` required `execute` — the class
135
+ could not be instantiated at all). Collapsing to a single abstract
136
+ `__call__` removes that mismatch structurally, not with a patch.
137
+
138
+ Subclasses arise in two ways:
139
+ * **hand-written** — `SequenceCompose`/`ParallelCompose` implement
140
+ `__call__` directly (inherited from their `create_action`-generated
141
+ base, `sequence`/`parallel`) because they need bespoke flattening
142
+ logic (`compose`) that no generic factory can derive on its own;
143
+ * **via `create_action`/`to_action`** — nearly everything else (`guard`,
144
+ `branch`, `for_each`, `sequence`, `parallel`, `fallback`, `identity`,
145
+ `constant`, `lambda_action`, `log_step`, `retry`, `tap`). There,
146
+ `act`/`__init__`/`__call__` are implementation details of the
147
+ *generator* (needed for the separate `validate_call`/`log_this`
148
+ wrapping of each), not a requirement of the base class itself.
149
+
150
+ ## Reflected operators — `__rand__`/`__ror__`/`__rrshift__`
151
+
152
+ Triggered exclusively by the `__not_rule__` hook on the `Rule` side (see
153
+ `Rule.py`'s own design notes) — `rule & action` first hits
154
+ `Rule.__and__`, which returns `NotImplemented`, and only then does Python
155
+ try `action.__rand__(rule)`.
156
+
157
+ The semantics of all three are derived from what the `guard` container
158
+ does (pass through unchanged if the `Rule` holds, otherwise raise):
159
+ * `rule >> action` — check, then continue (sequence).
160
+ * `rule & action` — check AND run the action (in parallel; both must
161
+ succeed — the spirit of `AllOf`, shifted from "predicate AND
162
+ predicate" to "gate AND action").
163
+ * `rule | action` — check, and on failure run the action instead (the
164
+ spirit of `AnyOf` — at least one path must succeed, only the first
165
+ path is a hard gate).
166
+
167
+ All three are implemented as the same one-line pattern —
168
+ `self.resolve_action(other) <op> self` — wrap the left-hand operand,
169
+ hand off to the forward operator. No second copy of any container's
170
+ construction logic; one place of truth covers both `rule & action` and
171
+ `action & action`.
172
+
173
+ **A typing cost, not a behavioral one:** `Rule.__and__`/`__or__` can now
174
+ return either `Rule` or `Action`, depending on the right-hand operand's
175
+ type. This is already handled on the `Rule` side via a structural
176
+ `_NotARule` `Protocol` plus `@overload` pairs on every binary operator
177
+ (`__and__`/`__or__`/`__rand__`/`__ror__`) — see `Rule.py`'s own design
178
+ notes, section 6, for why a `Protocol` is used instead of importing
179
+ `Action` directly (keeping `simplibs-validate` independent of
180
+ `simplibs-actions`, even for typing purposes).
181
+
182
+ ## `__not_rule__` remains unchanged
183
+
184
+ Still the single hook driving this entire asymmetry — `Rule` declines to
185
+ treat `Action` as a predicate, `Action` supplies its own, meaningful
186
+ interpretation instead. See `Rule.py`'s design notes for the complete
187
+ rationale.
188
+ """
@@ -0,0 +1,17 @@
1
+ from .Action import Action
2
+
3
+
4
+ _DESIGN_NOTES = """
5
+ # Action Base Sub-Package
6
+
7
+ ## Purpose
8
+ Provides the abstract base class `Action` that defines core pipeline execution,
9
+ dunder-method composition, representation, and polymorphic resolution for all
10
+ action types in the package.
11
+
12
+ ## Components Registry
13
+
14
+ | Component | Type | Description |
15
+ | :-------- | :---- | :------------------------------------------------------------------------------ |
16
+ | `Action` | Class | Base class for callable data transformation units with operator composition. |
17
+ """
@@ -0,0 +1,16 @@
1
+ from .raise_invalid_action_target import raise_invalid_action_target
2
+
3
+
4
+ _DESIGN_NOTES = """
5
+ # Action Base Validations Sub-Package
6
+
7
+ ## Purpose
8
+ Internal diagnostic and validation helpers used by the `Action` base class to enforce
9
+ type safety and provide formatted diagnostics during action resolution and composition.
10
+
11
+ ## Internal Components Registry
12
+
13
+ | Component | Type | Description |
14
+ | :----------------------------- | :------- | :-------------------------------------------------------------------------- |
15
+ | `raise_invalid_action_target` | Function | Raises a formatted `ParamError` when an object cannot be converted to Action.|
16
+ """
@@ -0,0 +1,54 @@
1
+ from typing import Any, NoReturn
2
+ from simplibs.exception import ParamError
3
+
4
+
5
+ def raise_invalid_action_target(obj: Any) -> NoReturn:
6
+ """Raises a ParamError when an object cannot be resolved into an Action.
7
+
8
+ Args:
9
+ obj: The candidate target object that failed resolution checks.
10
+
11
+ Raises:
12
+ ParamError: Unconditionally raised with diagnostic context.
13
+ """
14
+ raise ParamError(
15
+ error_name="INVALID_ACTION_TARGET",
16
+ label="action target candidate",
17
+ value=type(obj).__name__,
18
+ problem=(
19
+ f"Object of type '{type(obj).__name__}' cannot be converted into an Action.",
20
+ "Action resolution expects an Action instance, a Rule, or any Callable.",
21
+ ),
22
+ expected="An Action instance, a Rule, or a callable function/object.",
23
+ how_to_fix=(
24
+ "Ensure you pass a valid callable entity to the operator or resolution helper.",
25
+ "If passing a custom class or object, ensure it is callable (implements __call__) "
26
+ "or inherits from Action / Rule.",
27
+ ),
28
+ exception=TypeError,
29
+ )
30
+
31
+
32
+ _DESIGN_NOTES = """
33
+ # raise_invalid_action_target — Action Resolution Failure Diagnostic
34
+
35
+ ## Purpose
36
+ Provides a standard, highly diagnostic `ParamError` exception when an object
37
+ passed to `Action.resolve_action` or compositional operators (`|`, `>>`) cannot
38
+ be converted into a valid `Action` instance.
39
+
40
+ ---
41
+
42
+ ## 1. Unified Exception Interface
43
+
44
+ By delegating invalid resolution reporting to a dedicated function, all
45
+ composition and resolution entry points share identical error diagnostics,
46
+ labels, and remediation instructions.
47
+
48
+ ---
49
+
50
+ ## 2. Dynamic Type Inspection
51
+
52
+ Reflects the actual type of the received `obj` in the diagnostic message to
53
+ give clear feedback to the developer about what unsupported type was provided.
54
+ """
@@ -0,0 +1,88 @@
1
+ # Composition Classes
2
+ from .operators.compose_class import ParallelCompose, SequenceCompose
3
+
4
+ # Flow Control Containers
5
+ from .flow_control import branch, for_each, guard
6
+
7
+ # Operator Containers
8
+ from .operators import fallback, parallel, sequence
9
+
10
+ # Primitive Containers
11
+ from .primitives import constant, identity
12
+
13
+ # Wrapper Containers
14
+ from .wrappers import lambda_action, log_action, retry, tap
15
+
16
+ # Aliases Sub-Package / Namespace Import
17
+ from . import flow_control, operators, primitives, wrappers
18
+
19
+ # Aggregated Aliases Namespace
20
+ class _Aliases:
21
+ """Namespace grouping all alias functions across container sub-packages."""
22
+ # Flow Control Aliases
23
+ apply_to_each = flow_control.aliases.apply_to_each
24
+ conditional_action = flow_control.aliases.conditional_action
25
+ guarded_action = flow_control.aliases.guarded_action
26
+
27
+ # Operator Aliases
28
+ run_in_parallel = operators.aliases.run_in_parallel
29
+ run_in_sequence = operators.aliases.run_in_sequence
30
+ try_or_fallback = operators.aliases.try_or_fallback
31
+
32
+ # Primitive Aliases
33
+ pass_through = primitives.aliases.pass_through
34
+ replace_with = primitives.aliases.replace_with
35
+
36
+ # Wrapper Aliases
37
+ callable_action = wrappers.aliases.callable_action
38
+ log_step = wrappers.aliases.log_step
39
+ retry_on_failure = wrappers.aliases.retry_on_failure
40
+ run_side_effect = wrappers.aliases.run_side_effect
41
+
42
+
43
+ aliases = _Aliases()
44
+
45
+
46
+ __all__ = [
47
+ # Composition Classes
48
+ "ParallelCompose",
49
+ "SequenceCompose",
50
+ # Flow Control
51
+ "branch",
52
+ "for_each",
53
+ "guard",
54
+ # Operators
55
+ "fallback",
56
+ "parallel",
57
+ "sequence",
58
+ # Primitives
59
+ "constant",
60
+ "identity",
61
+ # Wrappers
62
+ "lambda_action",
63
+ "log_action",
64
+ "retry",
65
+ "tap",
66
+ # Aliases
67
+ "aliases",
68
+ ]
69
+
70
+
71
+ _DESIGN_NOTES = """
72
+ # Main Action Containers Package
73
+
74
+ ## Purpose
75
+ The `containers` package forms the core structural foundation of the action system. It provides
76
+ a rich set of higher-order action containers that manage execution flow, error handling, batching,
77
+ parallelism, side effects, and pipeline primitive building blocks.
78
+
79
+ ## Internal Sub-Packages Registry
80
+
81
+ | Sub-Package | Type | Description |
82
+ | :------------- | :----------------- | :------------------------------------------------------------------------------ |
83
+ | `_helpers` | Internal Helpers | Private utilities for action normalization, inspection, and sequence unwrapping.|
84
+ | `flow_control` | Public Containers | Branching (`branch`), iteration (`for_each`), and conditional evaluation (`guard`).|
85
+ | `operators` | Public Containers | Main composition containers (`sequence`, `parallel`, `fallback`) and classes. |
86
+ | `primitives` | Public Containers | Fundamental pipeline building blocks (`identity`, `constant`). |
87
+ | `wrappers` | Public Containers | Decorating and utility containers (`lambda_action`, `log_action`, `retry`, `tap`).|
88
+ """
@@ -0,0 +1,18 @@
1
+ from .as_action import as_action
2
+ from .as_predicate import as_predicate
3
+
4
+
5
+ _DESIGN_NOTES = """
6
+ # Action Container Helpers Sub-Package
7
+
8
+ ## Purpose
9
+ Internal resolution and adaptation helpers used across action containers to normalize
10
+ arbitrary runnables into unified `Action` instances and condition inputs into executable predicates.
11
+
12
+ ## Internal Components Registry
13
+
14
+ | Component | Type | Description |
15
+ | :------------- | :------- | :------------------------------------------------------------------------------ |
16
+ | `as_action` | Alias | Direct shorthand for `Action.resolve_action` to normalize runnables to Actions. |
17
+ | `as_predicate` | Function | Normalizes `Rule` instances or callables into unified boolean-returning methods.|
18
+ """
@@ -0,0 +1,31 @@
1
+ # Outers
2
+ from ...base_class import Action
3
+
4
+
5
+ as_action = Action.resolve_action
6
+ """Module-level alias for `Action.resolve_action`.
7
+
8
+ Provides a clean, self-describing shorthand within container implementations,
9
+ eliminating the need to repeatedly access the static method via `Action.resolve_action(...)`.
10
+ """
11
+
12
+
13
+ _DESIGN_NOTES = """
14
+ # as_action — module-level alias for Action.resolve_action
15
+
16
+ ## Purpose
17
+ Every container that accepts "something runnable" (`then_branch`, `action`,
18
+ `on_error`, individual `sequence`/`parallel` steps...) needs to resolve a
19
+ given object into a uniform `Action` instance.
20
+ `Action.resolve_action` houses the core resolution logic. `as_action` serves
21
+ as a module-level alias to avoid repeated, verbose access via the class name
22
+ (`Action.resolve_action`) across container modules, making the call sites
23
+ substantially more self-describing and expressive.
24
+
25
+ ## Why a direct assignment alias, not a wrapping function
26
+ No wrapper overhead or duplicate logic — all resolution mechanics (detecting
27
+ `Action`/`Rule`/callable and adapting accordingly) remain strictly encapsulated
28
+ inside `Action.resolve_action`. Assigning `as_action = Action.resolve_action`
29
+ directly guarantees zero execution overhead while retaining full IDE signature
30
+ hints, docstrings, and type safety.
31
+ """
@@ -0,0 +1,58 @@
1
+ from typing import Any, Callable
2
+ from simplibs.rules import Rule
3
+ # Inners
4
+ from .validations import raise_invalid_predicate
5
+
6
+
7
+ def as_predicate(condition: Any) -> Callable[[Any], bool]:
8
+ """Normalize a Rule instance or a plain callable into a single bool-returning call.
9
+
10
+ Replaces repeated duck-typing checks in `guard`/`branch` with a single
11
+ source of truth — aligning with the helper pattern used across the library.
12
+ """
13
+
14
+ # 1. Official Rule instance -> direct method reference (fastest path)
15
+ if isinstance(condition, Rule):
16
+ return condition.is_valid
17
+
18
+ # 2. Plain lambda / function / any callable
19
+ if callable(condition):
20
+ # noinspection PyTypeChecker
21
+ return condition
22
+
23
+ # 3. Structured exception helper for invalid predicate inputs
24
+ raise_invalid_predicate(condition)
25
+
26
+
27
+ _DESIGN_NOTES = """
28
+ # as_predicate — shared condition adapter
29
+
30
+ ## Purpose
31
+ `guard` and `branch` both need the same thing: "give me a function that
32
+ returns bool for `data`", whether `condition`/`rule` arrived as a `Rule`
33
+ instance (`is_valid`) or a plain callable. Without this helper, both
34
+ containers would carry duplicate validation and resolution logic. Using an
35
+ explicit `isinstance(condition, Rule)` check ensures fast and type-safe
36
+ method binding.
37
+
38
+ ## Exception Handling
39
+ When an invalid predicate (neither a `Rule` nor a callable) is passed,
40
+ `raise_invalid_predicate` is triggered to throw a structured exception,
41
+ keeping validation error formats consistent across the library.
42
+
43
+ ## Why it doesn't special-case Action
44
+ `Action.__call__` is callable too, but this helper treats it like any
45
+ other callable — it falls into the `callable(condition)` branch and gets
46
+ invoked directly. The result (`Any`, not necessarily `bool`) is then
47
+ evaluated by ordinary Python truthiness in `guard`/`branch`. This works,
48
+ but gives no guarantee the return value is meaningful as a condition —
49
+ left unaddressed here on purpose; if it ever needs constraining, that
50
+ belongs in `guard`/`branch` themselves, not this shared helper.
51
+
52
+ ## Relationship to as_action
53
+ Sibling in this same `_helpers` package — `as_predicate` answers "is this
54
+ true?", `as_action` answers "run this over data". Split into two files,
55
+ not one `_helpers.py`, since each serves a different group of containers,
56
+ consistent with the "one item per file" convention across the rest
57
+ of the library.
58
+ """
@@ -0,0 +1,16 @@
1
+ from .raise_invalid_predicate import raise_invalid_predicate
2
+
3
+
4
+ _DESIGN_NOTES = """
5
+ # Action Container Helper Validations Sub-Package
6
+
7
+ ## Purpose
8
+ Internal structured exception emission helpers dedicated to validating condition
9
+ and predicate inputs used by container helper functions.
10
+
11
+ ## Internal Components Registry
12
+
13
+ | Component | Type | Description |
14
+ | :------------------------ | :------- | :------------------------------------------------------------------------------ |
15
+ | `raise_invalid_predicate` | Function | Raises a structured `ParamError` when an input is neither a `Rule` nor callable.|
16
+ """
@@ -0,0 +1,42 @@
1
+ from typing import Any, NoReturn
2
+ from simplibs.exception import ParamError
3
+
4
+
5
+ def raise_invalid_predicate(condition: Any) -> NoReturn:
6
+ """Raise a ParamError if the given condition is neither a Rule nor a Callable."""
7
+
8
+ # 1. Raise structured ParamError wrapping a TypeError exception
9
+ raise ParamError(
10
+ error_name="INVALID_PREDICATE_TARGET",
11
+ label="condition / rule",
12
+ value=type(condition).__name__,
13
+ problem=(
14
+ f"Expected a Rule or Callable, got: {type(condition).__name__}.",
15
+ "The passed object cannot be evaluated as a predicate or condition.",
16
+ ),
17
+ expected="A Rule instance or a callable function (Callable).",
18
+ how_to_fix=(
19
+ "Ensure that you pass a Rule object or a callable function returning a boolean as the condition.",
20
+ ),
21
+ exception=TypeError,
22
+ )
23
+
24
+
25
+ _DESIGN_NOTES = """
26
+ # raise_invalid_predicate — structured validation error helper
27
+
28
+ ## Purpose
29
+ Provides a centralized, structured exception emitter when a non-predicate value
30
+ (neither a `Rule` instance nor a callable object) is supplied where a condition
31
+ is expected (e.g., in `guard` or `branch` containers).
32
+
33
+ ## Target Code & Integration
34
+ - **Caller**: `as_predicate` helper in `containers/_helpers/as_predicate.py`.
35
+ - **Target Context**: Triggered during container setup/execution when normalizing
36
+ input arguments passed as rules or conditional branches.
37
+
38
+ ## Why a separate helper
39
+ Isolating exception construction keeps the core `as_predicate` resolution loop
40
+ lean, fast, and easy to read, while enforcing consistent structured error messaging
41
+ (`ParamError`) across the library.
42
+ """
@@ -0,0 +1,21 @@
1
+ from . import aliases
2
+ from .branch import branch
3
+ from .for_each import for_each
4
+ from .guard import guard
5
+
6
+
7
+ _DESIGN_NOTES = """
8
+ # Flow Control Containers Sub-Package
9
+
10
+ ## Purpose
11
+ Provides flow control containers that manage data routing, conditional execution,
12
+ and collection iteration within action pipelines.
13
+
14
+ ## Internal Components Registry
15
+
16
+ | Component | Type | Description |
17
+ | :--------- | :-------- | :------------------------------------------------------------------------------ |
18
+ | `branch` | Container | Soft conditional branching (routes data or passes through unchanged). |
19
+ | `for_each` | Container | Iterative execution (applies an action to each item of an iterable). |
20
+ | `guard` | Container | Hard gate verification (passes data if rule holds, otherwise raises). |
21
+ """