pythagoras 0.24.6__tar.gz → 0.24.7__tar.gz

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 (71) hide show
  1. {pythagoras-0.24.6 → pythagoras-0.24.7}/PKG-INFO +1 -1
  2. {pythagoras-0.24.6 → pythagoras-0.24.7}/pyproject.toml +1 -1
  3. pythagoras-0.24.7/src/pythagoras/_070_protected_code_portals/basic_pre_validators.py +172 -0
  4. pythagoras-0.24.7/src/pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +41 -0
  5. pythagoras-0.24.7/src/pythagoras/_070_protected_code_portals/protected_decorators.py +97 -0
  6. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +169 -4
  7. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_080_pure_code_portals/pure_core_classes.py +178 -25
  8. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_080_pure_code_portals/pure_decorator.py +37 -0
  9. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_080_pure_code_portals/recursion_pre_validator.py +39 -0
  10. pythagoras-0.24.7/src/pythagoras/_090_swarming_portals/output_suppressor.py +47 -0
  11. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_090_swarming_portals/swarming_portals.py +165 -19
  12. pythagoras-0.24.7/src/pythagoras/_100_top_level_API/__init__.py +13 -0
  13. pythagoras-0.24.7/src/pythagoras/_800_signatures_and_converters/__init__.py +22 -0
  14. pythagoras-0.24.7/src/pythagoras/core/__init__.py +60 -0
  15. pythagoras-0.24.6/src/pythagoras/_070_protected_code_portals/basic_pre_validators.py +0 -57
  16. pythagoras-0.24.6/src/pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +0 -39
  17. pythagoras-0.24.6/src/pythagoras/_070_protected_code_portals/protected_decorators.py +0 -39
  18. pythagoras-0.24.6/src/pythagoras/_090_swarming_portals/output_suppressor.py +0 -18
  19. pythagoras-0.24.6/src/pythagoras/_100_top_level_API/__init__.py +0 -2
  20. pythagoras-0.24.6/src/pythagoras/_800_signatures_and_converters/__init__.py +0 -5
  21. pythagoras-0.24.6/src/pythagoras/core/__init__.py +0 -6
  22. {pythagoras-0.24.6 → pythagoras-0.24.7}/README.md +0 -0
  23. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/.DS_Store +0 -0
  24. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_010_basic_portals/__init__.py +0 -0
  25. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_010_basic_portals/basic_portal_core_classes.py +0 -0
  26. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_010_basic_portals/exceptions.py +0 -0
  27. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_010_basic_portals/long_infoname.py +0 -0
  28. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_010_basic_portals/portal_tester.py +0 -0
  29. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_010_basic_portals/post_init_metaclass.py +0 -0
  30. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_020_ordinary_code_portals/__init__.py +0 -0
  31. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_020_ordinary_code_portals/code_normalizer.py +0 -0
  32. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_020_ordinary_code_portals/function_processing.py +0 -0
  33. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +0 -0
  34. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +0 -0
  35. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_030_data_portals/__init__.py +0 -0
  36. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_030_data_portals/data_portal_core_classes.py +0 -0
  37. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_030_data_portals/ready_and_get.py +0 -0
  38. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_030_data_portals/storable_decorator.py +0 -0
  39. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/__init__.py +0 -0
  40. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/exception_processing_tracking.py +0 -0
  41. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/execution_environment_summary.py +0 -0
  42. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/kw_args.py +0 -0
  43. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/logging_decorator.py +0 -0
  44. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +0 -0
  45. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/notebook_checker.py +0 -0
  46. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/output_capturer.py +0 -0
  47. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_040_logging_code_portals/uncaught_exceptions.py +0 -0
  48. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_050_safe_code_portals/__init__.py +0 -0
  49. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_050_safe_code_portals/safe_decorator.py +0 -0
  50. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +0 -0
  51. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_060_autonomous_code_portals/__init__.py +0 -0
  52. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +0 -0
  53. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +0 -0
  54. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +0 -0
  55. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_070_protected_code_portals/__init__.py +0 -0
  56. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_070_protected_code_portals/list_flattener.py +0 -0
  57. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_070_protected_code_portals/package_manager.py +0 -0
  58. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_070_protected_code_portals/system_utils.py +0 -0
  59. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_070_protected_code_portals/validation_succesful_const.py +0 -0
  60. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_080_pure_code_portals/__init__.py +0 -0
  61. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_090_swarming_portals/__init__.py +0 -0
  62. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_100_top_level_API/default_local_portal.py +0 -0
  63. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_100_top_level_API/top_level_API.py +0 -0
  64. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +0 -0
  65. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +0 -0
  66. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_800_signatures_and_converters/hash_signatures.py +0 -0
  67. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_800_signatures_and_converters/node_signature.py +0 -0
  68. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_800_signatures_and_converters/random_signatures.py +0 -0
  69. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_900_project_stats_collector/__init__.py +0 -0
  70. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/_900_project_stats_collector/project_analyzer.py +0 -0
  71. {pythagoras-0.24.6 → pythagoras-0.24.7}/src/pythagoras/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythagoras
3
- Version: 0.24.6
3
+ Version: 0.24.7
4
4
  Summary: Planet-scale distributed computing in Python.
5
5
  Keywords: cloud,ML,AI,serverless,distributed,parallel,machine-learning,deep-learning,pythagoras
6
6
  Author: Volodymyr (Vlad) Pavlov
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "pythagoras"
7
- version = "0.24.6"
7
+ version = "0.24.7"
8
8
  authors = [
9
9
  {name = "Volodymyr (Vlad) Pavlov", email = "vlpavlov@ieee.org"},
10
10
  ]
@@ -0,0 +1,172 @@
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
+
19
+ from .._070_protected_code_portals import SimplePreValidatorFn
20
+ from .validation_succesful_const import ValidationSuccessFlag
21
+
22
+
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
+ """
42
+ cores = pth.get_unused_cpu_cores()
43
+ if cores >= n - 0.1:
44
+ return pth.VALIDATION_SUCCESSFUL
45
+
46
+
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")
65
+ return SimplePreValidatorFn(_at_least_X_CPU_cores_free_check).fix_kwargs(n=cores)
66
+
67
+
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
+ """
81
+ ram = pth.get_unused_ram_mb() / 1024
82
+ if ram >= x - 0.1:
83
+ return pth.VALIDATION_SUCCESSFUL
84
+
85
+
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")
104
+ return SimplePreValidatorFn(_at_least_X_G_RAM_free_check).fix_kwargs(x=Gb)
105
+
106
+
107
+ def _check_python_package_and_install_if_needed(
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")
131
+ import importlib, time
132
+ try:
133
+ importlib.import_module(package_name)
134
+ return pth.VALIDATION_SUCCESSFUL
135
+ except:
136
+ portal = self.portal
137
+ address = (pth.get_node_signature()
138
+ , package_name
139
+ , "installation_attempt")
140
+ # allow installation retries every 10 minutes
141
+ if (not address in portal._config_settings
142
+ or portal._config_settings[address] < time.time() - 600):
143
+ portal._config_settings[address] = time.time()
144
+ pth.install_package(package_name)
145
+ return pth.VALIDATION_SUCCESSFUL
146
+
147
+
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
+ """
164
+ validators = []
165
+ for package_name in args:
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
169
+ new_validator = SimplePreValidatorFn(_check_python_package_and_install_if_needed)
170
+ new_validator = new_validator.fix_kwargs(package_name=package_name)
171
+ validators.append(new_validator)
172
+ return validators
@@ -0,0 +1,41 @@
1
+ import ast
2
+ from typing import List, Set
3
+
4
+
5
+ def check_if_fn_accepts_args(required_arg_names: List[str]|Set[str], fn: str) -> bool:
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.
10
+
11
+ Args:
12
+ required_arg_names: Iterable of parameter names that must be accepted.
13
+ fn: Source code string containing exactly one function definition.
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.
21
+ """
22
+
23
+ tree = ast.parse(fn)
24
+
25
+ func_def_nodes = [node for node in tree.body if isinstance(node, ast.FunctionDef)]
26
+ if not func_def_nodes:
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.")
30
+ func_def = func_def_nodes[0]
31
+ args = func_def.args
32
+
33
+ # If there's a **kwargs (args.kwarg != None), it can accept any named argument
34
+ if args.kwarg is not None:
35
+ return True
36
+
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)
@@ -0,0 +1,97 @@
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
+ """
12
+
13
+ from typing import Callable, Any
14
+
15
+ # from .validator_fn_classes import ValidatorFn
16
+ from .._060_autonomous_code_portals import autonomous
17
+ from .protected_portal_core_classes import *
18
+ from persidict import Joker, KEEP_CURRENT
19
+
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
+ """
42
+
43
+ _pre_validators: list[ValidatorFn] | None
44
+ _post_validators: list[ValidatorFn] | None
45
+
46
+ def __init__(self
47
+ , pre_validators: list[ValidatorFn] | None = None
48
+ , post_validators: list[ValidatorFn] | None = None
49
+ , fixed_kwargs: dict[str,Any] | None = None
50
+ , excessive_logging: bool|Joker = KEEP_CURRENT
51
+ , portal: ProtectedCodePortal | None = None
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
+ """
71
+ assert isinstance(portal, ProtectedCodePortal) or portal is None
72
+ assert isinstance(fixed_kwargs, dict) or fixed_kwargs is None
73
+ autonomous.__init__(self=self
74
+ , portal=portal
75
+ , excessive_logging=excessive_logging
76
+ , fixed_kwargs=fixed_kwargs)
77
+ self._pre_validators = pre_validators
78
+ self._post_validators = post_validators
79
+
80
+
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
+ """
91
+ wrapper = ProtectedFn(fn
92
+ , portal=self._portal
93
+ , pre_validators=self._pre_validators
94
+ , fixed_kwargs=self._fixed_kwargs
95
+ , post_validators=self._post_validators
96
+ , excessive_logging=self._excessive_logging)
97
+ return wrapper
@@ -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
- All the functions-validators are converted to AutonomousFn objects,
256
- and returned as a list, sorted by functions' hash signatures.
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
- """A signature of a call to a pure function"""
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 info about the function, its input arguments and returned value."""
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" }