pythagoras 0.24.6__py3-none-any.whl → 0.24.8__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.
- pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +3 -0
- pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +3 -0
- pythagoras/_070_protected_code_portals/basic_pre_validators.py +130 -15
- pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +20 -18
- pythagoras/_070_protected_code_portals/protected_decorators.py +59 -1
- pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +169 -4
- pythagoras/_080_pure_code_portals/__init__.py +5 -1
- pythagoras/_080_pure_code_portals/pure_core_classes.py +178 -25
- pythagoras/_080_pure_code_portals/pure_decorator.py +37 -0
- pythagoras/_080_pure_code_portals/recursion_pre_validator.py +39 -0
- pythagoras/_090_swarming_portals/output_suppressor.py +32 -3
- pythagoras/_090_swarming_portals/swarming_portals.py +165 -19
- pythagoras/_100_top_level_API/__init__.py +11 -0
- pythagoras/_800_signatures_and_converters/__init__.py +17 -0
- pythagoras/core/__init__.py +54 -0
- {pythagoras-0.24.6.dist-info → pythagoras-0.24.8.dist-info}/METADATA +1 -1
- {pythagoras-0.24.6.dist-info → pythagoras-0.24.8.dist-info}/RECORD +18 -18
- {pythagoras-0.24.6.dist-info → pythagoras-0.24.8.dist-info}/WHEEL +0 -0
|
@@ -39,6 +39,9 @@ class LoggingFn(StorableFn):
|
|
|
39
39
|
exceptions, and custom events. It also supports an excessive_logging mode
|
|
40
40
|
that enables storing rich per-call artifacts.
|
|
41
41
|
|
|
42
|
+
A logging function can only be called with keyword arguments.
|
|
43
|
+
It can't be called with positional arguments.
|
|
44
|
+
|
|
42
45
|
Attributes:
|
|
43
46
|
_auxiliary_config_params_at_init (dict): Internal configuration store
|
|
44
47
|
inherited from StorableFn. Includes the 'excessive_logging' flag
|
|
@@ -23,6 +23,9 @@ Autonomous functions can have nested functions and classes.
|
|
|
23
23
|
Only ordinary functions can be autonomous. Asynchronous functions, closures,
|
|
24
24
|
class methods, and lambda functions cannot be autonomous.
|
|
25
25
|
|
|
26
|
+
An autonomous function can only be called with keyword arguments.
|
|
27
|
+
It can't be called with positional arguments.
|
|
28
|
+
|
|
26
29
|
Autonomous functions support partial application of arguments:
|
|
27
30
|
the process of pre-filling some arguments of a function,
|
|
28
31
|
producing a new autonomous function that takes the remaining arguments.
|
|
@@ -1,34 +1,133 @@
|
|
|
1
|
+
"""Basic pre-validation utilities for protected code portals.
|
|
2
|
+
|
|
3
|
+
This module contains small, composable validators used by protected portals
|
|
4
|
+
before executing user functions. Each public factory returns a
|
|
5
|
+
SimplePreValidatorFn configured with fixed arguments, so validators can be
|
|
6
|
+
attached declaratively to protected functions.
|
|
7
|
+
|
|
8
|
+
Execution context:
|
|
9
|
+
- When executed by a portal, validator functions run with two names injected
|
|
10
|
+
into their global namespace: `self` (the ValidatorFn instance) and `pth`
|
|
11
|
+
(the pythagoras package). This allows them to access portal services such as
|
|
12
|
+
system introspection and package management.
|
|
13
|
+
|
|
14
|
+
Conventions:
|
|
15
|
+
- Return ValidationSuccessFlag (VALIDATION_SUCCESSFUL) to indicate the check
|
|
16
|
+
passed; return None to indicate the check did not pass.
|
|
17
|
+
"""
|
|
18
|
+
|
|
1
19
|
from .._070_protected_code_portals import SimplePreValidatorFn
|
|
2
20
|
from .validation_succesful_const import ValidationSuccessFlag
|
|
3
21
|
|
|
4
22
|
|
|
5
|
-
def _at_least_X_CPU_cores_free_check(n:int)-> ValidationSuccessFlag | None:
|
|
23
|
+
def _at_least_X_CPU_cores_free_check(n: int) -> ValidationSuccessFlag | None:
|
|
24
|
+
"""Pass if at least ``n`` logical CPU cores are currently free.
|
|
25
|
+
|
|
26
|
+
This is a lightweight runtime check based on a heuristic estimation of
|
|
27
|
+
unused logical CPU capacity (see pth.get_unused_cpu_cores).
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
n (int): Minimum number of free logical CPU cores required.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
ValidationSuccessFlag | None: VALIDATION_SUCCESSFUL if the estimated
|
|
34
|
+
number of free cores is >= n (within a small 0.1 tolerance);
|
|
35
|
+
otherwise None.
|
|
36
|
+
|
|
37
|
+
Notes:
|
|
38
|
+
- The tolerance (0.1) helps account for fluctuations in the estimator.
|
|
39
|
+
- Uses instantaneous/short-horizon metrics; momentary spikes may affect
|
|
40
|
+
the outcome.
|
|
41
|
+
"""
|
|
6
42
|
cores = pth.get_unused_cpu_cores()
|
|
7
|
-
if cores >= n-0.1:
|
|
43
|
+
if cores >= n - 0.1:
|
|
8
44
|
return pth.VALIDATION_SUCCESSFUL
|
|
9
45
|
|
|
10
46
|
|
|
11
|
-
def unused_cpu(cores:int) -> SimplePreValidatorFn:
|
|
12
|
-
|
|
13
|
-
|
|
47
|
+
def unused_cpu(cores: int) -> SimplePreValidatorFn:
|
|
48
|
+
"""Create a validator that requires at least the given free CPU cores.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
cores (int): Minimum number of free logical CPU cores required (> 0).
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
SimplePreValidatorFn: A pre-validator that succeeds only when
|
|
55
|
+
the system has at least ``cores`` free logical CPU cores.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
TypeError: If ``cores`` is not an integer.
|
|
59
|
+
ValueError: If ``cores`` is not greater than 0.
|
|
60
|
+
"""
|
|
61
|
+
if not isinstance(cores, int):
|
|
62
|
+
raise TypeError("cores must be an int")
|
|
63
|
+
if cores <= 0:
|
|
64
|
+
raise ValueError("cores must be > 0")
|
|
14
65
|
return SimplePreValidatorFn(_at_least_X_CPU_cores_free_check).fix_kwargs(n=cores)
|
|
15
66
|
|
|
16
67
|
|
|
17
|
-
def _at_least_X_G_RAM_free_check(x:int)-> ValidationSuccessFlag | None:
|
|
68
|
+
def _at_least_X_G_RAM_free_check(x: int) -> ValidationSuccessFlag | None:
|
|
69
|
+
"""Pass if at least ``x`` GiB of RAM are currently available.
|
|
70
|
+
|
|
71
|
+
The check uses pth.get_unused_ram_mb() divided by 1024 to obtain gibibytes
|
|
72
|
+
(GiB) and compares with a small tolerance.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
x (int): Minimum amount of free RAM in GiB required.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
ValidationSuccessFlag | None: VALIDATION_SUCCESSFUL if the estimated
|
|
79
|
+
free RAM in GiB is >= x (within a 0.1 tolerance); otherwise None.
|
|
80
|
+
"""
|
|
18
81
|
ram = pth.get_unused_ram_mb() / 1024
|
|
19
|
-
if ram >= x-0.1:
|
|
82
|
+
if ram >= x - 0.1:
|
|
20
83
|
return pth.VALIDATION_SUCCESSFUL
|
|
21
84
|
|
|
22
85
|
|
|
23
|
-
def unused_ram(Gb:int) -> SimplePreValidatorFn:
|
|
24
|
-
|
|
25
|
-
|
|
86
|
+
def unused_ram(Gb: int) -> SimplePreValidatorFn:
|
|
87
|
+
"""Create a validator that requires at least the given free RAM (GiB).
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
Gb (int): Minimum free memory required in GiB (> 0).
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
SimplePreValidatorFn: A pre-validator that succeeds only when
|
|
94
|
+
the system has at least ``Gb`` GiB of RAM available.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
TypeError: If ``Gb`` is not an integer.
|
|
98
|
+
ValueError: If ``Gb`` is not greater than 0.
|
|
99
|
+
"""
|
|
100
|
+
if not isinstance(Gb, int):
|
|
101
|
+
raise TypeError("Gb must be an int")
|
|
102
|
+
if Gb <= 0:
|
|
103
|
+
raise ValueError("Gb must be > 0")
|
|
26
104
|
return SimplePreValidatorFn(_at_least_X_G_RAM_free_check).fix_kwargs(x=Gb)
|
|
27
105
|
|
|
28
106
|
|
|
29
107
|
def _check_python_package_and_install_if_needed(
|
|
30
|
-
package_name)-> ValidationSuccessFlag | None:
|
|
31
|
-
|
|
108
|
+
package_name: str) -> ValidationSuccessFlag | None:
|
|
109
|
+
"""Ensure a Python package is importable, attempting installation if not.
|
|
110
|
+
|
|
111
|
+
This validator tries to import the given package. If import fails, it will
|
|
112
|
+
throttle installation attempts to at most once every 10 minutes per
|
|
113
|
+
(node, package) pair and then invoke pth.install_package(package_name).
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
package_name (str): The importable package/module name to check.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
ValidationSuccessFlag | None: VALIDATION_SUCCESSFUL if the package is
|
|
120
|
+
already importable or was successfully installed; otherwise None.
|
|
121
|
+
|
|
122
|
+
Notes:
|
|
123
|
+
- The function relies on names injected by the portal: ``self`` (the
|
|
124
|
+
validator instance) and ``pth`` (pythagoras package).
|
|
125
|
+
- Throttling key is a tuple of (node_signature, package_name,
|
|
126
|
+
"installation_attempt") stored in portal._config_settings.
|
|
127
|
+
- Installation is performed synchronously and may take time.
|
|
128
|
+
"""
|
|
129
|
+
if not isinstance(package_name, str):
|
|
130
|
+
raise TypeError("package_name must be a str")
|
|
32
131
|
import importlib, time
|
|
33
132
|
try:
|
|
34
133
|
importlib.import_module(package_name)
|
|
@@ -46,11 +145,27 @@ def _check_python_package_and_install_if_needed(
|
|
|
46
145
|
return pth.VALIDATION_SUCCESSFUL
|
|
47
146
|
|
|
48
147
|
|
|
49
|
-
def installed_packages(*args)
|
|
148
|
+
def installed_packages(*args) -> list[SimplePreValidatorFn]:
|
|
149
|
+
"""Create validators ensuring each named package is available.
|
|
150
|
+
|
|
151
|
+
For each provided package name, this returns a SimplePreValidatorFn that
|
|
152
|
+
checks the package is importable and installs it if needed (with throttling).
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
*args: One or more package names as strings.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
list[SimplePreValidatorFn]: A list of pre-validators, one per package
|
|
159
|
+
name, preserving the original order.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
TypeError: If any of the provided arguments is not a string.
|
|
163
|
+
"""
|
|
50
164
|
validators = []
|
|
51
165
|
for package_name in args:
|
|
52
|
-
|
|
53
|
-
|
|
166
|
+
if not isinstance(package_name, str):
|
|
167
|
+
raise TypeError("All package names must be strings")
|
|
168
|
+
# TODO: check if the package is available on pypi.org
|
|
54
169
|
new_validator = SimplePreValidatorFn(_check_python_package_and_install_if_needed)
|
|
55
170
|
new_validator = new_validator.fix_kwargs(package_name=package_name)
|
|
56
171
|
validators.append(new_validator)
|
|
@@ -3,15 +3,21 @@ from typing import List, Set
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def check_if_fn_accepts_args(required_arg_names: List[str]|Set[str], fn: str) -> bool:
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
"""Determine whether a function can accept specific keyword argument names.
|
|
7
|
+
|
|
8
|
+
Analyzes the source code of a single Python function and checks whether
|
|
9
|
+
all required names could be passed as keyword arguments.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
Args:
|
|
12
|
+
required_arg_names: Iterable of parameter names that must be accepted.
|
|
13
|
+
fn: Source code string containing exactly one function definition.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Returns:
|
|
16
|
+
True if the function has **kwargs or explicitly defines all required names
|
|
17
|
+
as keyword-acceptable parameters; otherwise False.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: If no function definition is found or if multiple functions are present.
|
|
15
21
|
"""
|
|
16
22
|
|
|
17
23
|
tree = ast.parse(fn)
|
|
@@ -19,6 +25,8 @@ def check_if_fn_accepts_args(required_arg_names: List[str]|Set[str], fn: str) ->
|
|
|
19
25
|
func_def_nodes = [node for node in tree.body if isinstance(node, ast.FunctionDef)]
|
|
20
26
|
if not func_def_nodes:
|
|
21
27
|
raise ValueError("No function definition found in the provided source code.")
|
|
28
|
+
if not len(func_def_nodes) == 1:
|
|
29
|
+
raise ValueError("Multiple function definitions found in the provided source code.")
|
|
22
30
|
func_def = func_def_nodes[0]
|
|
23
31
|
args = func_def.args
|
|
24
32
|
|
|
@@ -26,14 +34,8 @@ def check_if_fn_accepts_args(required_arg_names: List[str]|Set[str], fn: str) ->
|
|
|
26
34
|
if args.kwarg is not None:
|
|
27
35
|
return True
|
|
28
36
|
|
|
29
|
-
# Collect all explicitly named parameters
|
|
30
|
-
|
|
31
|
-
for arg in args.args
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
param_names.add(kwarg.arg)
|
|
35
|
-
|
|
36
|
-
for name in required_arg_names:
|
|
37
|
-
if name not in param_names:
|
|
38
|
-
return False
|
|
39
|
-
return True
|
|
37
|
+
# Collect all explicitly named parameters (excluding positional-only)
|
|
38
|
+
# Note: args.posonlyargs are NOT included as they cannot be passed by keyword
|
|
39
|
+
param_names = {arg.arg for arg in args.args + args.kwonlyargs}
|
|
40
|
+
|
|
41
|
+
return set(required_arg_names).issubset(param_names)
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Decorators for building protected functions.
|
|
2
|
+
|
|
3
|
+
This module provides the protected decorator which wraps callables
|
|
4
|
+
into ProtectedFn objects. A ProtectedFn coordinates pre- and post-execution
|
|
5
|
+
validation using ValidatorFn instances and executes within a
|
|
6
|
+
ProtectedCodePortal context.
|
|
7
|
+
|
|
8
|
+
The decorator is a thin, declarative layer over the underlying core classes,
|
|
9
|
+
allowing you to attach validators at definition
|
|
10
|
+
time while keeping function logic clean and focused.
|
|
11
|
+
"""
|
|
2
12
|
|
|
3
13
|
from typing import Callable, Any
|
|
4
14
|
|
|
@@ -8,6 +18,27 @@ from .protected_portal_core_classes import *
|
|
|
8
18
|
from persidict import Joker, KEEP_CURRENT
|
|
9
19
|
|
|
10
20
|
class protected(autonomous):
|
|
21
|
+
"""Decorator for protected functions with pre/post validation.
|
|
22
|
+
|
|
23
|
+
This decorator wraps a target callable into a ProtectedFn that enforces
|
|
24
|
+
a sequence of pre- and post-execution validators. It builds on the
|
|
25
|
+
autonomous decorator, adding validator support to it.
|
|
26
|
+
|
|
27
|
+
Typical usage:
|
|
28
|
+
@protected(pre_validators=[...], post_validators=[...])
|
|
29
|
+
def fn(...):
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
See Also:
|
|
33
|
+
ProtectedFn: The runtime wrapper that performs validation and execution.
|
|
34
|
+
ProtectedCodePortal: Portal coordinating protected function execution.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
_pre_validators (list[ValidatorFn] | None): Validators executed before
|
|
38
|
+
the target function.
|
|
39
|
+
_post_validators (list[ValidatorFn] | None): Validators executed after
|
|
40
|
+
the target function.
|
|
41
|
+
"""
|
|
11
42
|
|
|
12
43
|
_pre_validators: list[ValidatorFn] | None
|
|
13
44
|
_post_validators: list[ValidatorFn] | None
|
|
@@ -19,6 +50,24 @@ class protected(autonomous):
|
|
|
19
50
|
, excessive_logging: bool|Joker = KEEP_CURRENT
|
|
20
51
|
, portal: ProtectedCodePortal | None = None
|
|
21
52
|
):
|
|
53
|
+
"""Initialize the protected decorator.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
pre_validators (list[ValidatorFn] | None): Pre-execution validators
|
|
57
|
+
to apply. Each item is either a ValidatorFn or a callable that
|
|
58
|
+
can be wrapped into a PreValidatorFn by ProtectedFn.
|
|
59
|
+
post_validators (list[ValidatorFn] | None): Post-execution validators
|
|
60
|
+
to apply. Each item is either a ValidatorFn or a callable that
|
|
61
|
+
can be wrapped into a PostValidatorFn by ProtectedFn.
|
|
62
|
+
fixed_kwargs (dict[str, Any] | None): Keyword arguments to pre-bind
|
|
63
|
+
to the wrapped function for every call.
|
|
64
|
+
excessive_logging (bool | Joker): Enables verbose logging for the
|
|
65
|
+
wrapped function and its validators. Use KEEP_CURRENT to inherit
|
|
66
|
+
the current setting from the portal/context.
|
|
67
|
+
portal (ProtectedCodePortal | None): Optional portal instance to
|
|
68
|
+
bind the wrapped function to. If None, a suitable portal will be
|
|
69
|
+
inferred when fuction is called.
|
|
70
|
+
"""
|
|
22
71
|
assert isinstance(portal, ProtectedCodePortal) or portal is None
|
|
23
72
|
assert isinstance(fixed_kwargs, dict) or fixed_kwargs is None
|
|
24
73
|
autonomous.__init__(self=self
|
|
@@ -30,6 +79,15 @@ class protected(autonomous):
|
|
|
30
79
|
|
|
31
80
|
|
|
32
81
|
def __call__(self, fn: Callable|str) -> ProtectedFn:
|
|
82
|
+
"""Wrap the given function into a ProtectedFn.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
fn (Callable | str): The target function or its source code string.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
ProtectedFn: A wrapper that performs pre/post validation and then
|
|
89
|
+
executes the function.
|
|
90
|
+
"""
|
|
33
91
|
wrapper = ProtectedFn(fn
|
|
34
92
|
, portal=self._portal
|
|
35
93
|
, pre_validators=self._pre_validators
|
|
@@ -54,6 +54,16 @@ class ProtectedCodePortal(AutonomousCodePortal):
|
|
|
54
54
|
, p_consistency_checks: float|Joker = KEEP_CURRENT
|
|
55
55
|
, excessive_logging: bool|Joker = KEEP_CURRENT
|
|
56
56
|
):
|
|
57
|
+
"""Initialize the portal.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
root_dict (PersiDict | str | None): Backing storage or its path.
|
|
61
|
+
If None, use default.
|
|
62
|
+
p_consistency_checks (float | Joker): Probability for internal
|
|
63
|
+
consistency checks (or KEEP_CURRENT to inherit).
|
|
64
|
+
excessive_logging (bool | Joker): Verbose logging flag
|
|
65
|
+
(KEEP_CURRENT to inherit).
|
|
66
|
+
"""
|
|
57
67
|
super().__init__(root_dict=root_dict
|
|
58
68
|
, p_consistency_checks=p_consistency_checks
|
|
59
69
|
, excessive_logging=excessive_logging)
|
|
@@ -220,6 +230,16 @@ class ProtectedFn(AutonomousFn):
|
|
|
220
230
|
def validate_execution_result(self
|
|
221
231
|
, kw_args: KwArgs
|
|
222
232
|
, result: Any) -> ValidationSuccessFlag|None:
|
|
233
|
+
"""Run post-validators to confirm the execution result is acceptable.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
kw_args (KwArgs): Arguments that were passed to the protected function.
|
|
237
|
+
result (Any): The value returned by the protected function.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
ValidationSuccessFlag | None: VALIDATION_SUCCESSFUL if all
|
|
241
|
+
post-validators pass, otherwise None.
|
|
242
|
+
"""
|
|
223
243
|
with self.portal as portal:
|
|
224
244
|
kw_args = kw_args.pack()
|
|
225
245
|
post_validators = copy(self.post_validators)
|
|
@@ -232,6 +252,24 @@ class ProtectedFn(AutonomousFn):
|
|
|
232
252
|
|
|
233
253
|
|
|
234
254
|
def execute(self, **kwargs) -> Any:
|
|
255
|
+
"""Execute the protected function with validation.
|
|
256
|
+
|
|
257
|
+
This method performs the following loop:
|
|
258
|
+
- Runs pre-validators. If a pre-validator returns a
|
|
259
|
+
ProtectedFnCallSignature, that signature is executed and validation is
|
|
260
|
+
reattempted. If any pre-validator fails, an AssertionError is raised.
|
|
261
|
+
- Executes the wrapped function.
|
|
262
|
+
- Runs post-validators and asserts they all succeed.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
**kwargs: Keyword arguments to pass to the wrapped function.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Any: The result returned by the wrapped function.
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
AssertionError: If pre- or post-validation fails.
|
|
272
|
+
"""
|
|
235
273
|
with (self.portal):
|
|
236
274
|
kw_args = KwArgs(**kwargs)
|
|
237
275
|
while True:
|
|
@@ -252,8 +290,21 @@ class ProtectedFn(AutonomousFn):
|
|
|
252
290
|
) -> list[ValidatorFn]:
|
|
253
291
|
"""Return list of validators in a normalized form.
|
|
254
292
|
|
|
255
|
-
|
|
256
|
-
|
|
293
|
+
- Wraps plain callables/strings into appropriate ValidatorFn subclasses.
|
|
294
|
+
- Flattens nested lists.
|
|
295
|
+
- Removes duplicates while inforcing deterministic
|
|
296
|
+
order via sort_dict_by_keys.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
validators (list[ValidatorFn] | ValidatorFn | None): Validators in
|
|
300
|
+
any supported representation (single, list, nested lists, etc.).
|
|
301
|
+
validator_type (type): Either PreValidatorFn or PostValidatorFn.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
list[ValidatorFn]: A sorted list of validator instances.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
TypeError: If an unexpected validator_type is provided.
|
|
257
308
|
"""
|
|
258
309
|
assert validator_type in {PreValidatorFn, PostValidatorFn}
|
|
259
310
|
if validators is None:
|
|
@@ -286,6 +337,12 @@ class ProtectedFn(AutonomousFn):
|
|
|
286
337
|
|
|
287
338
|
@property
|
|
288
339
|
def portal(self) -> ProtectedCodePortal:
|
|
340
|
+
"""Return the bound ProtectedCodePortal.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
ProtectedCodePortal: The portal controlling execution context and
|
|
344
|
+
storage for this protected function.
|
|
345
|
+
"""
|
|
289
346
|
return super().portal
|
|
290
347
|
|
|
291
348
|
|
|
@@ -310,14 +367,33 @@ class ProtectedFn(AutonomousFn):
|
|
|
310
367
|
|
|
311
368
|
|
|
312
369
|
def get_signature(self, arguments:dict) -> ProtectedFnCallSignature:
|
|
370
|
+
"""Create a call signature for this protected function.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
arguments (dict): Arguments to bind into the call signature.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
ProtectedFnCallSignature: Signature object representing a
|
|
377
|
+
particular call to this function.
|
|
378
|
+
"""
|
|
313
379
|
return ProtectedFnCallSignature(self, arguments)
|
|
314
380
|
|
|
315
381
|
|
|
316
382
|
class ProtectedFnCallSignature(AutonomousFnCallSignature):
|
|
317
|
-
"""
|
|
383
|
+
"""Invocation signature for a protected function.
|
|
384
|
+
|
|
385
|
+
Encapsulates a function reference and bound arguments that can be executed
|
|
386
|
+
later via execute().
|
|
387
|
+
"""
|
|
318
388
|
_fn_cache: ProtectedFn | None
|
|
319
389
|
|
|
320
390
|
def __init__(self, fn: ProtectedFn, arguments: dict):
|
|
391
|
+
"""Initialize the signature.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
fn (ProtectedFn): The protected function to call.
|
|
395
|
+
arguments (dict): Keyword arguments to be passed at execution time.
|
|
396
|
+
"""
|
|
321
397
|
assert isinstance(fn, ProtectedFn)
|
|
322
398
|
assert isinstance(arguments, dict)
|
|
323
399
|
super().__init__(fn, arguments)
|
|
@@ -329,10 +405,26 @@ class ProtectedFnCallSignature(AutonomousFnCallSignature):
|
|
|
329
405
|
|
|
330
406
|
|
|
331
407
|
class ValidatorFn(AutonomousFn):
|
|
408
|
+
"""Base class for validator wrappers.
|
|
409
|
+
|
|
410
|
+
A ValidatorFn ensures the wrapped callable accepts exactly the keyword
|
|
411
|
+
arguments declared by get_allowed_kwargs_names(). Subclasses define the
|
|
412
|
+
specific interface for pre/post validation phases.
|
|
413
|
+
"""
|
|
332
414
|
def __init__(self, fn: Callable | str | AutonomousFn
|
|
333
415
|
, fixed_kwargs: dict | None = None
|
|
334
416
|
, excessive_logging: bool | Joker = KEEP_CURRENT
|
|
335
417
|
, portal: AutonomousCodePortal | None = None):
|
|
418
|
+
"""Initialize a validator function wrapper.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
fn (Callable | str | AutonomousFn): The validator implementation or
|
|
422
|
+
its source code.
|
|
423
|
+
fixed_kwargs (dict | None): Keyword arguments fixed for every
|
|
424
|
+
validation call.
|
|
425
|
+
excessive_logging (bool | Joker): Controls verbose logging.
|
|
426
|
+
portal (AutonomousCodePortal | None): Optional portal binding.
|
|
427
|
+
"""
|
|
336
428
|
super().__init__(
|
|
337
429
|
fn=fn
|
|
338
430
|
, fixed_kwargs=fixed_kwargs
|
|
@@ -344,20 +436,52 @@ class ValidatorFn(AutonomousFn):
|
|
|
344
436
|
|
|
345
437
|
@classmethod
|
|
346
438
|
def get_allowed_kwargs_names(cls)->set[str]:
|
|
439
|
+
"""Return the exact set of allowed keyword argument names.
|
|
440
|
+
|
|
441
|
+
Subclasses must override to declare their interface.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
set[str]: Names of keyword arguments accepted by execute().
|
|
445
|
+
"""
|
|
347
446
|
raise NotImplementedError("This method must be overridden")
|
|
348
447
|
|
|
349
448
|
|
|
350
449
|
def execute(self,**kwargs) \
|
|
351
450
|
-> ProtectedFnCallSignature | ValidationSuccessFlag | None:
|
|
451
|
+
"""Execute the validator after verifying keyword arguments.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
**kwargs: Must exactly match get_allowed_kwargs_names().
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
ProtectedFnCallSignature | ValidationSuccessFlag | None: Depending
|
|
458
|
+
on the validator type and outcome.
|
|
459
|
+
"""
|
|
352
460
|
assert set(kwargs) == self.get_allowed_kwargs_names()
|
|
353
461
|
return super().execute(**kwargs)
|
|
354
462
|
|
|
355
463
|
|
|
356
464
|
class PreValidatorFn(ValidatorFn):
|
|
465
|
+
"""Base class for pre-execution validators.
|
|
466
|
+
|
|
467
|
+
Pre-validators are executed before the protected function. They may return:
|
|
468
|
+
- VALIDATION_SUCCESSFUL to indicate execution can proceed;
|
|
469
|
+
- ProtectedFnCallSignature to request execution of an auxiliary action
|
|
470
|
+
prior to re-validating;
|
|
471
|
+
- None to indicate failure.
|
|
472
|
+
"""
|
|
357
473
|
def __init__(self, fn: Callable | str | AutonomousFn
|
|
358
474
|
, fixed_kwargs: dict | None = None
|
|
359
475
|
, excessive_logging: bool | Joker = KEEP_CURRENT
|
|
360
476
|
, portal: AutonomousCodePortal | None = None):
|
|
477
|
+
"""Initialize a pre-execution validator wrapper.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
fn (Callable | str | AutonomousFn): The pre-validator implementation.
|
|
481
|
+
fixed_kwargs (dict | None): Keyword arguments fixed for every call.
|
|
482
|
+
excessive_logging (bool | Joker): Controls verbose logging.
|
|
483
|
+
portal (AutonomousCodePortal | None): Optional portal binding.
|
|
484
|
+
"""
|
|
361
485
|
super().__init__(
|
|
362
486
|
fn=fn
|
|
363
487
|
, fixed_kwargs=fixed_kwargs
|
|
@@ -366,10 +490,22 @@ class PreValidatorFn(ValidatorFn):
|
|
|
366
490
|
|
|
367
491
|
|
|
368
492
|
class SimplePreValidatorFn(PreValidatorFn):
|
|
493
|
+
"""A pre-validator that takes no runtime inputs.
|
|
494
|
+
|
|
495
|
+
The wrapped callable must accept no parameters; use fixed_kwargs only.
|
|
496
|
+
"""
|
|
369
497
|
def __init__(self, fn: Callable | str | AutonomousFn
|
|
370
498
|
, fixed_kwargs: dict | None = None
|
|
371
499
|
, excessive_logging: bool | Joker = KEEP_CURRENT
|
|
372
500
|
, portal: AutonomousCodePortal | None = None):
|
|
501
|
+
"""Initialize a simple pre-validator.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
fn (Callable | str | AutonomousFn): The implementation.
|
|
505
|
+
fixed_kwargs (dict | None): Fixed keyword arguments, if any.
|
|
506
|
+
excessive_logging (bool | Joker): Controls verbose logging.
|
|
507
|
+
portal (AutonomousCodePortal | None): Optional portal binding.
|
|
508
|
+
"""
|
|
373
509
|
super().__init__(
|
|
374
510
|
fn=fn
|
|
375
511
|
, fixed_kwargs=fixed_kwargs
|
|
@@ -384,10 +520,23 @@ class SimplePreValidatorFn(PreValidatorFn):
|
|
|
384
520
|
|
|
385
521
|
|
|
386
522
|
class ComplexPreValidatorFn(PreValidatorFn):
|
|
523
|
+
"""A pre-validator that can inspect inputs and the function address.
|
|
524
|
+
|
|
525
|
+
The callable must accept the keyword arguments named
|
|
526
|
+
packed_kwargs and fn_addr.
|
|
527
|
+
"""
|
|
387
528
|
def __init__(self, fn: Callable | str | AutonomousFn
|
|
388
529
|
, fixed_kwargs: dict | None = None
|
|
389
530
|
, excessive_logging: bool | Joker = KEEP_CURRENT
|
|
390
531
|
, portal: AutonomousCodePortal | None = None):
|
|
532
|
+
"""Initialize a complex pre-validator.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
fn (Callable | str | AutonomousFn): The implementation.
|
|
536
|
+
fixed_kwargs (dict | None): Fixed keyword arguments, if any.
|
|
537
|
+
excessive_logging (bool | Joker): Controls verbose logging.
|
|
538
|
+
portal (AutonomousCodePortal | None): Optional portal binding.
|
|
539
|
+
"""
|
|
391
540
|
super().__init__(
|
|
392
541
|
fn=fn
|
|
393
542
|
, fixed_kwargs=fixed_kwargs
|
|
@@ -402,10 +551,22 @@ class ComplexPreValidatorFn(PreValidatorFn):
|
|
|
402
551
|
|
|
403
552
|
|
|
404
553
|
class PostValidatorFn(ValidatorFn):
|
|
554
|
+
"""Post-execution validator wrapper.
|
|
555
|
+
|
|
556
|
+
The callable must accept packed_kwargs, fn_addr, and result.
|
|
557
|
+
"""
|
|
405
558
|
def __init__(self, fn: Callable | str | AutonomousFn
|
|
406
559
|
, fixed_kwargs: dict | None = None
|
|
407
560
|
, excessive_logging: bool | Joker = KEEP_CURRENT
|
|
408
561
|
, portal: AutonomousCodePortal | None = None):
|
|
562
|
+
"""Initialize a post-execution validator.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
fn (Callable | str | AutonomousFn): The implementation.
|
|
566
|
+
fixed_kwargs (dict | None): Fixed keyword arguments, if any.
|
|
567
|
+
excessive_logging (bool | Joker): Controls verbose logging.
|
|
568
|
+
portal (AutonomousCodePortal | None): Optional portal binding.
|
|
569
|
+
"""
|
|
409
570
|
super().__init__(
|
|
410
571
|
fn=fn
|
|
411
572
|
, fixed_kwargs=fixed_kwargs
|
|
@@ -414,5 +575,9 @@ class PostValidatorFn(ValidatorFn):
|
|
|
414
575
|
|
|
415
576
|
@classmethod
|
|
416
577
|
def get_allowed_kwargs_names(cls) -> set[str]:
|
|
417
|
-
"""Post-validators use
|
|
578
|
+
"""Post-validators use function metadata, inputs, and the result.
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
set[str]: {"packed_kwargs", "fn_addr", "result"}
|
|
582
|
+
"""
|
|
418
583
|
return {"packed_kwargs", "fn_addr", "result" }
|
|
@@ -4,6 +4,10 @@ A pure function is a protected function that has no side effects and
|
|
|
4
4
|
always returns the same result if it is called multiple times
|
|
5
5
|
with the same arguments.
|
|
6
6
|
|
|
7
|
+
A pure function is an autonomous function, which means that it doesn't
|
|
8
|
+
depend on external imports or definitions. It also means it can only be called
|
|
9
|
+
with keyword arguments; positional arguments are not allowed.
|
|
10
|
+
|
|
7
11
|
This subpackage defines a decorator which is used to inform Pythagoras that
|
|
8
12
|
a function is intended to be pure: @pure().
|
|
9
13
|
|
|
@@ -17,7 +21,7 @@ changes in the source code of the function. If the source code of a pure
|
|
|
17
21
|
function changes, the function is executed again on the next call.
|
|
18
22
|
However, the previously cached results are still available
|
|
19
23
|
for the old version of the function. Only changes in the function's
|
|
20
|
-
source code are tracked.
|
|
24
|
+
source code are tracked, but not in the packages it is using.
|
|
21
25
|
"""
|
|
22
26
|
|
|
23
27
|
from .pure_core_classes import *
|