pythagoras 0.24.3__py3-none-any.whl → 0.24.6__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/_020_ordinary_code_portals/code_normalizer.py +37 -9
- pythagoras/_020_ordinary_code_portals/function_processing.py +58 -15
- pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +14 -0
- pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +196 -24
- pythagoras/_030_data_portals/data_portal_core_classes.py +74 -22
- pythagoras/_030_data_portals/ready_and_get.py +45 -4
- pythagoras/_030_data_portals/storable_decorator.py +18 -1
- pythagoras/_040_logging_code_portals/exception_processing_tracking.py +30 -2
- pythagoras/_040_logging_code_portals/execution_environment_summary.py +60 -24
- pythagoras/_040_logging_code_portals/kw_args.py +74 -12
- pythagoras/_040_logging_code_portals/logging_decorator.py +23 -1
- pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +365 -12
- pythagoras/_040_logging_code_portals/notebook_checker.py +9 -1
- pythagoras/_040_logging_code_portals/uncaught_exceptions.py +40 -0
- pythagoras/_050_safe_code_portals/safe_decorator.py +27 -1
- pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +87 -11
- pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +31 -4
- pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +94 -14
- pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +133 -4
- pythagoras/_070_protected_code_portals/list_flattener.py +45 -7
- pythagoras/_070_protected_code_portals/package_manager.py +99 -24
- pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +70 -0
- pythagoras/_070_protected_code_portals/system_utils.py +85 -12
- pythagoras/_070_protected_code_portals/validation_succesful_const.py +12 -7
- pythagoras/_090_swarming_portals/swarming_portals.py +4 -6
- pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +55 -20
- pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +20 -5
- pythagoras/_800_signatures_and_converters/hash_signatures.py +46 -10
- pythagoras/_800_signatures_and_converters/node_signature.py +27 -12
- pythagoras/_800_signatures_and_converters/random_signatures.py +14 -3
- {pythagoras-0.24.3.dist-info → pythagoras-0.24.6.dist-info}/METADATA +1 -1
- {pythagoras-0.24.3.dist-info → pythagoras-0.24.6.dist-info}/RECORD +33 -33
- {pythagoras-0.24.3.dist-info → pythagoras-0.24.6.dist-info}/WHEEL +0 -0
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
_is_in_notebook: bool|None = None
|
|
3
3
|
|
|
4
4
|
def is_executed_in_notebook() -> bool:
|
|
5
|
-
"""
|
|
5
|
+
"""Return whether code is running inside a Jupyter/IPython notebook.
|
|
6
|
+
|
|
7
|
+
Uses a lightweight heuristic: checks if IPython is present and whether the
|
|
8
|
+
current shell exposes the set_custom_exc attribute, which is specific to
|
|
9
|
+
IPython interactive environments (including Jupyter).
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
bool: True if running inside a Jupyter/IPython notebook, False otherwise.
|
|
13
|
+
"""
|
|
6
14
|
global _is_in_notebook
|
|
7
15
|
if _is_in_notebook is not None:
|
|
8
16
|
return _is_in_notebook
|
|
@@ -16,6 +16,17 @@ from .._800_signatures_and_converters.random_signatures import (
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def pth_excepthook(exc_type, exc_value, trace_back) -> None:
|
|
19
|
+
"""sys.excepthook replacement that logs uncaught exceptions.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
exc_type: The exception class.
|
|
23
|
+
exc_value: The exception instance.
|
|
24
|
+
trace_back: Traceback object for the exception.
|
|
25
|
+
|
|
26
|
+
Side Effects:
|
|
27
|
+
- Records the exception in the active LoggingCodePortal's crash history.
|
|
28
|
+
- Calls the original sys.__excepthook__ after logging.
|
|
29
|
+
"""
|
|
19
30
|
if _exception_needs_to_be_processed(exc_type, exc_value, trace_back):
|
|
20
31
|
exception_id = "app_"+ get_random_signature() + "_crash"
|
|
21
32
|
event_body = add_execution_environment_summary(
|
|
@@ -30,6 +41,17 @@ def pth_excepthook(exc_type, exc_value, trace_back) -> None:
|
|
|
30
41
|
|
|
31
42
|
def pth_excepthandler(_, exc_type, exc_value
|
|
32
43
|
, trace_back, tb_offset=None) -> None:
|
|
44
|
+
"""IPython custom exception handler that logs uncaught exceptions.
|
|
45
|
+
|
|
46
|
+
This signature matches IPython's set_custom_exc handler protocol.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
_: Unused first parameter required by IPython.
|
|
50
|
+
exc_type: The exception class.
|
|
51
|
+
exc_value: The exception instance.
|
|
52
|
+
trace_back: Traceback object for the exception.
|
|
53
|
+
tb_offset: Optional traceback offset used by IPython. Unused.
|
|
54
|
+
"""
|
|
33
55
|
if _exception_needs_to_be_processed(exc_type, exc_value, trace_back):
|
|
34
56
|
exception_id = "app_" + get_random_signature() + "_crash"
|
|
35
57
|
event_body = add_execution_environment_summary(
|
|
@@ -45,6 +67,15 @@ _previous_excepthook = None
|
|
|
45
67
|
_number_of_handlers_registrations = 0
|
|
46
68
|
|
|
47
69
|
def register_systemwide_uncaught_exception_handlers() -> None:
|
|
70
|
+
"""Install Pythagoras handlers for uncaught exceptions system-wide.
|
|
71
|
+
|
|
72
|
+
In standard Python, replaces sys.excepthook; in Jupyter/IPython,
|
|
73
|
+
registers a custom exception handler via IPython.set_custom_exc when
|
|
74
|
+
available. Multiple registrations are reference-counted and idempotent.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
None
|
|
78
|
+
"""
|
|
48
79
|
global _number_of_handlers_registrations, _previous_excepthook
|
|
49
80
|
_number_of_handlers_registrations += 1
|
|
50
81
|
if _number_of_handlers_registrations > 1:
|
|
@@ -64,6 +95,15 @@ def register_systemwide_uncaught_exception_handlers() -> None:
|
|
|
64
95
|
|
|
65
96
|
|
|
66
97
|
def unregister_systemwide_uncaught_exception_handlers() -> None:
|
|
98
|
+
"""Uninstall previously registered Pythagoras exception handlers.
|
|
99
|
+
|
|
100
|
+
Decrements the registration reference counter. When it reaches zero,
|
|
101
|
+
restores the previous sys.excepthook (if any) and removes the custom
|
|
102
|
+
IPython exception handler in notebook environments.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
None
|
|
106
|
+
"""
|
|
67
107
|
global _number_of_handlers_registrations, _previous_excepthook
|
|
68
108
|
_number_of_handlers_registrations -= 1
|
|
69
109
|
if _number_of_handlers_registrations > 0:
|
|
@@ -7,12 +7,29 @@ from .safe_portal_core_classes import SafeFn, SafeCodePortal
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class safe(logging):
|
|
10
|
-
"""
|
|
10
|
+
"""Decorator that wraps a callable into a SafeFn for portal execution.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
@safe()
|
|
14
|
+
def my_fn(x: int) -> int:
|
|
15
|
+
return x + 1
|
|
16
|
+
|
|
17
|
+
Notes:
|
|
18
|
+
This decorator configures only logging-related behavior for now. Actual
|
|
19
|
+
safety/sandboxing is not yet implemented.
|
|
11
20
|
"""
|
|
12
21
|
|
|
13
22
|
def __init__(self
|
|
14
23
|
, excessive_logging: bool|None|Joker = KEEP_CURRENT
|
|
15
24
|
, portal: SafeCodePortal | None = None):
|
|
25
|
+
"""Create a safe decorator bound to an optional portal.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
excessive_logging: Whether to enable verbose logging for wrapped
|
|
29
|
+
calls. KEEP_CURRENT inherits from current context.
|
|
30
|
+
portal: The SafeCodePortal to attach the resulting SafeFn to. If
|
|
31
|
+
None, the active portal (if any) may be used by lower layers.
|
|
32
|
+
"""
|
|
16
33
|
assert isinstance(portal, SafeCodePortal) or portal is None
|
|
17
34
|
logging.__init__(self=self
|
|
18
35
|
, portal=portal
|
|
@@ -20,6 +37,15 @@ class safe(logging):
|
|
|
20
37
|
|
|
21
38
|
|
|
22
39
|
def __call__(self,fn:Callable)->SafeFn:
|
|
40
|
+
"""Wrap a Python callable into a SafeFn.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
fn: The function to wrap.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
SafeFn: The wrapped function that can be executed via a portal and
|
|
47
|
+
will record logging information.
|
|
48
|
+
"""
|
|
23
49
|
wrapper = SafeFn(fn
|
|
24
50
|
, portal=self._portal
|
|
25
51
|
, excessive_logging=self._excessive_logging)
|
|
@@ -13,20 +13,43 @@ It will be done soon by integrating https://pypi.org/project/RestrictedPython/
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
from typing import Callable
|
|
17
|
-
|
|
18
|
-
# from parameterizable import register_parameterizable_class
|
|
19
|
-
from persidict import PersiDict, Joker, KEEP_CURRENT
|
|
20
|
-
|
|
21
16
|
from .._040_logging_code_portals.logging_portal_core_classes import *
|
|
22
17
|
|
|
23
18
|
|
|
24
19
|
class SafeCodePortal(LoggingCodePortal):
|
|
20
|
+
"""A portal that executes functions with logging under a "safe" contract.
|
|
21
|
+
|
|
22
|
+
Currently, SafeCodePortal inherits all behavior from LoggingCodePortal.
|
|
23
|
+
True sandboxing/isolation has not yet been implemented and will be
|
|
24
|
+
introduced in the future (see Notes).
|
|
25
|
+
|
|
26
|
+
Notes:
|
|
27
|
+
The actual safety guarantees (prohibiting access to external
|
|
28
|
+
filesystem, network, process state, etc.) are not yet enforced.
|
|
29
|
+
The plan is to integrate RestrictedPython to implement a proper
|
|
30
|
+
sandbox. Until then, SafeCodePortal behaves like LoggingCodePortal
|
|
31
|
+
but keeps the API intended for safe execution.
|
|
32
|
+
"""
|
|
33
|
+
|
|
25
34
|
def __init__(self
|
|
26
35
|
, root_dict: PersiDict|str|None = None
|
|
27
36
|
, p_consistency_checks: float|Joker = KEEP_CURRENT
|
|
28
37
|
, excessive_logging: bool|Joker = KEEP_CURRENT
|
|
29
38
|
):
|
|
39
|
+
"""Initialize a SafeCodePortal.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
root_dict: Root persistent dictionary or its path used by the
|
|
43
|
+
underlying data portal for storing execution artifacts. If a
|
|
44
|
+
string is provided, it is treated as a path on disk. If None,
|
|
45
|
+
an in-memory structure may be used (depending on configuration).
|
|
46
|
+
p_consistency_checks: Probability in [0, 1] of running additional
|
|
47
|
+
consistency checks on persistent state mutations. Use
|
|
48
|
+
KEEP_CURRENT to inherit the active setting from parent context.
|
|
49
|
+
excessive_logging: Whether to enable verbose logging of execution
|
|
50
|
+
attempts, results, outputs and events. Use KEEP_CURRENT to
|
|
51
|
+
inherit the active setting from parent context.
|
|
52
|
+
"""
|
|
30
53
|
LoggingCodePortal.__init__(self
|
|
31
54
|
, root_dict=root_dict
|
|
32
55
|
, p_consistency_checks=p_consistency_checks
|
|
@@ -34,26 +57,60 @@ class SafeCodePortal(LoggingCodePortal):
|
|
|
34
57
|
|
|
35
58
|
|
|
36
59
|
class SafeFnCallSignature(LoggingFnCallSignature):
|
|
37
|
-
"""A signature
|
|
60
|
+
"""A signature object describing a call to a SafeFn.
|
|
61
|
+
|
|
62
|
+
This specialization only narrows the types returned by some accessors
|
|
63
|
+
(e.g., fn) to Safe* counterparts. All logging behavior and storage layout
|
|
64
|
+
are inherited from LoggingFnCallSignature.
|
|
65
|
+
"""
|
|
38
66
|
_fn_cache: SafeFn | None
|
|
39
67
|
|
|
40
68
|
def __init__(self, fn: SafeFn, arguments: dict):
|
|
69
|
+
"""Construct a signature for a specific SafeFn call.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
fn: The safe function object to be called.
|
|
73
|
+
arguments: The keyword arguments to use for the call.
|
|
74
|
+
"""
|
|
41
75
|
assert isinstance(fn, SafeFn)
|
|
42
76
|
assert isinstance(arguments, dict)
|
|
43
77
|
super().__init__(fn, arguments)
|
|
44
78
|
|
|
45
79
|
@property
|
|
46
80
|
def fn(self) -> SafeFn:
|
|
47
|
-
"""Return the
|
|
81
|
+
"""Return the SafeFn referenced by this signature.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
SafeFn: The underlying safe function instance.
|
|
85
|
+
"""
|
|
48
86
|
return super().fn
|
|
49
87
|
|
|
50
88
|
|
|
51
89
|
class SafeFn(LoggingFn):
|
|
90
|
+
"""A function wrapper intended for safe execution within a portal.
|
|
91
|
+
|
|
92
|
+
SafeFn currently behaves like LoggingFn, adding only type-narrowed
|
|
93
|
+
accessors for convenience. Future versions will enforce sandboxed
|
|
94
|
+
execution (see Notes).
|
|
95
|
+
|
|
96
|
+
Notes:
|
|
97
|
+
No actual sandboxing is performed yet. Behavior equals LoggingFn.
|
|
98
|
+
"""
|
|
99
|
+
|
|
52
100
|
def __init__(self
|
|
53
101
|
, fn: Callable|str
|
|
54
102
|
, portal: LoggingCodePortal|None = None
|
|
55
103
|
, excessive_logging: bool|Joker = KEEP_CURRENT
|
|
56
104
|
):
|
|
105
|
+
"""Create a SafeFn wrapper.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
fn: The Python callable to wrap or its import string.
|
|
109
|
+
portal: The portal to associate with this function. If None, the
|
|
110
|
+
active portal (if any) may be used by the underlying layers.
|
|
111
|
+
excessive_logging: Whether to enable verbose logging for this fn.
|
|
112
|
+
Use KEEP_CURRENT to inherit from the surrounding context.
|
|
113
|
+
"""
|
|
57
114
|
LoggingFn.__init__(self
|
|
58
115
|
, fn = fn
|
|
59
116
|
, portal=portal
|
|
@@ -61,23 +118,42 @@ class SafeFn(LoggingFn):
|
|
|
61
118
|
|
|
62
119
|
|
|
63
120
|
def __getstate__(self):
|
|
64
|
-
"""
|
|
121
|
+
"""Return picklable state.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
dict: The state returned by the parent LoggingFn for pickling.
|
|
125
|
+
"""
|
|
65
126
|
state = super().__getstate__()
|
|
66
127
|
return state
|
|
67
128
|
|
|
68
129
|
|
|
69
130
|
def __setstate__(self, state):
|
|
70
|
-
"""
|
|
131
|
+
"""Restore object state after unpickling.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
state: The state previously produced by __getstate__.
|
|
135
|
+
"""
|
|
71
136
|
super().__setstate__(state)
|
|
72
137
|
|
|
73
138
|
|
|
74
139
|
@property
|
|
75
140
|
def portal(self) -> SafeCodePortal:
|
|
141
|
+
"""Return the associated SafeCodePortal.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
SafeCodePortal: The portal that owns this function.
|
|
145
|
+
"""
|
|
76
146
|
return super().portal
|
|
77
147
|
|
|
78
148
|
|
|
79
149
|
def get_signature(self, arguments:dict) -> SafeFnCallSignature:
|
|
80
|
-
|
|
150
|
+
"""Create a SafeFnCallSignature for a given call.
|
|
81
151
|
|
|
152
|
+
Args:
|
|
153
|
+
arguments: The keyword arguments for the call.
|
|
82
154
|
|
|
83
|
-
|
|
155
|
+
Returns:
|
|
156
|
+
SafeFnCallSignature: A typed call signature suitable for execution
|
|
157
|
+
and logging through the portal.
|
|
158
|
+
"""
|
|
159
|
+
return SafeFnCallSignature(fn=self, arguments=arguments)
|
|
@@ -43,11 +43,16 @@ from persidict import Joker, KEEP_CURRENT
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class autonomous(safe):
|
|
46
|
-
"""Decorator
|
|
46
|
+
"""Decorator that turns a regular function into an autonomous one.
|
|
47
47
|
|
|
48
|
-
An autonomous function is
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
An autonomous function is a self-contained function: it
|
|
49
|
+
can only use built-ins and any names it imports inside its own body. This
|
|
50
|
+
decorator wraps the target callable into an AutonomousFn and enforces both
|
|
51
|
+
static and runtime autonomy checks via the selected portal.
|
|
52
|
+
|
|
53
|
+
Notes:
|
|
54
|
+
- Only regular (non-async) functions are supported.
|
|
55
|
+
- Methods, closures, lambdas, and coroutines are not considered autonomous.
|
|
51
56
|
"""
|
|
52
57
|
_fixed_args: dict|None
|
|
53
58
|
|
|
@@ -56,6 +61,19 @@ class autonomous(safe):
|
|
|
56
61
|
, excessive_logging: bool|Joker = KEEP_CURRENT
|
|
57
62
|
, portal: AutonomousCodePortal | None = None
|
|
58
63
|
):
|
|
64
|
+
"""Initialize the decorator.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
fixed_kwargs: Keyword arguments to pre-bind (partially apply) to the
|
|
68
|
+
decorated function. These will be merged into every call.
|
|
69
|
+
excessive_logging: If True, enables verbose logging within the
|
|
70
|
+
selected portal. KEEP_CURRENT leaves the portal's setting as-is.
|
|
71
|
+
portal: Portal instance to use for autonomy and safety checks.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
AssertionError: If portal is not an AutonomousCodePortal or None, or
|
|
75
|
+
if fixed_kwargs is not a dict or None.
|
|
76
|
+
"""
|
|
59
77
|
assert isinstance(portal, AutonomousCodePortal) or portal is None
|
|
60
78
|
assert isinstance(fixed_kwargs, dict) or fixed_kwargs is None
|
|
61
79
|
safe.__init__(self=self
|
|
@@ -65,6 +83,15 @@ class autonomous(safe):
|
|
|
65
83
|
|
|
66
84
|
|
|
67
85
|
def __call__(self, fn: Callable|str) -> AutonomousFn:
|
|
86
|
+
"""Wrap the function with autonomy enforcement.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
fn: The function object to decorate.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
AutonomousFn: A wrapper that enforces autonomy at decoration and at
|
|
93
|
+
execution time, with any fixed keyword arguments pre-applied.
|
|
94
|
+
"""
|
|
68
95
|
wrapper = AutonomousFn(fn
|
|
69
96
|
,portal=self._portal
|
|
70
97
|
,fixed_kwargs=self._fixed_kwargs
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import builtins
|
|
4
|
-
from typing import Callable, Any
|
|
5
|
-
|
|
6
|
-
from persidict import PersiDict, Joker, KEEP_CURRENT
|
|
7
|
-
|
|
8
|
-
from .._010_basic_portals import PortalAwareClass
|
|
9
|
-
from .._010_basic_portals.basic_portal_core_classes import _visit_portal
|
|
10
4
|
from .._020_ordinary_code_portals.code_normalizer import _pythagoras_decorator_names
|
|
11
|
-
from .._030_data_portals import DataPortal
|
|
12
5
|
from .._040_logging_code_portals import KwArgs
|
|
13
6
|
|
|
14
7
|
from .._060_autonomous_code_portals.names_usage_analyzer import (
|
|
@@ -17,12 +10,27 @@ from .._060_autonomous_code_portals.names_usage_analyzer import (
|
|
|
17
10
|
from .._050_safe_code_portals.safe_portal_core_classes import *
|
|
18
11
|
|
|
19
12
|
class AutonomousCodePortal(SafeCodePortal):
|
|
20
|
-
|
|
13
|
+
"""Portal configured for enforcing autonomy constraints.
|
|
14
|
+
|
|
15
|
+
This portal behaves like SafeCodePortal but is specialized for autonomous
|
|
16
|
+
functions. It controls logging and consistency checks for operations related
|
|
17
|
+
to AutonomousFn instances.
|
|
18
|
+
"""
|
|
21
19
|
def __init__(self
|
|
22
20
|
, root_dict: PersiDict | str | None = None
|
|
23
21
|
, p_consistency_checks: float | Joker = KEEP_CURRENT
|
|
24
22
|
, excessive_logging: bool|Joker = KEEP_CURRENT
|
|
25
23
|
):
|
|
24
|
+
"""Create an autonomous code portal.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
root_dict: Persistence root backing the portal state. Can be a
|
|
28
|
+
PersiDict instance, a path string, or None for defaults.
|
|
29
|
+
p_consistency_checks: Probability [0..1] to run extra consistency
|
|
30
|
+
checks on operations. KEEP_CURRENT uses the existing setting.
|
|
31
|
+
excessive_logging: Whether to enable verbose logging. KEEP_CURRENT
|
|
32
|
+
preserves the existing portal setting.
|
|
33
|
+
"""
|
|
26
34
|
SafeCodePortal.__init__(self
|
|
27
35
|
, root_dict=root_dict
|
|
28
36
|
, p_consistency_checks=p_consistency_checks
|
|
@@ -30,10 +38,19 @@ class AutonomousCodePortal(SafeCodePortal):
|
|
|
30
38
|
|
|
31
39
|
|
|
32
40
|
class AutonomousFnCallSignature(SafeFnCallSignature):
|
|
33
|
-
"""A signature of a call to an autonomous function
|
|
41
|
+
"""A signature of a call to an autonomous function.
|
|
42
|
+
|
|
43
|
+
This extends SafeFnCallSignature to reference AutonomousFn instances.
|
|
44
|
+
"""
|
|
34
45
|
_fn_cache: AutonomousFn | None
|
|
35
46
|
|
|
36
47
|
def __init__(self, fn: AutonomousFn, arguments: dict):
|
|
48
|
+
"""Create a call signature for an autonomous function.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
fn: The autonomous function being called.
|
|
52
|
+
arguments: The call-time arguments mapping (already validated).
|
|
53
|
+
"""
|
|
37
54
|
assert isinstance(fn, AutonomousFn)
|
|
38
55
|
assert isinstance(arguments, dict)
|
|
39
56
|
super().__init__(fn, arguments)
|
|
@@ -45,7 +62,13 @@ class AutonomousFnCallSignature(SafeFnCallSignature):
|
|
|
45
62
|
|
|
46
63
|
|
|
47
64
|
class AutonomousFn(SafeFn):
|
|
65
|
+
"""A SafeFn wrapper that enforces function autonomy rules.
|
|
48
66
|
|
|
67
|
+
AutonomousFn performs static validation at construction time to ensure that
|
|
68
|
+
the wrapped function uses only built-ins or names imported inside its body,
|
|
69
|
+
has no yield statements, and does not reference nonlocal variables.
|
|
70
|
+
It also supports partial application via fixed keyword arguments.
|
|
71
|
+
"""
|
|
49
72
|
_fixed_kwargs_cache: KwArgs | None
|
|
50
73
|
_fixed_kwargs_packed: KwArgs | None
|
|
51
74
|
|
|
@@ -53,6 +76,20 @@ class AutonomousFn(SafeFn):
|
|
|
53
76
|
, fixed_kwargs: dict[str,Any]|None = None
|
|
54
77
|
, excessive_logging: bool|Joker = KEEP_CURRENT
|
|
55
78
|
, portal: AutonomousCodePortal|None = None):
|
|
79
|
+
"""Construct an AutonomousFn and validate autonomy constraints.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
fn: The function object, a string with the function's source code,
|
|
83
|
+
or an existing SafeFn to wrap. If an AutonomousFn is provided,
|
|
84
|
+
fixed_kwargs are merged.
|
|
85
|
+
fixed_kwargs: Keyword arguments to pre-bind (partially apply).
|
|
86
|
+
excessive_logging: Verbose logging flag or KEEP_CURRENT.
|
|
87
|
+
portal: AutonomousCodePortal to use; may be None to defer.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
AssertionError: If static analysis detects violations of autonomy
|
|
91
|
+
(nonlocal/global unbound names, missing imports, or yield usage).
|
|
92
|
+
"""
|
|
56
93
|
super().__init__(fn=fn
|
|
57
94
|
, portal = portal
|
|
58
95
|
, excessive_logging = excessive_logging)
|
|
@@ -105,6 +142,11 @@ class AutonomousFn(SafeFn):
|
|
|
105
142
|
|
|
106
143
|
@property
|
|
107
144
|
def fixed_kwargs(self) -> KwArgs:
|
|
145
|
+
"""KwArgs of pre-bound keyword arguments for this function.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
KwArgs: The fixed keyword arguments.
|
|
149
|
+
"""
|
|
108
150
|
if not hasattr(self, "_fixed_kwargs_cache"):
|
|
109
151
|
with self.portal:
|
|
110
152
|
self._fixed_kwargs_cache = self._fixed_kwargs_packed.unpack()
|
|
@@ -112,6 +154,19 @@ class AutonomousFn(SafeFn):
|
|
|
112
154
|
|
|
113
155
|
|
|
114
156
|
def execute(self, **kwargs) -> Any:
|
|
157
|
+
"""Execute the function within the portal, applying fixed kwargs.
|
|
158
|
+
|
|
159
|
+
Any kwargs provided here must not overlap with pre-bound fixed kwargs.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
**kwargs: Call-time keyword arguments.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Any: Result of the wrapped function call.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
AssertionError: If provided kwargs overlap with fixed kwargs.
|
|
169
|
+
"""
|
|
115
170
|
with self.portal:
|
|
116
171
|
overlapping_keys = set(kwargs.keys()) & set(self.fixed_kwargs.keys())
|
|
117
172
|
assert len(overlapping_keys) == 0
|
|
@@ -120,16 +175,33 @@ class AutonomousFn(SafeFn):
|
|
|
120
175
|
|
|
121
176
|
|
|
122
177
|
def get_signature(self, arguments:dict) -> AutonomousFnCallSignature:
|
|
178
|
+
"""Build a call signature object for this function.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
arguments: Mapping of argument names to values for this call.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
AutonomousFnCallSignature: The signature representing this call.
|
|
185
|
+
"""
|
|
123
186
|
return AutonomousFnCallSignature(fn=self, arguments=arguments)
|
|
124
187
|
|
|
125
188
|
|
|
126
189
|
def fix_kwargs(self, **kwargs) -> AutonomousFn:
|
|
127
|
-
"""Create a new function
|
|
190
|
+
"""Create a new autonomous function with some kwargs pre-filled.
|
|
128
191
|
|
|
129
|
-
This is
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
192
|
+
This is partial application: it creates a function with fewer parameters
|
|
193
|
+
by fixing a subset of keyword arguments.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
**kwargs: Keyword arguments to fix for the new function.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
AutonomousFn: A new wrapper that will always apply the provided
|
|
200
|
+
keyword arguments in addition to already fixed ones.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
AssertionError: If any of the provided kwargs overlap with already
|
|
204
|
+
fixed kwargs.
|
|
133
205
|
"""
|
|
134
206
|
|
|
135
207
|
overlapping_keys = set(kwargs.keys()) & set(self.fixed_kwargs.keys())
|
|
@@ -140,6 +212,13 @@ class AutonomousFn(SafeFn):
|
|
|
140
212
|
|
|
141
213
|
|
|
142
214
|
def _first_visit_to_portal(self, portal: DataPortal) -> None:
|
|
215
|
+
"""Hook called on the first visit to a data portal.
|
|
216
|
+
|
|
217
|
+
Ensures that fixed kwargs are materialized (packed) within the portal.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
portal: The data portal being visited for the first time.
|
|
221
|
+
"""
|
|
143
222
|
super()._first_visit_to_portal(portal)
|
|
144
223
|
with portal:
|
|
145
224
|
_ = self.fixed_kwargs.pack()
|
|
@@ -160,6 +239,7 @@ class AutonomousFn(SafeFn):
|
|
|
160
239
|
|
|
161
240
|
@property
|
|
162
241
|
def portal(self) -> AutonomousCodePortal:
|
|
242
|
+
"""Return the autonomous portal associated with this function."""
|
|
163
243
|
return super().portal
|
|
164
244
|
|
|
165
245
|
|