pythagoras 0.24.2__py3-none-any.whl → 0.24.4__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/.DS_Store +0 -0
- pythagoras/_010_basic_portals/__init__.py +0 -1
- pythagoras/_010_basic_portals/basic_portal_core_classes.py +2 -14
- 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 +368 -15
- 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/_090_swarming_portals/swarming_portals.py +4 -6
- {pythagoras-0.24.2.dist-info → pythagoras-0.24.4.dist-info}/METADATA +3 -3
- {pythagoras-0.24.2.dist-info → pythagoras-0.24.4.dist-info}/RECORD +23 -24
- pythagoras/_010_basic_portals/not_picklable_class.py +0 -36
- {pythagoras-0.24.2.dist-info → pythagoras-0.24.4.dist-info}/WHEEL +0 -0
pythagoras/.DS_Store
CHANGED
|
Binary file
|
|
@@ -14,12 +14,11 @@ from abc import abstractmethod
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import TypeVar, Type, Any, NewType
|
|
16
16
|
import pandas as pd
|
|
17
|
-
import
|
|
17
|
+
from parameterizable import NotPicklableClass
|
|
18
18
|
from parameterizable import ParameterizableClass, sort_dict_by_keys
|
|
19
19
|
|
|
20
20
|
from persidict import PersiDict, FileDirDict, SafeStrTuple
|
|
21
21
|
from .post_init_metaclass import PostInitMeta
|
|
22
|
-
from .not_picklable_class import NotPicklable
|
|
23
22
|
from .._800_signatures_and_converters import get_hash_signature
|
|
24
23
|
|
|
25
24
|
def _describe_persistent_characteristic(name, value) -> pd.DataFrame:
|
|
@@ -205,7 +204,7 @@ def get_default_portal_base_dir() -> str:
|
|
|
205
204
|
return target_directory_str
|
|
206
205
|
|
|
207
206
|
|
|
208
|
-
class BasicPortal(
|
|
207
|
+
class BasicPortal(NotPicklableClass,ParameterizableClass, metaclass = PostInitMeta):
|
|
209
208
|
"""A base class for portal objects that enable access to 'outside' world.
|
|
210
209
|
|
|
211
210
|
In a Pythagoras-based application, a portal is the application's 'window'
|
|
@@ -359,17 +358,6 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
|
|
|
359
358
|
return sorted_params
|
|
360
359
|
|
|
361
360
|
|
|
362
|
-
# @property
|
|
363
|
-
# def auxiliary_param_names(self) -> set[str]:
|
|
364
|
-
# """Get the names of the portal's ephemeral parameters.
|
|
365
|
-
#
|
|
366
|
-
# Portal's ephemeral parameters are not stored persistently.
|
|
367
|
-
# They affect behaviour of a portal object in an application session,
|
|
368
|
-
# but they do not affect behaviour of the actual portal across multiple runs.
|
|
369
|
-
# """
|
|
370
|
-
# return set()
|
|
371
|
-
|
|
372
|
-
|
|
373
361
|
@property
|
|
374
362
|
def _str_id(self) -> PortalStrID:
|
|
375
363
|
"""Get the portal's persistent hash.
|
|
@@ -21,16 +21,44 @@ _pythagoras_decorator_names = {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
def _get_normalized_function_source_impl(
|
|
24
|
-
a_func: Callable | str
|
|
25
|
-
|
|
24
|
+
a_func: Callable | str,
|
|
25
|
+
drop_pth_decorators: bool = False,
|
|
26
26
|
) -> str:
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
"""Produce a normalized representation of a function's source code.
|
|
28
|
+
|
|
29
|
+
The normalization process performs the following steps:
|
|
30
|
+
1) Accepts either a callable or a string of source code. If a callable
|
|
31
|
+
is provided, it must be an ordinary function (validated) and its
|
|
32
|
+
source is retrieved with inspect.getsource.
|
|
33
|
+
2) Removes empty lines and normalizes indentation for functions defined
|
|
34
|
+
inside other scopes so that parsing can succeed.
|
|
35
|
+
3) Parses the code with ast.parse and validates there is exactly one
|
|
36
|
+
top-level function definition.
|
|
37
|
+
4) Optionally drops a single Pythagoras decorator (if present) when
|
|
38
|
+
drop_pth_decorators=True. Only known Pythagoras decorator names are
|
|
39
|
+
removed; any other decorator remains.
|
|
40
|
+
5) Strips all docstrings, return/type annotations, and variable
|
|
41
|
+
annotations from the AST. For nodes that become empty as a result,
|
|
42
|
+
a "pass" is inserted to keep the code valid.
|
|
43
|
+
6) Unparses the AST back to code and runs autopep8 to enforce PEP 8
|
|
44
|
+
formatting.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
a_func: The target function or a string containing its source code.
|
|
48
|
+
drop_pth_decorators: If True, remove a single known Pythagoras
|
|
49
|
+
decorator (e.g., @ordinary) from the function, if present.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A normalized source code string for the function.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
NonCompliantFunction: If the function has multiple decorators when
|
|
56
|
+
a callable or a string representing a single function is
|
|
57
|
+
expected; or if it fails ordinarity checks.
|
|
58
|
+
AssertionError: If input is neither a callable nor a string, if
|
|
59
|
+
parsing assumptions fail (e.g., unexpected AST node types), or
|
|
60
|
+
when internal integrity checks do not hold.
|
|
61
|
+
SyntaxError: If the provided source string cannot be parsed.
|
|
34
62
|
"""
|
|
35
63
|
|
|
36
64
|
a_func_name, code = None, ""
|
|
@@ -6,7 +6,27 @@ from .._010_basic_portals.long_infoname import get_long_infoname
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def get_function_name_from_source(function_source_code: str) -> str:
|
|
9
|
-
"""
|
|
9
|
+
"""Extract the function name from a source code snippet.
|
|
10
|
+
|
|
11
|
+
This parser scans the source code line-by-line and returns the name
|
|
12
|
+
from the first line that starts with "def ". It assumes a standard
|
|
13
|
+
function definition like:
|
|
14
|
+
|
|
15
|
+
def my_func(arg1, arg2):
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
It does not currently detect async functions defined with "async def",
|
|
19
|
+
and it will raise an error if no valid definition is found.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
function_source_code: The source code containing a function definition.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The function name as it appears before the opening parenthesis.
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
ValueError: If no function definition line is found in the input.
|
|
29
|
+
"""
|
|
10
30
|
lines = function_source_code.split('\n')
|
|
11
31
|
for line in lines:
|
|
12
32
|
stripped_line = line.strip()
|
|
@@ -18,11 +38,17 @@ def get_function_name_from_source(function_source_code: str) -> str:
|
|
|
18
38
|
|
|
19
39
|
|
|
20
40
|
def accepts_unlimited_positional_args(func: Callable) -> bool:
|
|
21
|
-
"""
|
|
41
|
+
"""Return True if the function accepts arbitrary positional args.
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
The check is based on inspect.signature and looks for a parameter of
|
|
44
|
+
kind VAR_POSITIONAL (i.e., a "*args" parameter).
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
func: The callable to inspect.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True if the function defines a VAR_POSITIONAL ("*args") parameter;
|
|
51
|
+
False otherwise.
|
|
26
52
|
"""
|
|
27
53
|
|
|
28
54
|
signature = inspect.signature(func)
|
|
@@ -33,7 +59,14 @@ def accepts_unlimited_positional_args(func: Callable) -> bool:
|
|
|
33
59
|
|
|
34
60
|
|
|
35
61
|
def count_parameters_with_defaults(func: Callable) -> int:
|
|
36
|
-
"""
|
|
62
|
+
"""Count parameters that have default values in the function signature.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
func: The callable to inspect.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The number of parameters with default values.
|
|
69
|
+
"""
|
|
37
70
|
signature = inspect.signature(func)
|
|
38
71
|
return sum(
|
|
39
72
|
param.default is not param.empty
|
|
@@ -41,18 +74,28 @@ def count_parameters_with_defaults(func: Callable) -> int:
|
|
|
41
74
|
)
|
|
42
75
|
|
|
43
76
|
|
|
44
|
-
def assert_ordinarity(a_func:Callable) -> None:
|
|
45
|
-
"""
|
|
77
|
+
def assert_ordinarity(a_func: Callable) -> None:
|
|
78
|
+
"""Validate that a callable complies with Pythagoras "ordinary" rules.
|
|
79
|
+
|
|
80
|
+
In Pythagoras, an "ordinary" function is a regular Python function that:
|
|
81
|
+
- is a plain function object (not a bound/unbound method, classmethod,
|
|
82
|
+
staticmethod object, lambda, closure, coroutine, or built-in), and
|
|
83
|
+
- does not accept unlimited positional arguments (no "*args"), and
|
|
84
|
+
- has no parameters with default values.
|
|
46
85
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
86
|
+
Notes:
|
|
87
|
+
- Static methods: The handling of static methods is undecided; for now they
|
|
88
|
+
are treated the same as regular functions only if provided directly as a
|
|
89
|
+
function object. If a function fails any of the checks below, an error is
|
|
90
|
+
raised.
|
|
50
91
|
|
|
51
|
-
|
|
52
|
-
|
|
92
|
+
Args:
|
|
93
|
+
a_func: The callable to validate.
|
|
53
94
|
|
|
54
|
-
|
|
55
|
-
|
|
95
|
+
Raises:
|
|
96
|
+
NonCompliantFunction: If the callable violates any of the ordinarity
|
|
97
|
+
constraints described above (e.g., not a function, is a method,
|
|
98
|
+
is a lambda/closure/async function, accepts *args, or has defaults).
|
|
56
99
|
"""
|
|
57
100
|
|
|
58
101
|
# TODO: decide how to handle static methods
|
|
@@ -14,11 +14,25 @@ class ordinary:
|
|
|
14
14
|
_portal: OrdinaryCodePortal | None
|
|
15
15
|
|
|
16
16
|
def __init__(self, portal: OrdinaryCodePortal | None = None):
|
|
17
|
+
"""Initialize the decorator.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
portal: Optional OrdinaryCodePortal to link to the resulting
|
|
21
|
+
OrdinaryFn wrappers.
|
|
22
|
+
"""
|
|
17
23
|
assert portal is None or isinstance(portal, OrdinaryCodePortal)
|
|
18
24
|
self._portal=portal
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
def __call__(self,fn:Callable)->OrdinaryFn:
|
|
28
|
+
"""Wrap a callable and return its OrdinaryFn representation.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
fn: The function to convert into an OrdinaryFn.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
OrdinaryFn: The wrapper around the provided function.
|
|
35
|
+
"""
|
|
22
36
|
wrapper = OrdinaryFn(fn, portal=self._portal)
|
|
23
37
|
return wrapper
|
|
24
38
|
|
|
@@ -15,13 +15,27 @@ from .._010_basic_portals.basic_portal_core_classes import (
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_normalized_function_source(a_func: OrdinaryFn | Callable | str) -> str:
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
"""Get the normalized source code for a function or OrdinaryFn.
|
|
19
|
+
|
|
20
|
+
This is a convenience wrapper around the internal normalizer that:
|
|
21
|
+
- Accepts either an OrdinaryFn instance, a regular callable, or a string of
|
|
22
|
+
source code containing a single function definition.
|
|
23
|
+
- Returns a normalized string representing the function's source code, with
|
|
24
|
+
comments, docstrings, type annotations, and empty lines removed and the
|
|
25
|
+
result formatted per PEP 8.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
a_func: An OrdinaryFn instance, a Python callable, or a string with the
|
|
29
|
+
function's source code.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
The normalized source code string.
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
NonCompliantFunction: If the function is not compliant with Pythagoras'
|
|
36
|
+
ordinarity rules or multiple decorators are present.
|
|
37
|
+
AssertionError: If input type is invalid or integrity checks fail.
|
|
38
|
+
SyntaxError: If the provided source cannot be parsed.
|
|
25
39
|
"""
|
|
26
40
|
|
|
27
41
|
if isinstance(a_func, OrdinaryFn):
|
|
@@ -34,10 +48,22 @@ def get_normalized_function_source(a_func: OrdinaryFn | Callable | str) -> str:
|
|
|
34
48
|
REGISTERED_FUNCTIONS_TXT = "Registered functions"
|
|
35
49
|
|
|
36
50
|
class OrdinaryCodePortal(BasicPortal):
|
|
51
|
+
"""Portal that manages OrdinaryFn instances and their runtime context.
|
|
52
|
+
|
|
53
|
+
The portal is responsible for tracking linked OrdinaryFn objects and
|
|
54
|
+
providing a context manager used during execution. It extends BasicPortal
|
|
55
|
+
with convenience methods specific to ordinary functions.
|
|
56
|
+
"""
|
|
37
57
|
|
|
38
58
|
def __init__(self
|
|
39
59
|
, root_dict: PersiDict | str | None = None
|
|
40
60
|
):
|
|
61
|
+
"""Initialize the portal.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
root_dict: Optional persistence root (PersiDict or path-like string)
|
|
65
|
+
used by the underlying BasicPortal to store state.
|
|
66
|
+
"""
|
|
41
67
|
super().__init__(root_dict = root_dict)
|
|
42
68
|
|
|
43
69
|
|
|
@@ -47,7 +73,18 @@ class OrdinaryCodePortal(BasicPortal):
|
|
|
47
73
|
|
|
48
74
|
|
|
49
75
|
def _get_linked_functions_ids(self, target_class: type | None=None) -> set[str]:
|
|
50
|
-
"""
|
|
76
|
+
"""Return the set of IDs for functions linked to this portal.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
target_class: Optional subclass of OrdinaryFn to filter the results.
|
|
80
|
+
If None, OrdinaryFn is used.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A set of string IDs corresponding to linked OrdinaryFn instances.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
TypeError: If target_class is not a subclass of OrdinaryFn.
|
|
87
|
+
"""
|
|
51
88
|
if target_class is None:
|
|
52
89
|
target_class = OrdinaryFn
|
|
53
90
|
if not issubclass(target_class, OrdinaryFn):
|
|
@@ -56,7 +93,18 @@ class OrdinaryCodePortal(BasicPortal):
|
|
|
56
93
|
|
|
57
94
|
|
|
58
95
|
def get_linked_functions(self, target_class: type | None=None) -> list[OrdinaryFn]:
|
|
59
|
-
"""
|
|
96
|
+
"""Return linked OrdinaryFn instances managed by this portal.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
target_class: Optional subclass of OrdinaryFn to filter the results.
|
|
100
|
+
If None, OrdinaryFn is used.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
A list of linked OrdinaryFn instances.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
TypeError: If target_class is not a subclass of OrdinaryFn.
|
|
107
|
+
"""
|
|
60
108
|
if target_class is None:
|
|
61
109
|
target_class = OrdinaryFn
|
|
62
110
|
if not issubclass(target_class, OrdinaryFn):
|
|
@@ -65,11 +113,26 @@ class OrdinaryCodePortal(BasicPortal):
|
|
|
65
113
|
|
|
66
114
|
|
|
67
115
|
def get_number_of_linked_functions(self, target_class: type | None=None) -> int:
|
|
116
|
+
"""Return the number of OrdinaryFn objects linked to this portal.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
target_class: Optional subclass of OrdinaryFn to filter the results.
|
|
120
|
+
If None, OrdinaryFn is used.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
The number of linked functions matching the filter.
|
|
124
|
+
"""
|
|
68
125
|
return len(self._get_linked_functions_ids(target_class=target_class))
|
|
69
126
|
|
|
70
127
|
|
|
71
128
|
def describe(self) -> pd.DataFrame:
|
|
72
|
-
"""
|
|
129
|
+
"""Describe the portal's current state.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
A pandas DataFrame with runtime characteristics of the portal,
|
|
133
|
+
including the number of registered functions, combined with the
|
|
134
|
+
base portal description.
|
|
135
|
+
"""
|
|
73
136
|
all_params = [super().describe()]
|
|
74
137
|
|
|
75
138
|
all_params.append(_describe_runtime_characteristic(
|
|
@@ -110,6 +173,19 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
110
173
|
, fn: Callable | str
|
|
111
174
|
, portal: OrdinaryCodePortal | None = None
|
|
112
175
|
):
|
|
176
|
+
"""Create a new OrdinaryFn wrapper.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
fn: Either a regular callable, a string with the function's source,
|
|
180
|
+
or another OrdinaryFn instance to clone.
|
|
181
|
+
portal: Optional OrdinaryCodePortal to link to this instance.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
TypeError: If fn is neither callable, a string, nor an OrdinaryFn.
|
|
185
|
+
NonCompliantFunction: If the provided function source is not
|
|
186
|
+
compliant with Pythagoras ordinarity rules.
|
|
187
|
+
SyntaxError: If the provided source cannot be parsed.
|
|
188
|
+
"""
|
|
113
189
|
PortalAwareClass.__init__(self, portal=portal)
|
|
114
190
|
if isinstance(fn, OrdinaryFn):
|
|
115
191
|
self.__setstate__(deepcopy(fn.__getstate__()))
|
|
@@ -125,31 +201,48 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
125
201
|
|
|
126
202
|
@property
|
|
127
203
|
def portal(self) -> OrdinaryCodePortal:
|
|
128
|
-
"""
|
|
204
|
+
"""Return the portal used by this function.
|
|
205
|
+
|
|
206
|
+
It's either the function's linked portal or, if that is None, the
|
|
207
|
+
currently active portal from the ambient context.
|
|
129
208
|
|
|
130
|
-
|
|
131
|
-
|
|
209
|
+
Returns:
|
|
210
|
+
OrdinaryCodePortal: The portal to be used by this object.
|
|
132
211
|
"""
|
|
133
212
|
return super().portal
|
|
134
213
|
|
|
135
214
|
|
|
136
215
|
@property
|
|
137
216
|
def source_code(self) -> str:
|
|
138
|
-
"""Get the source code of the function.
|
|
217
|
+
"""Get the normalized source code of the function.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
str: The function source after normalization (no comments,
|
|
221
|
+
docstrings, annotations; PEP 8 formatting).
|
|
222
|
+
"""
|
|
139
223
|
return self._source_code
|
|
140
224
|
|
|
141
225
|
|
|
142
226
|
@property
|
|
143
227
|
def name(self) -> str:
|
|
144
|
-
"""Get the name of the function.
|
|
228
|
+
"""Get the name of the function.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
str: The function identifier parsed from the source code.
|
|
232
|
+
"""
|
|
145
233
|
if not hasattr(self, "_name_cache") or self._name_cache is None:
|
|
146
234
|
self._name_cache = get_function_name_from_source(self.source_code)
|
|
147
235
|
return self._name_cache
|
|
148
236
|
|
|
149
237
|
|
|
150
238
|
@property
|
|
151
|
-
def hash_signature(self):
|
|
152
|
-
"""Get the hash signature of the function.
|
|
239
|
+
def hash_signature(self) -> str:
|
|
240
|
+
"""Get the hash signature of the function.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
str: A stable, content-derived signature used to uniquely identify
|
|
244
|
+
the function's normalized source and selected metadata.
|
|
245
|
+
"""
|
|
153
246
|
if not hasattr(self, "_hash_signature_cache"):
|
|
154
247
|
self._hash_signature_cache = get_hash_signature(self)
|
|
155
248
|
return self._hash_signature_cache
|
|
@@ -157,6 +250,12 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
157
250
|
|
|
158
251
|
@property
|
|
159
252
|
def _virtual_file_name(self):
|
|
253
|
+
"""Return a synthetic filename used when compiling the function.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
str: A stable file name derived from the function name and hash
|
|
257
|
+
signature, ending with ".py".
|
|
258
|
+
"""
|
|
160
259
|
if not hasattr(self, "_virtual_file_name_cache") or self._virtual_file_name_cache is None:
|
|
161
260
|
self._virtual_file_name_cache = self.name + "_" + self.hash_signature + ".py"
|
|
162
261
|
return self._virtual_file_name_cache
|
|
@@ -164,6 +263,11 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
164
263
|
|
|
165
264
|
@property
|
|
166
265
|
def _kwargs_var_name(self):
|
|
266
|
+
"""Return the internal name used to store call kwargs during exec.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
str: A stable variable name unique to this function instance.
|
|
270
|
+
"""
|
|
167
271
|
if not hasattr(self, "_kwargs_var_name_cache") or self._kwargs_var_name_cache is None:
|
|
168
272
|
self._kwargs_var_name_cache = "kwargs_" + self.name
|
|
169
273
|
self._kwargs_var_name_cache += "_" + self.hash_signature
|
|
@@ -172,6 +276,11 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
172
276
|
|
|
173
277
|
@property
|
|
174
278
|
def _result_var_name(self):
|
|
279
|
+
"""Return the internal name used to store the call result.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
str: A stable variable name unique to this function instance.
|
|
283
|
+
"""
|
|
175
284
|
if not hasattr(self, "_result_var_name_cache") or self._result_var_name_cache is None:
|
|
176
285
|
self._result_var_name_cache = "result_" + self.name
|
|
177
286
|
self._result_var_name_cache += "_" + self.hash_signature
|
|
@@ -180,6 +289,11 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
180
289
|
|
|
181
290
|
@property
|
|
182
291
|
def _tmp_fn_name(self):
|
|
292
|
+
"""Return the internal temporary function name used during exec.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
str: A stable function name unique to this function instance.
|
|
296
|
+
"""
|
|
183
297
|
if not hasattr(self, "_tmp_fn_name_cache") or self._tmp_fn_name_cache is None:
|
|
184
298
|
self._tmp_fn_name_cache = "func_" + self.name
|
|
185
299
|
self._tmp_fn_name_cache += "_" + self.hash_signature
|
|
@@ -188,6 +302,11 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
188
302
|
|
|
189
303
|
@property
|
|
190
304
|
def _compiled_code(self):
|
|
305
|
+
"""Return cached code object used to execute the function call.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Any: A Python code object suitable for exec.
|
|
309
|
+
"""
|
|
191
310
|
if not hasattr(self, "_compiled_code_cache") or (
|
|
192
311
|
self._compiled_code_cache is None):
|
|
193
312
|
source_to_execute = self.source_code
|
|
@@ -201,11 +320,11 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
201
320
|
|
|
202
321
|
|
|
203
322
|
def _invalidate_cache(self):
|
|
204
|
-
"""Invalidate
|
|
323
|
+
"""Invalidate all cached, derived attributes for this function.
|
|
205
324
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
325
|
+
Any property backed by a "_..._cache" attribute is deleted so that it
|
|
326
|
+
will be recomputed on next access. Also calls the superclass
|
|
327
|
+
implementation to clear any portal-related caches.
|
|
209
328
|
"""
|
|
210
329
|
if hasattr(self, "_compiled_code_cache"):
|
|
211
330
|
del self._compiled_code_cache
|
|
@@ -226,10 +345,31 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
226
345
|
|
|
227
346
|
@classmethod
|
|
228
347
|
def _compile(cls,*args, **kwargs) -> Any:
|
|
348
|
+
"""Compile Python source code.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
*args: Positional arguments passed to compile().
|
|
352
|
+
**kwargs: Keyword arguments passed to compile().
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Any: The code object returned by compile().
|
|
356
|
+
"""
|
|
229
357
|
return compile(*args, **kwargs)
|
|
230
358
|
|
|
231
359
|
|
|
232
360
|
def __call__(self,* args, **kwargs) -> Any:
|
|
361
|
+
"""Invoke the wrapped function using only keyword arguments.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
*args: Positional arguments are not allowed and will raise.
|
|
365
|
+
**kwargs: Keyword arguments to pass to the function.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Any: The result of executing the function.
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
AssertionError: If positional arguments are supplied.
|
|
372
|
+
"""
|
|
233
373
|
assert len(args) == 0, (f"Function {self.name} can't"
|
|
234
374
|
+ " be called with positional arguments,"
|
|
235
375
|
+ " only keyword arguments are allowed.")
|
|
@@ -237,7 +377,13 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
237
377
|
|
|
238
378
|
|
|
239
379
|
def _available_names(self):
|
|
240
|
-
"""
|
|
380
|
+
"""Return names injected into the function's execution context.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
dict: A mapping of name to object made available during execution,
|
|
384
|
+
including globals(), the OrdinaryFn itself under its function name
|
|
385
|
+
and under "self", and the pythagoras package as "pth".
|
|
386
|
+
"""
|
|
241
387
|
import pythagoras as pth
|
|
242
388
|
names= dict(globals())
|
|
243
389
|
names[self.name] = self
|
|
@@ -247,6 +393,17 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
247
393
|
|
|
248
394
|
|
|
249
395
|
def execute(self,**kwargs):
|
|
396
|
+
"""Execute the underlying function with the provided keyword args.
|
|
397
|
+
|
|
398
|
+
The call is executed inside the portal context, with an execution
|
|
399
|
+
namespace populated by _available_names().
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
**kwargs: Keyword-only arguments for the function call.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Any: The value returned by the function.
|
|
406
|
+
"""
|
|
250
407
|
with self.portal:
|
|
251
408
|
names_dict = self._available_names()
|
|
252
409
|
names_dict[self._kwargs_var_name] = kwargs
|
|
@@ -256,18 +413,33 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
256
413
|
|
|
257
414
|
|
|
258
415
|
def __getstate__(self):
|
|
259
|
-
"""
|
|
416
|
+
"""Return the picklable state for this instance.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
dict: A mapping that contains the normalized source code under the
|
|
420
|
+
"source_code" key. Runtime caches and portal references are omitted.
|
|
421
|
+
"""
|
|
260
422
|
state = dict(source_code=self._source_code)
|
|
261
423
|
return state
|
|
262
424
|
|
|
263
425
|
|
|
264
426
|
def __setstate__(self, state):
|
|
265
|
-
"""
|
|
427
|
+
"""Restore instance state from pickled data.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
state (dict): The state mapping previously produced by __getstate__,
|
|
431
|
+
expected to contain the "source_code" key.
|
|
432
|
+
"""
|
|
266
433
|
super().__setstate__(state)
|
|
267
434
|
self._source_code = state["source_code"]
|
|
268
435
|
|
|
269
436
|
|
|
270
437
|
def __hash_signature_descriptor__(self) -> str:
|
|
438
|
+
"""Return a short descriptor string used in hashing.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
str: Lowercased string combining the function name and class name.
|
|
442
|
+
"""
|
|
271
443
|
descriptor = self.name
|
|
272
444
|
descriptor += "_" + self.__class__.__name__
|
|
273
445
|
descriptor = descriptor.lower()
|