pythagoras 0.24.8__py3-none-any.whl → 0.24.10__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/_010_basic_portals/__init__.py +0 -1
- pythagoras/_010_basic_portals/basic_portal_core_classes.py +21 -15
- pythagoras/_010_basic_portals/portal_tester.py +3 -2
- pythagoras/_020_ordinary_code_portals/__init__.py +1 -0
- pythagoras/_020_ordinary_code_portals/code_normalizer.py +14 -14
- pythagoras/_020_ordinary_code_portals/exceptions.py +11 -0
- pythagoras/_020_ordinary_code_portals/function_processing.py +10 -10
- pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +2 -1
- pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +11 -9
- pythagoras/_030_data_portals/data_portal_core_classes.py +13 -11
- pythagoras/_030_data_portals/storable_decorator.py +3 -1
- pythagoras/_040_logging_code_portals/logging_decorator.py +4 -2
- pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +14 -13
- pythagoras/_050_safe_code_portals/safe_decorator.py +2 -1
- pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +4 -2
- pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +5 -3
- pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +23 -17
- pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +18 -15
- pythagoras/_070_protected_code_portals/protected_decorators.py +4 -2
- pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +15 -8
- pythagoras/_080_pure_code_portals/pure_core_classes.py +22 -15
- pythagoras/_080_pure_code_portals/recursion_pre_validator.py +8 -4
- pythagoras/_090_swarming_portals/swarming_portals.py +17 -13
- {pythagoras-0.24.8.dist-info → pythagoras-0.24.10.dist-info}/METADATA +1 -1
- {pythagoras-0.24.8.dist-info → pythagoras-0.24.10.dist-info}/RECORD +26 -26
- pythagoras/_010_basic_portals/exceptions.py +0 -36
- {pythagoras-0.24.8.dist-info → pythagoras-0.24.10.dist-info}/WHEEL +0 -0
|
@@ -65,8 +65,8 @@ def _describe_runtime_characteristic(name, value) -> pd.DataFrame:
|
|
|
65
65
|
return pd.DataFrame(d)
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
_BASE_DIRECTORY_TXT = "Base directory"
|
|
69
|
+
_BACKEND_TYPE_TXT = "Backend type"
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def _get_description_value_by_key(dataframe:pd.DataFrame, key:str) -> Any:
|
|
@@ -200,7 +200,8 @@ def get_default_portal_base_dir() -> str:
|
|
|
200
200
|
target_directory = home_directory / ".pythagoras" / ".default_portal"
|
|
201
201
|
target_directory.mkdir(parents=True, exist_ok=True)
|
|
202
202
|
target_directory_str = str(target_directory.resolve())
|
|
203
|
-
|
|
203
|
+
if not isinstance(target_directory_str, str):
|
|
204
|
+
raise TypeError(f"Expected target_directory_str to be str, got {type(target_directory_str).__name__}")
|
|
204
205
|
return target_directory_str
|
|
205
206
|
|
|
206
207
|
|
|
@@ -280,8 +281,10 @@ class BasicPortal(NotPicklableClass,ParameterizableClass, metaclass = PostInitMe
|
|
|
280
281
|
global _all_links_from_objects_to_portals
|
|
281
282
|
result = set()
|
|
282
283
|
for obj_str_id, portal_str_id in _all_links_from_objects_to_portals.items():
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
if not isinstance(obj_str_id, str):
|
|
285
|
+
raise TypeError(f"Expected obj_str_id to be str, got {type(obj_str_id).__name__}")
|
|
286
|
+
if not isinstance(portal_str_id, str):
|
|
287
|
+
raise TypeError(f"Expected portal_str_id to be str, got {type(portal_str_id).__name__}")
|
|
285
288
|
if portal_str_id == self._str_id:
|
|
286
289
|
if target_class is None:
|
|
287
290
|
result.add(obj_str_id)
|
|
@@ -386,9 +389,9 @@ class BasicPortal(NotPicklableClass,ParameterizableClass, metaclass = PostInitMe
|
|
|
386
389
|
all_params = []
|
|
387
390
|
|
|
388
391
|
all_params.append(_describe_persistent_characteristic(
|
|
389
|
-
|
|
392
|
+
_BASE_DIRECTORY_TXT, self._root_dict.base_dir))
|
|
390
393
|
all_params.append(_describe_persistent_characteristic(
|
|
391
|
-
|
|
394
|
+
_BACKEND_TYPE_TXT, self._root_dict.__class__.__name__))
|
|
392
395
|
|
|
393
396
|
result = pd.concat(all_params)
|
|
394
397
|
result.reset_index(drop=True, inplace=True)
|
|
@@ -411,10 +414,9 @@ class BasicPortal(NotPicklableClass,ParameterizableClass, metaclass = PostInitMe
|
|
|
411
414
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
412
415
|
"""Pop the portal from the stack of active ones"""
|
|
413
416
|
global _active_portals_stack, _active_portals_counters_stack
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
+ "within a 'with' statement with another portal.")
|
|
417
|
+
if _active_portals_stack[-1] != self:
|
|
418
|
+
raise RuntimeError(
|
|
419
|
+
"Inconsistent state of the portal stack. Most probably, portal.__enter__() method was called explicitly within a 'with' statement with another portal.")
|
|
418
420
|
if _active_portals_counters_stack[-1] == 1:
|
|
419
421
|
_active_portals_stack.pop()
|
|
420
422
|
_active_portals_counters_stack.pop()
|
|
@@ -462,7 +464,8 @@ class PortalAwareClass(metaclass = PostInitMeta):
|
|
|
462
464
|
portal: The portal to link this object to, or None to use the
|
|
463
465
|
currently active portal for operations.
|
|
464
466
|
"""
|
|
465
|
-
|
|
467
|
+
if not (portal is None or isinstance(portal, BasicPortal)):
|
|
468
|
+
raise TypeError(f"portal must be a BasicPortal or None, got {type(portal).__name__}")
|
|
466
469
|
self._linked_portal_at_init = portal
|
|
467
470
|
# self._hash_id_cache = None
|
|
468
471
|
self._visited_portals = set()
|
|
@@ -501,7 +504,8 @@ class PortalAwareClass(metaclass = PostInitMeta):
|
|
|
501
504
|
portal = self._linked_portal_at_init
|
|
502
505
|
elif self._str_id in _all_links_from_objects_to_portals:
|
|
503
506
|
portal_str_id = _all_links_from_objects_to_portals[self._str_id]
|
|
504
|
-
|
|
507
|
+
if not isinstance(portal_str_id, str):
|
|
508
|
+
raise TypeError(f"Expected portal_str_id to be str, got {type(portal_str_id).__name__}")
|
|
505
509
|
portal = _all_known_portals[portal_str_id]
|
|
506
510
|
return portal
|
|
507
511
|
|
|
@@ -595,7 +599,8 @@ class PortalAwareClass(metaclass = PostInitMeta):
|
|
|
595
599
|
"""Return True if the object has been registered in at least one of the portals."""
|
|
596
600
|
global _all_activated_portal_aware_objects
|
|
597
601
|
if len(self._visited_portals) >=1:
|
|
598
|
-
|
|
602
|
+
if self._str_id not in _all_activated_portal_aware_objects:
|
|
603
|
+
raise RuntimeError(f"Object with id {self._str_id} is expected to be in the activated objects registry")
|
|
599
604
|
return True
|
|
600
605
|
return False
|
|
601
606
|
|
|
@@ -606,7 +611,8 @@ class PortalAwareClass(metaclass = PostInitMeta):
|
|
|
606
611
|
Empty the list of portals it has been registered into.
|
|
607
612
|
"""
|
|
608
613
|
global _all_activated_portal_aware_objects
|
|
609
|
-
|
|
614
|
+
if not self.is_activated:
|
|
615
|
+
raise RuntimeError(f"Object with id {self._str_id} is not activated")
|
|
610
616
|
del _all_activated_portal_aware_objects[self._str_id]
|
|
611
617
|
self._invalidate_cache()
|
|
612
618
|
self._visited_portals = set()
|
|
@@ -25,14 +25,15 @@ class _PortalTester:
|
|
|
25
25
|
|
|
26
26
|
Raises:
|
|
27
27
|
Exception: If another _PortalTester instance is already active.
|
|
28
|
-
|
|
28
|
+
TypeError: If portal_class is not a subclass of BasicPortal.
|
|
29
29
|
"""
|
|
30
30
|
if _PortalTester._current_instance is not None:
|
|
31
31
|
raise Exception("_PortalTester can't be nested")
|
|
32
32
|
_PortalTester._current_instance = self
|
|
33
33
|
|
|
34
34
|
if portal_class is not None:
|
|
35
|
-
|
|
35
|
+
if not issubclass(portal_class, BasicPortal):
|
|
36
|
+
raise TypeError(f"portal_class must be a subclass of BasicPortal, got {portal_class}")
|
|
36
37
|
self.portal_class = portal_class
|
|
37
38
|
self.args = args
|
|
38
39
|
self.kwargs = kwargs
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
|
-
from .._010_basic_portals.exceptions import NonCompliantFunction
|
|
4
1
|
import ast
|
|
5
2
|
import inspect
|
|
6
3
|
from typing import Callable
|
|
@@ -9,6 +6,7 @@ import autopep8
|
|
|
9
6
|
from .function_processing import get_function_name_from_source
|
|
10
7
|
from .._010_basic_portals.long_infoname import get_long_infoname
|
|
11
8
|
from .function_processing import assert_ordinarity
|
|
9
|
+
from .exceptions import FunctionError
|
|
12
10
|
|
|
13
11
|
_pythagoras_decorator_names = {
|
|
14
12
|
"ordinary"
|
|
@@ -52,12 +50,11 @@ def _get_normalized_function_source_impl(
|
|
|
52
50
|
A normalized source code string for the function.
|
|
53
51
|
|
|
54
52
|
Raises:
|
|
55
|
-
|
|
53
|
+
FunctionError: If the function has multiple decorators when
|
|
56
54
|
a callable or a string representing a single function is
|
|
57
55
|
expected; or if it fails ordinarity checks.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
when internal integrity checks do not hold.
|
|
56
|
+
TypeError | ValueError: If input types or parsing assumptions fail
|
|
57
|
+
(e.g., unexpected AST node types), or when integrity checks do not hold.
|
|
61
58
|
SyntaxError: If the provided source string cannot be parsed.
|
|
62
59
|
"""
|
|
63
60
|
|
|
@@ -71,7 +68,7 @@ def _get_normalized_function_source_impl(
|
|
|
71
68
|
code = a_func
|
|
72
69
|
a_func_name = get_function_name_from_source(code)
|
|
73
70
|
else:
|
|
74
|
-
|
|
71
|
+
raise TypeError(f"a_func must be a callable or a string, got {type(a_func).__name__}")
|
|
75
72
|
|
|
76
73
|
code_lines = code.splitlines()
|
|
77
74
|
|
|
@@ -88,7 +85,8 @@ def _get_normalized_function_source_impl(
|
|
|
88
85
|
chars_to_remove = code_no_empty_lines[0][:n_chars_to_remove]
|
|
89
86
|
code_clean_version = []
|
|
90
87
|
for line in code_no_empty_lines:
|
|
91
|
-
|
|
88
|
+
if not line.startswith(chars_to_remove):
|
|
89
|
+
raise ValueError(f"Inconsistent indentation detected while normalizing function {a_func_name}")
|
|
92
90
|
cleaned_line = line[n_chars_to_remove:]
|
|
93
91
|
code_clean_version.append(cleaned_line)
|
|
94
92
|
|
|
@@ -97,14 +95,15 @@ def _get_normalized_function_source_impl(
|
|
|
97
95
|
a_func_name = get_function_name_from_source(code_clean_version)
|
|
98
96
|
code_ast = ast.parse(code_clean_version)
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
if not isinstance(code_ast, ast.Module):
|
|
99
|
+
raise TypeError(f"Expected AST Module for {a_func_name}, got {type(code_ast).__name__}")
|
|
100
|
+
if not isinstance(code_ast.body[0], ast.FunctionDef):
|
|
101
|
+
raise ValueError(f"Top-level node is not a FunctionDef for {a_func_name}; got {type(code_ast.body[0]).__name__}")
|
|
103
102
|
|
|
104
103
|
# TODO: add support for multiple decorators???
|
|
105
104
|
decorator_list = code_ast.body[0].decorator_list
|
|
106
105
|
if len(decorator_list) > 1:
|
|
107
|
-
raise
|
|
106
|
+
raise FunctionError(
|
|
108
107
|
f"Function {a_func_name} can't have multiple decorators,"
|
|
109
108
|
+ " only one decorator is allowed.")
|
|
110
109
|
|
|
@@ -127,7 +126,8 @@ def _get_normalized_function_source_impl(
|
|
|
127
126
|
break
|
|
128
127
|
except:
|
|
129
128
|
pass
|
|
130
|
-
|
|
129
|
+
if pth_dec_counter != 1:
|
|
130
|
+
raise ValueError(f"Unexpected decorator configuration for {a_func_name}: unable to drop Pythagoras decorator")
|
|
131
131
|
code_ast.body[0].decorator_list = []
|
|
132
132
|
|
|
133
133
|
# Remove docstrings.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FunctionError(Exception):
|
|
5
|
+
"""Exception raised when a function does not comply with Pythagoras requirements.
|
|
6
|
+
|
|
7
|
+
This exception is raised when a function (or its usage) fails to meet
|
|
8
|
+
the compliance requirements of the Pythagoras framework.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
pass
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import types, inspect
|
|
2
2
|
from typing import Callable
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from .exceptions import FunctionError
|
|
5
5
|
from .._010_basic_portals.long_infoname import get_long_infoname
|
|
6
6
|
|
|
7
7
|
|
|
@@ -93,7 +93,7 @@ def assert_ordinarity(a_func: Callable) -> None:
|
|
|
93
93
|
a_func: The callable to validate.
|
|
94
94
|
|
|
95
95
|
Raises:
|
|
96
|
-
|
|
96
|
+
FunctionError: If the callable violates any of the ordinarity
|
|
97
97
|
constraints described above (e.g., not a function, is a method,
|
|
98
98
|
is a lambda/closure/async function, accepts *args, or has defaults).
|
|
99
99
|
"""
|
|
@@ -104,35 +104,35 @@ def assert_ordinarity(a_func: Callable) -> None:
|
|
|
104
104
|
name = get_long_infoname(a_func)
|
|
105
105
|
|
|
106
106
|
if not callable(a_func):
|
|
107
|
-
raise
|
|
107
|
+
raise FunctionError(f"{name} must be callable.")
|
|
108
108
|
|
|
109
109
|
if not inspect.isfunction(a_func):
|
|
110
|
-
raise
|
|
110
|
+
raise FunctionError(f"The function {name} is not ordinary."
|
|
111
111
|
"It must be a function, not a method, "
|
|
112
112
|
"a classmethod, or a lambda function.")
|
|
113
113
|
|
|
114
114
|
if isinstance(a_func, types.MethodType):
|
|
115
|
-
raise
|
|
115
|
+
raise FunctionError(f"The function {name} can't be "
|
|
116
116
|
"an instance or a class method, only "
|
|
117
117
|
"regular functions are allowed.")
|
|
118
118
|
|
|
119
119
|
if hasattr(a_func, "__closure__") and a_func.__closure__ is not None:
|
|
120
|
-
raise
|
|
120
|
+
raise FunctionError(f"The function {name} can't be a closure,"
|
|
121
121
|
" only regular functions are allowed.")
|
|
122
122
|
|
|
123
123
|
if a_func.__name__ == "<lambda>":
|
|
124
|
-
raise
|
|
124
|
+
raise FunctionError(f"The function {name} can't be lambda,"
|
|
125
125
|
" only regular functions are allowed.")
|
|
126
126
|
|
|
127
127
|
if accepts_unlimited_positional_args(a_func):
|
|
128
|
-
raise
|
|
128
|
+
raise FunctionError("Pythagoras only allows functions "
|
|
129
129
|
f"with named arguments, but {name} accepts "
|
|
130
130
|
"unlimited (nameless) positional arguments.")
|
|
131
131
|
|
|
132
132
|
if inspect.iscoroutinefunction(a_func):
|
|
133
|
-
raise
|
|
133
|
+
raise FunctionError(f"The function {name} can't be "
|
|
134
134
|
"an async function, only regular functions are allowed.")
|
|
135
135
|
|
|
136
136
|
if count_parameters_with_defaults(a_func) > 0:
|
|
137
|
-
raise
|
|
137
|
+
raise FunctionError(f"The function {name} can't have "
|
|
138
138
|
"default values for its parameters.")
|
|
@@ -20,7 +20,8 @@ class ordinary:
|
|
|
20
20
|
portal: Optional OrdinaryCodePortal to link to the resulting
|
|
21
21
|
OrdinaryFn wrappers.
|
|
22
22
|
"""
|
|
23
|
-
|
|
23
|
+
if not (portal is None or isinstance(portal, OrdinaryCodePortal)):
|
|
24
|
+
raise TypeError(f"portal must be an OrdinaryCodePortal or None, got {type(portal).__name__}")
|
|
24
25
|
self._portal=portal
|
|
25
26
|
|
|
26
27
|
|
|
@@ -6,6 +6,7 @@ from typing import Callable, Any
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from persidict import PersiDict
|
|
8
8
|
|
|
9
|
+
from .exceptions import FunctionError
|
|
9
10
|
from .._010_basic_portals import BasicPortal, PortalAwareClass
|
|
10
11
|
from .code_normalizer import _get_normalized_function_source_impl
|
|
11
12
|
from .function_processing import get_function_name_from_source
|
|
@@ -32,9 +33,9 @@ def get_normalized_function_source(a_func: OrdinaryFn | Callable | str) -> str:
|
|
|
32
33
|
The normalized source code string.
|
|
33
34
|
|
|
34
35
|
Raises:
|
|
35
|
-
|
|
36
|
+
FunctionError: If the function is not compliant with Pythagoras'
|
|
36
37
|
ordinarity rules or multiple decorators are present.
|
|
37
|
-
|
|
38
|
+
TypeError | ValueError: If input type is invalid or integrity checks fail.
|
|
38
39
|
SyntaxError: If the provided source cannot be parsed.
|
|
39
40
|
"""
|
|
40
41
|
|
|
@@ -45,7 +46,7 @@ def get_normalized_function_source(a_func: OrdinaryFn | Callable | str) -> str:
|
|
|
45
46
|
a_func, drop_pth_decorators=True)
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
_REGISTERED_FUNCTIONS_TXT = "Registered functions"
|
|
49
50
|
|
|
50
51
|
class OrdinaryCodePortal(BasicPortal):
|
|
51
52
|
"""Portal that manages OrdinaryFn instances and their runtime context.
|
|
@@ -136,7 +137,7 @@ class OrdinaryCodePortal(BasicPortal):
|
|
|
136
137
|
all_params = [super().describe()]
|
|
137
138
|
|
|
138
139
|
all_params.append(_describe_runtime_characteristic(
|
|
139
|
-
|
|
140
|
+
_REGISTERED_FUNCTIONS_TXT, self.get_number_of_linked_functions()))
|
|
140
141
|
|
|
141
142
|
result = pd.concat(all_params)
|
|
142
143
|
result.reset_index(drop=True, inplace=True)
|
|
@@ -182,7 +183,7 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
182
183
|
|
|
183
184
|
Raises:
|
|
184
185
|
TypeError: If fn is neither callable, a string, nor an OrdinaryFn.
|
|
185
|
-
|
|
186
|
+
FunctionError: If the provided function source is not
|
|
186
187
|
compliant with Pythagoras ordinarity rules.
|
|
187
188
|
SyntaxError: If the provided source cannot be parsed.
|
|
188
189
|
"""
|
|
@@ -368,11 +369,12 @@ class OrdinaryFn(PortalAwareClass):
|
|
|
368
369
|
Any: The result of executing the function.
|
|
369
370
|
|
|
370
371
|
Raises:
|
|
371
|
-
|
|
372
|
+
TypeError: If positional arguments are supplied.
|
|
372
373
|
"""
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
if len(args) != 0:
|
|
375
|
+
raise FunctionError(f"Function {self.name} can't be called"
|
|
376
|
+
f" with positional arguments, only keyword arguments are allowed."
|
|
377
|
+
f" Got {len(args)} positional args.")
|
|
376
378
|
return self.execute(**kwargs)
|
|
377
379
|
|
|
378
380
|
|
|
@@ -17,8 +17,8 @@ from .._010_basic_portals.basic_portal_core_classes import (
|
|
|
17
17
|
from .._020_ordinary_code_portals import OrdinaryCodePortal ,OrdinaryFn
|
|
18
18
|
from persidict import WriteOnceDict
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
_TOTAL_VALUES_TXT = "Values, total"
|
|
21
|
+
_PROBABILITY_OF_CHECKS_TXT = "Probability of consistency checks"
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def get_active_data_portal() -> DataPortal:
|
|
@@ -28,10 +28,11 @@ def get_active_data_portal() -> DataPortal:
|
|
|
28
28
|
DataPortal: The portal that is active in the current context ("with" block).
|
|
29
29
|
|
|
30
30
|
Raises:
|
|
31
|
-
|
|
31
|
+
TypeError: If the active portal is not an instance of DataPortal.
|
|
32
32
|
"""
|
|
33
33
|
portal = get_active_portal()
|
|
34
|
-
|
|
34
|
+
if not isinstance(portal, DataPortal):
|
|
35
|
+
raise TypeError(f"Active portal must be DataPortal, got {type(portal).__name__}")
|
|
35
36
|
return portal
|
|
36
37
|
|
|
37
38
|
|
|
@@ -43,10 +44,12 @@ def get_nonactive_data_portals() -> list[DataPortal]:
|
|
|
43
44
|
the runtime but are not the current active portal stack.
|
|
44
45
|
|
|
45
46
|
Raises:
|
|
46
|
-
|
|
47
|
+
TypeError: If any returned portal is not an instance of DataPortal.
|
|
47
48
|
"""
|
|
48
49
|
portals = get_nonactive_portals()
|
|
49
|
-
|
|
50
|
+
if not all(isinstance(p, DataPortal) for p in portals):
|
|
51
|
+
bad = [type(p).__name__ for p in portals if not isinstance(p, DataPortal)]
|
|
52
|
+
raise TypeError(f"Expected all nonactive portals to be DataPortal, got invalid types: {bad}")
|
|
50
53
|
return portals
|
|
51
54
|
|
|
52
55
|
|
|
@@ -211,9 +214,9 @@ class DataPortal(OrdinaryCodePortal):
|
|
|
211
214
|
all_params = [super().describe()]
|
|
212
215
|
|
|
213
216
|
all_params.append(_describe_persistent_characteristic(
|
|
214
|
-
|
|
217
|
+
_TOTAL_VALUES_TXT, len(self._value_store)))
|
|
215
218
|
all_params.append(_describe_runtime_characteristic(
|
|
216
|
-
|
|
219
|
+
_PROBABILITY_OF_CHECKS_TXT, self.p_consistency_checks))
|
|
217
220
|
|
|
218
221
|
result = pd.concat(all_params)
|
|
219
222
|
result.reset_index(drop=True, inplace=True)
|
|
@@ -493,9 +496,8 @@ class ValueAddr(HashAddr):
|
|
|
493
496
|
, hash_signature=hash_signature)
|
|
494
497
|
return
|
|
495
498
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
+ "convert HashAddr into ValueAddr")
|
|
499
|
+
if isinstance(data, HashAddr):
|
|
500
|
+
raise TypeError("get_ValueAddr is the only way to convert HashAddr into ValueAddr")
|
|
499
501
|
|
|
500
502
|
descriptor = self._build_descriptor(data)
|
|
501
503
|
hash_signature = self._build_hash_signature(data)
|
|
@@ -18,7 +18,9 @@ class storable(ordinary):
|
|
|
18
18
|
portal: The DataPortal to use as default when wrapping functions.
|
|
19
19
|
If None, the active portal at call time will be used.
|
|
20
20
|
"""
|
|
21
|
-
|
|
21
|
+
if not (isinstance(portal, DataPortal) or portal is None):
|
|
22
|
+
raise TypeError(f"portal must be a DataPortal or None, "
|
|
23
|
+
f"got {type(portal).__name__}")
|
|
22
24
|
ordinary.__init__(self=self, portal=portal)
|
|
23
25
|
|
|
24
26
|
def __call__(self,fn:Callable)->StorableFn:
|
|
@@ -27,8 +27,10 @@ class logging(storable):
|
|
|
27
27
|
portal: Optional LoggingCodePortal to bind the wrapped function to.
|
|
28
28
|
If None, the active portal at execution time is used.
|
|
29
29
|
"""
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if not isinstance(excessive_logging, (bool, Joker)):
|
|
31
|
+
raise TypeError(f"excessive_logging must be bool or Joker, got {type(excessive_logging).__name__}")
|
|
32
|
+
if not (isinstance(portal, LoggingCodePortal) or portal is None):
|
|
33
|
+
raise TypeError(f"portal must be LoggingCodePortal or None, got {type(portal).__name__}")
|
|
32
34
|
storable.__init__(self=self, portal=portal)
|
|
33
35
|
self._excessive_logging = excessive_logging
|
|
34
36
|
|
|
@@ -159,7 +159,8 @@ class LoggingFnCallSignature:
|
|
|
159
159
|
_addr_cache: ValueAddr | None
|
|
160
160
|
|
|
161
161
|
def __init__(self, fn:LoggingFn, arguments:dict):
|
|
162
|
-
|
|
162
|
+
if not isinstance(fn, LoggingFn):
|
|
163
|
+
raise TypeError(f"fn must be an instance of LoggingFn, got {type(fn).__name__}")
|
|
163
164
|
isinstance(arguments, dict)
|
|
164
165
|
arguments = KwArgs(**arguments)
|
|
165
166
|
with fn.portal:
|
|
@@ -729,13 +730,13 @@ class LoggingFnExecutionFrame(NotPicklableClass):
|
|
|
729
730
|
Returns:
|
|
730
731
|
LoggingFnExecutionFrame: This frame instance for use as a context var.
|
|
731
732
|
"""
|
|
732
|
-
|
|
733
|
-
"An instance of PureFnExecutionFrame can be used only once.")
|
|
733
|
+
if self.context_used:
|
|
734
|
+
raise RuntimeError(f"An instance of PureFnExecutionFrame can be used only once.")
|
|
734
735
|
self.context_used = True
|
|
735
|
-
|
|
736
|
-
"An instance of PureFnExecutionFrame can be used only once.")
|
|
737
|
-
|
|
738
|
-
"An instance of PureFnExecutionFrame can be used only once.")
|
|
736
|
+
if self.exception_counter != 0:
|
|
737
|
+
raise RuntimeError(f"An instance of PureFnExecutionFrame can be used only once.")
|
|
738
|
+
if self.event_counter != 0:
|
|
739
|
+
raise RuntimeError(f"An instance of PureFnExecutionFrame can be used only once.")
|
|
739
740
|
self.portal.__enter__()
|
|
740
741
|
if isinstance(self.output_capturer, OutputCapturer):
|
|
741
742
|
self.output_capturer.__enter__()
|
|
@@ -797,9 +798,9 @@ class LoggingFnExecutionFrame(NotPicklableClass):
|
|
|
797
798
|
LoggingFnExecutionFrame.call_stack.pop()
|
|
798
799
|
|
|
799
800
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
801
|
+
_EXCEPTIONS_TOTAL_TXT = "Exceptions, total"
|
|
802
|
+
_EXCEPTIONS_TODAY_TXT = "Exceptions, today"
|
|
803
|
+
_EXCESSIVE_LOGGING_TXT = "Excessive logging"
|
|
803
804
|
|
|
804
805
|
class LoggingCodePortal(DataPortal):
|
|
805
806
|
"""A portal that supports function-level logging for events and exceptions.
|
|
@@ -921,12 +922,12 @@ class LoggingCodePortal(DataPortal):
|
|
|
921
922
|
"""
|
|
922
923
|
all_params = [super().describe()]
|
|
923
924
|
all_params.append(_describe_persistent_characteristic(
|
|
924
|
-
|
|
925
|
+
_EXCEPTIONS_TOTAL_TXT, len(self._crash_history)))
|
|
925
926
|
all_params.append(_describe_persistent_characteristic(
|
|
926
|
-
|
|
927
|
+
_EXCEPTIONS_TODAY_TXT
|
|
927
928
|
, len(self._crash_history.get_subdict(current_date_gmt_string()))))
|
|
928
929
|
all_params.append(_describe_runtime_characteristic(
|
|
929
|
-
|
|
930
|
+
_EXCESSIVE_LOGGING_TXT, self.excessive_logging))
|
|
930
931
|
|
|
931
932
|
result = pd.concat(all_params)
|
|
932
933
|
result.reset_index(drop=True, inplace=True)
|
|
@@ -30,7 +30,8 @@ class safe(logging):
|
|
|
30
30
|
portal: The SafeCodePortal to attach the resulting SafeFn to. If
|
|
31
31
|
None, the active portal (if any) may be used by lower layers.
|
|
32
32
|
"""
|
|
33
|
-
|
|
33
|
+
if not (isinstance(portal, SafeCodePortal) or portal is None):
|
|
34
|
+
raise TypeError(f"portal must be a SafeCodePortal or None, got {type(portal).__name__}")
|
|
34
35
|
logging.__init__(self=self
|
|
35
36
|
, portal=portal
|
|
36
37
|
, excessive_logging=excessive_logging)
|
|
@@ -72,8 +72,10 @@ class SafeFnCallSignature(LoggingFnCallSignature):
|
|
|
72
72
|
fn: The safe function object to be called.
|
|
73
73
|
arguments: The keyword arguments to use for the call.
|
|
74
74
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
if not isinstance(fn, SafeFn):
|
|
76
|
+
raise TypeError(f"fn must be a SafeFn instance, got {type(fn).__name__}")
|
|
77
|
+
if not isinstance(arguments, dict):
|
|
78
|
+
raise TypeError(f"arguments must be a dict, got {type(arguments).__name__}")
|
|
77
79
|
super().__init__(fn, arguments)
|
|
78
80
|
|
|
79
81
|
@property
|
|
@@ -74,11 +74,13 @@ class autonomous(safe):
|
|
|
74
74
|
portal: Portal instance to use for autonomy and safety checks.
|
|
75
75
|
|
|
76
76
|
Raises:
|
|
77
|
-
|
|
77
|
+
TypeError: If portal is not an AutonomousCodePortal or None, or
|
|
78
78
|
if fixed_kwargs is not a dict or None.
|
|
79
79
|
"""
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
if not (isinstance(portal, AutonomousCodePortal) or portal is None):
|
|
81
|
+
raise TypeError(f"portal must be an AutonomousCodePortal or None, got {type(portal).__name__}")
|
|
82
|
+
if not (isinstance(fixed_kwargs, dict) or fixed_kwargs is None):
|
|
83
|
+
raise TypeError(f"fixed_kwargs must be a dict or None, got {type(fixed_kwargs).__name__}")
|
|
82
84
|
safe.__init__(self=self
|
|
83
85
|
, portal=portal
|
|
84
86
|
, excessive_logging=excessive_logging)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import builtins
|
|
4
|
+
|
|
5
|
+
from .. import FunctionError
|
|
4
6
|
from .._020_ordinary_code_portals.code_normalizer import _pythagoras_decorator_names
|
|
5
7
|
from .._040_logging_code_portals import KwArgs
|
|
6
8
|
|
|
@@ -51,8 +53,10 @@ class AutonomousFnCallSignature(SafeFnCallSignature):
|
|
|
51
53
|
fn: The autonomous function being called.
|
|
52
54
|
arguments: The call-time arguments mapping (already validated).
|
|
53
55
|
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
if not isinstance(fn, AutonomousFn):
|
|
57
|
+
raise TypeError(f"fn must be AutonomousFn, got {type(fn).__name__}")
|
|
58
|
+
if not isinstance(arguments, dict):
|
|
59
|
+
raise TypeError(f"arguments must be dict, got {type(arguments).__name__}")
|
|
56
60
|
super().__init__(fn, arguments)
|
|
57
61
|
|
|
58
62
|
@property
|
|
@@ -87,7 +91,7 @@ class AutonomousFn(SafeFn):
|
|
|
87
91
|
portal: AutonomousCodePortal to use; may be None to defer.
|
|
88
92
|
|
|
89
93
|
Raises:
|
|
90
|
-
|
|
94
|
+
FunctionError: If static analysis detects violations of autonomy
|
|
91
95
|
(nonlocal/global unbound names, missing imports, or yield usage).
|
|
92
96
|
"""
|
|
93
97
|
super().__init__(fn=fn
|
|
@@ -110,19 +114,20 @@ class AutonomousFn(SafeFn):
|
|
|
110
114
|
analyzer = analyze_names_in_function(self.source_code)
|
|
111
115
|
normalized_source = analyzer["normalized_source"]
|
|
112
116
|
analyzer = analyzer["analyzer"]
|
|
113
|
-
|
|
117
|
+
if self.source_code != normalized_source:
|
|
118
|
+
raise RuntimeError("Normalized source does not match original source for autonomous function")
|
|
114
119
|
|
|
115
120
|
nonlocal_names = analyzer.names.explicitly_nonlocal_unbound_deep
|
|
116
121
|
all_decorators = _pythagoras_decorator_names
|
|
117
122
|
# all_decorators = sys.modules["pythagoras"].all_decorators
|
|
118
123
|
nonlocal_names -= set(all_decorators) #????????????
|
|
119
124
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
if len(nonlocal_names) != 0:
|
|
126
|
+
raise FunctionError(
|
|
127
|
+
f"Function {self.name} is not autonomous, it uses external nonlocal objects: {analyzer.names.explicitly_nonlocal_unbound_deep}")
|
|
123
128
|
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
if analyzer.n_yelds != 0:
|
|
130
|
+
raise FunctionError(f"Function {self.name} is not autonomous, it uses yield statements")
|
|
126
131
|
|
|
127
132
|
import_required = analyzer.names.explicitly_global_unbound_deep
|
|
128
133
|
import_required |= analyzer.names.unclassified_deep
|
|
@@ -134,10 +139,9 @@ class AutonomousFn(SafeFn):
|
|
|
134
139
|
import_required -= pth_names
|
|
135
140
|
import_required -= {fn_name}
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
+ f" without importing them inside the function body")
|
|
142
|
+
if len(import_required) != 0:
|
|
143
|
+
raise FunctionError(
|
|
144
|
+
f"Function {self.name} is not autonomous, it uses global objects {import_required} without importing them inside the function body")
|
|
141
145
|
|
|
142
146
|
|
|
143
147
|
@property
|
|
@@ -165,11 +169,12 @@ class AutonomousFn(SafeFn):
|
|
|
165
169
|
Any: Result of the wrapped function call.
|
|
166
170
|
|
|
167
171
|
Raises:
|
|
168
|
-
|
|
172
|
+
ValueError: If provided kwargs overlap with fixed kwargs.
|
|
169
173
|
"""
|
|
170
174
|
with self.portal:
|
|
171
175
|
overlapping_keys = set(kwargs.keys()) & set(self.fixed_kwargs.keys())
|
|
172
|
-
|
|
176
|
+
if len(overlapping_keys) != 0:
|
|
177
|
+
raise ValueError(f"Overlapping kwargs with fixed kwargs: {sorted(overlapping_keys)}")
|
|
173
178
|
kwargs.update(self.fixed_kwargs)
|
|
174
179
|
return super().execute(**kwargs)
|
|
175
180
|
|
|
@@ -200,12 +205,13 @@ class AutonomousFn(SafeFn):
|
|
|
200
205
|
keyword arguments in addition to already fixed ones.
|
|
201
206
|
|
|
202
207
|
Raises:
|
|
203
|
-
|
|
208
|
+
ValueError: If any of the provided kwargs overlap with already
|
|
204
209
|
fixed kwargs.
|
|
205
210
|
"""
|
|
206
211
|
|
|
207
212
|
overlapping_keys = set(kwargs.keys()) & set(self.fixed_kwargs.keys())
|
|
208
|
-
|
|
213
|
+
if len(overlapping_keys) != 0:
|
|
214
|
+
raise ValueError(f"Overlapping kwargs with fixed kwargs: {sorted(overlapping_keys)}")
|
|
209
215
|
new_fixed_kwargs = {**self.fixed_kwargs,**kwargs}
|
|
210
216
|
new_fn = type(self)(fn=self, fixed_kwargs=new_fixed_kwargs)
|
|
211
217
|
return new_fn
|
|
@@ -279,7 +279,7 @@ def analyze_names_in_function(
|
|
|
279
279
|
- normalized_source (str): The normalized source code.
|
|
280
280
|
|
|
281
281
|
Raises:
|
|
282
|
-
|
|
282
|
+
ValueError: If the input is not a single regular function (e.g., a
|
|
283
283
|
lambda, async function, callable class, or multiple definitions).
|
|
284
284
|
"""
|
|
285
285
|
|
|
@@ -289,21 +289,24 @@ def analyze_names_in_function(
|
|
|
289
289
|
while lines[line_num].startswith("@"):
|
|
290
290
|
line_num+=1
|
|
291
291
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
if not lines[line_num].startswith("def "):
|
|
293
|
+
raise ValueError(
|
|
294
|
+
"This action can only be applied to conventional functions, "
|
|
295
|
+
"not to instances of callable classes, not to lambda functions, "
|
|
296
|
+
"not to async functions.")
|
|
297
297
|
tree = ast.parse(normalized_source)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
298
|
+
if not isinstance(tree, ast.Module):
|
|
299
|
+
raise ValueError(
|
|
300
|
+
f"Only one high level function definition is allowed to be processed. "
|
|
301
|
+
f"The following code is not allowed: {normalized_source}")
|
|
302
|
+
if not isinstance(tree.body[0], ast.FunctionDef):
|
|
303
|
+
raise ValueError(
|
|
304
|
+
f"Only one high level function definition is allowed to be processed. "
|
|
305
|
+
f"The following code is not allowed: {normalized_source}")
|
|
306
|
+
if len(tree.body) != 1:
|
|
307
|
+
raise ValueError(
|
|
308
|
+
f"Only one high level function definition is allowed to be processed. "
|
|
309
|
+
f"The following code is not allowed: {normalized_source}")
|
|
307
310
|
analyzer = NamesUsageAnalyzer()
|
|
308
311
|
analyzer.visit(tree)
|
|
309
312
|
result = dict(tree=tree, analyzer=analyzer
|
|
@@ -68,8 +68,10 @@ class protected(autonomous):
|
|
|
68
68
|
bind the wrapped function to. If None, a suitable portal will be
|
|
69
69
|
inferred when fuction is called.
|
|
70
70
|
"""
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
if not (isinstance(portal, ProtectedCodePortal) or portal is None):
|
|
72
|
+
raise TypeError(f"portal must be a ProtectedCodePortal or None, got {type(portal).__name__}")
|
|
73
|
+
if not (isinstance(fixed_kwargs, dict) or fixed_kwargs is None):
|
|
74
|
+
raise TypeError(f"fixed_kwargs must be a dict or None, got {type(fixed_kwargs).__name__}")
|
|
73
75
|
autonomous.__init__(self=self
|
|
74
76
|
, portal=portal
|
|
75
77
|
, excessive_logging=excessive_logging
|
|
@@ -280,7 +280,7 @@ class ProtectedFn(AutonomousFn):
|
|
|
280
280
|
elif validation_result is None:
|
|
281
281
|
assert False, (f"Pre-validators failed for function {self.name}")
|
|
282
282
|
result = super().execute(**kwargs)
|
|
283
|
-
assert self.validate_execution_result(kw_args, result)
|
|
283
|
+
assert (self.validate_execution_result(kw_args, result) is VALIDATION_SUCCESSFUL)
|
|
284
284
|
return result
|
|
285
285
|
|
|
286
286
|
|
|
@@ -306,7 +306,8 @@ class ProtectedFn(AutonomousFn):
|
|
|
306
306
|
Raises:
|
|
307
307
|
TypeError: If an unexpected validator_type is provided.
|
|
308
308
|
"""
|
|
309
|
-
|
|
309
|
+
if validator_type not in {PreValidatorFn, PostValidatorFn}:
|
|
310
|
+
raise TypeError(f"validator_type must be PreValidatorFn or PostValidatorFn, got {validator_type}")
|
|
310
311
|
if validators is None:
|
|
311
312
|
return []
|
|
312
313
|
if not isinstance(validators, list):
|
|
@@ -314,7 +315,8 @@ class ProtectedFn(AutonomousFn):
|
|
|
314
315
|
or isinstance(validators, ValidatorFn)
|
|
315
316
|
or isinstance(validators, str)):
|
|
316
317
|
validators = [validators]
|
|
317
|
-
|
|
318
|
+
if not isinstance(validators, list):
|
|
319
|
+
raise TypeError(f"validators must be a list or compatible item(s); got type {type(validators).__name__}")
|
|
318
320
|
validators = flatten_list(validators)
|
|
319
321
|
new_validators = []
|
|
320
322
|
for validator in validators:
|
|
@@ -394,8 +396,10 @@ class ProtectedFnCallSignature(AutonomousFnCallSignature):
|
|
|
394
396
|
fn (ProtectedFn): The protected function to call.
|
|
395
397
|
arguments (dict): Keyword arguments to be passed at execution time.
|
|
396
398
|
"""
|
|
397
|
-
|
|
398
|
-
|
|
399
|
+
if not isinstance(fn, ProtectedFn):
|
|
400
|
+
raise TypeError(f"fn must be a ProtectedFn instance, got {type(fn).__name__}")
|
|
401
|
+
if not isinstance(arguments, dict):
|
|
402
|
+
raise TypeError(f"arguments must be a dict, got {type(arguments).__name__}")
|
|
399
403
|
super().__init__(fn, arguments)
|
|
400
404
|
|
|
401
405
|
@property
|
|
@@ -446,8 +450,8 @@ class ValidatorFn(AutonomousFn):
|
|
|
446
450
|
raise NotImplementedError("This method must be overridden")
|
|
447
451
|
|
|
448
452
|
|
|
449
|
-
def execute(self,**kwargs
|
|
450
|
-
|
|
453
|
+
def execute(self,**kwargs
|
|
454
|
+
) -> ProtectedFnCallSignature | ValidationSuccessFlag | None:
|
|
451
455
|
"""Execute the validator after verifying keyword arguments.
|
|
452
456
|
|
|
453
457
|
Args:
|
|
@@ -457,7 +461,10 @@ class ValidatorFn(AutonomousFn):
|
|
|
457
461
|
ProtectedFnCallSignature | ValidationSuccessFlag | None: Depending
|
|
458
462
|
on the validator type and outcome.
|
|
459
463
|
"""
|
|
460
|
-
|
|
464
|
+
expected = self.get_allowed_kwargs_names()
|
|
465
|
+
provided = set(kwargs)
|
|
466
|
+
if provided != expected:
|
|
467
|
+
raise ValueError(f"Invalid kwargs for {type(self).__name__}: expected {sorted(expected)}, got {sorted(provided)}")
|
|
461
468
|
return super().execute(**kwargs)
|
|
462
469
|
|
|
463
470
|
|
|
@@ -47,8 +47,8 @@ ASupportingFunc:TypeAlias = str | AutonomousFn
|
|
|
47
47
|
|
|
48
48
|
SupportingFuncs:TypeAlias = ASupportingFunc | List[ASupportingFunc] | None
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
_CACHED_EXECUTION_RESULTS_TXT = "Cached execution results"
|
|
51
|
+
_EXECUTION_QUEUE_SIZE_TXT = "Execution queue size"
|
|
52
52
|
|
|
53
53
|
class PureCodePortal(ProtectedCodePortal):
|
|
54
54
|
"""Portal that manages execution and caching for pure functions.
|
|
@@ -113,9 +113,9 @@ class PureCodePortal(ProtectedCodePortal):
|
|
|
113
113
|
all_params = [super().describe()]
|
|
114
114
|
|
|
115
115
|
all_params.append(_describe_persistent_characteristic(
|
|
116
|
-
|
|
116
|
+
_CACHED_EXECUTION_RESULTS_TXT, len(self._execution_results)))
|
|
117
117
|
all_params.append(_describe_persistent_characteristic(
|
|
118
|
-
|
|
118
|
+
_EXECUTION_QUEUE_SIZE_TXT, len(self._execution_requests)))
|
|
119
119
|
|
|
120
120
|
result = pd.concat(all_params)
|
|
121
121
|
result.reset_index(drop=True, inplace=True)
|
|
@@ -140,8 +140,10 @@ class PureFnCallSignature(ProtectedFnCallSignature):
|
|
|
140
140
|
fn: The pure function being called.
|
|
141
141
|
arguments: Keyword arguments for the call.
|
|
142
142
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
if not isinstance(fn, PureFn):
|
|
144
|
+
raise TypeError(f"fn must be a PureFn instance, got {type(fn).__name__}")
|
|
145
|
+
if not isinstance(arguments, dict):
|
|
146
|
+
raise TypeError(f"arguments must be a dict, got {type(arguments).__name__}")
|
|
145
147
|
super().__init__(fn, arguments)
|
|
146
148
|
|
|
147
149
|
@property
|
|
@@ -314,9 +316,11 @@ class PureFn(ProtectedFn):
|
|
|
314
316
|
list[PureFnExecutionResultAddr]: Addresses for all requested
|
|
315
317
|
executions, in the same order as the input list.
|
|
316
318
|
"""
|
|
317
|
-
|
|
319
|
+
if not isinstance(list_of_kwargs, (list, tuple)):
|
|
320
|
+
raise TypeError(f"list_of_kwargs must be a list or tuple, got {type(list_of_kwargs).__name__}")
|
|
318
321
|
for kwargs in list_of_kwargs:
|
|
319
|
-
|
|
322
|
+
if not isinstance(kwargs, dict):
|
|
323
|
+
raise TypeError(f"Each item in list_of_kwargs must be a dict, got {type(kwargs).__name__}")
|
|
320
324
|
with self.portal:
|
|
321
325
|
list_to_return = []
|
|
322
326
|
list_to_swarm = []
|
|
@@ -411,7 +415,8 @@ class PureFnExecutionResultAddr(HashAddr):
|
|
|
411
415
|
fn: The PureFn whose execution result is addressed.
|
|
412
416
|
arguments: The keyword arguments for the call (packed dict).
|
|
413
417
|
"""
|
|
414
|
-
|
|
418
|
+
if not isinstance(fn, PureFn):
|
|
419
|
+
raise TypeError(f"fn must be a PureFn instance, got {type(fn).__name__}")
|
|
415
420
|
with fn.portal as portal:
|
|
416
421
|
self._kwargs_cache = KwArgs(**arguments)
|
|
417
422
|
self._fn_cache = fn
|
|
@@ -530,7 +535,8 @@ class PureFnExecutionResultAddr(HashAddr):
|
|
|
530
535
|
be imported from a known non-active portal); False otherwise.
|
|
531
536
|
"""
|
|
532
537
|
if hasattr(self, "_ready_cache"):
|
|
533
|
-
|
|
538
|
+
if not self._ready_cache:
|
|
539
|
+
raise RuntimeError(f"Internal inconsistency: _ready_cache is set but False for address {self}")
|
|
534
540
|
return True
|
|
535
541
|
with self.fn.portal:
|
|
536
542
|
if self._ready_in_active_portal:
|
|
@@ -608,7 +614,8 @@ class PureFnExecutionResultAddr(HashAddr):
|
|
|
608
614
|
TimeoutError: If the timeout elapses before the value becomes
|
|
609
615
|
available.
|
|
610
616
|
"""
|
|
611
|
-
|
|
617
|
+
if timeout is not None and timeout < 0:
|
|
618
|
+
raise ValueError(f"timeout must be None or non-negative, got {timeout}")
|
|
612
619
|
if hasattr(self, "_result_cache"):
|
|
613
620
|
return self._result_cache
|
|
614
621
|
|
|
@@ -672,8 +679,8 @@ class PureFnExecutionResultAddr(HashAddr):
|
|
|
672
679
|
process is currently working on it, or if there were too many
|
|
673
680
|
past attempts to execute it. Otherwise, returns True.
|
|
674
681
|
"""
|
|
675
|
-
|
|
676
|
-
|
|
682
|
+
_DEFAULT_EXECUTION_TIME = 10 #TODO: move to portal config
|
|
683
|
+
_MAX_EXECUTION_ATTEMPTS = 5
|
|
677
684
|
# TODO: these should not be constants
|
|
678
685
|
if self.ready:
|
|
679
686
|
return False
|
|
@@ -682,13 +689,13 @@ class PureFnExecutionResultAddr(HashAddr):
|
|
|
682
689
|
n_past_attempts = len(past_attempts)
|
|
683
690
|
if n_past_attempts == 0:
|
|
684
691
|
return True
|
|
685
|
-
if n_past_attempts >
|
|
692
|
+
if n_past_attempts > _MAX_EXECUTION_ATTEMPTS:
|
|
686
693
|
#TODO: log this event. Should we have DLQ?
|
|
687
694
|
return False
|
|
688
695
|
most_recent_timestamp = max(
|
|
689
696
|
past_attempts.timestamp(a) for a in past_attempts)
|
|
690
697
|
current_timestamp = time.time()
|
|
691
698
|
if (current_timestamp - most_recent_timestamp
|
|
692
|
-
>
|
|
699
|
+
> _DEFAULT_EXECUTION_TIME*(2**n_past_attempts)):
|
|
693
700
|
return True
|
|
694
701
|
return False
|
|
@@ -42,11 +42,14 @@ def _recursion_pre_validator(
|
|
|
42
42
|
protected execution pipeline.
|
|
43
43
|
"""
|
|
44
44
|
unpacked_kwargs = packed_kwargs.unpack()
|
|
45
|
-
|
|
45
|
+
if param_name not in unpacked_kwargs:
|
|
46
|
+
raise KeyError(f"Missing required parameter '{param_name}' in kwargs")
|
|
46
47
|
fn = fn_addr.get()
|
|
47
48
|
param_value = unpacked_kwargs[param_name]
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
if not isinstance(param_value, int):
|
|
50
|
+
raise TypeError(f"Parameter '{param_name}' must be int, got {type(param_value).__name__}")
|
|
51
|
+
if param_value < 0:
|
|
52
|
+
raise ValueError(f"Parameter '{param_name}' must be non-negative, got {param_value}")
|
|
50
53
|
if param_value in {0,1}:
|
|
51
54
|
return pth.VALIDATION_SUCCESSFUL
|
|
52
55
|
|
|
@@ -90,7 +93,8 @@ def recursive_parameters(
|
|
|
90
93
|
"""
|
|
91
94
|
result = []
|
|
92
95
|
for name in args:
|
|
93
|
-
|
|
96
|
+
if not isinstance(name, str):
|
|
97
|
+
raise TypeError(f"recursive parameter names must be strings, got {type(name).__name__}")
|
|
94
98
|
validator = ComplexPreValidatorFn(
|
|
95
99
|
_recursion_pre_validator, fixed_kwargs=dict(param_name=name))
|
|
96
100
|
result.append(validator)
|
|
@@ -41,7 +41,7 @@ from multiprocessing import get_context
|
|
|
41
41
|
from .._090_swarming_portals.output_suppressor import (
|
|
42
42
|
OutputSuppressor)
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
_BACKGROUND_WORKERS_TXT = "Background workers"
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class SwarmingPortal(PureCodePortal):
|
|
@@ -115,15 +115,17 @@ class SwarmingPortal(PureCodePortal):
|
|
|
115
115
|
, p_consistency_checks=p_consistency_checks
|
|
116
116
|
, excessive_logging=excessive_logging)
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
if not isinstance(max_n_workers, (int, Joker)):
|
|
119
|
+
raise TypeError(f"max_n_workers must be int or Joker, got {type(max_n_workers).__name__}")
|
|
119
120
|
|
|
120
|
-
if parent_process_id is None
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
if (parent_process_id is None) != (parent_process_start_time is None):
|
|
122
|
+
raise RuntimeError(
|
|
123
|
+
f"parent_process_id and parent_process_start_time must both be None or both set; got id={parent_process_id}, start_time={parent_process_start_time}")
|
|
124
|
+
if parent_process_id is None:
|
|
125
|
+
self._auxiliary_config_params_at_init["max_n_workers"] = max_n_workers
|
|
125
126
|
else:
|
|
126
|
-
|
|
127
|
+
if max_n_workers != 0:
|
|
128
|
+
raise ValueError(f"In child context, max_n_workers must be 0, got {max_n_workers}")
|
|
127
129
|
|
|
128
130
|
compute_nodes_prototype = self._root_dict.get_subdict("compute_nodes")
|
|
129
131
|
compute_nodes_shared_params = compute_nodes_prototype.get_params()
|
|
@@ -262,7 +264,7 @@ class SwarmingPortal(PureCodePortal):
|
|
|
262
264
|
"""
|
|
263
265
|
all_params = [super().describe()]
|
|
264
266
|
all_params.append(_describe_runtime_characteristic(
|
|
265
|
-
|
|
267
|
+
_BACKGROUND_WORKERS_TXT, self.max_n_workers))
|
|
266
268
|
|
|
267
269
|
result = pd.concat(all_params)
|
|
268
270
|
result.reset_index(drop=True, inplace=True)
|
|
@@ -353,8 +355,8 @@ def _launch_many_background_workers(portal_init_jsparams:JsonSerializedObject) -
|
|
|
353
355
|
parent_process_start_time = get_current_process_start_time())
|
|
354
356
|
|
|
355
357
|
portal = parameterizable.loadjs(portal_init_jsparams)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
if not isinstance(portal, SwarmingPortal):
|
|
359
|
+
raise TypeError(f"Expected SwarmingPortal, got {type(portal).__name__}")
|
|
358
360
|
summary = build_execution_environment_summary()
|
|
359
361
|
portal._compute_nodes.json[portal._execution_environment_address] = summary
|
|
360
362
|
|
|
@@ -402,7 +404,8 @@ def _background_worker(portal_init_jsparams:JsonSerializedObject) -> None:
|
|
|
402
404
|
reconstructing a SwarmingPortal in child context.
|
|
403
405
|
"""
|
|
404
406
|
portal = parameterizable.loadjs(portal_init_jsparams)
|
|
405
|
-
|
|
407
|
+
if not isinstance(portal, SwarmingPortal):
|
|
408
|
+
raise TypeError(f"Expected SwarmingPortal, got {type(portal).__name__}")
|
|
406
409
|
with portal:
|
|
407
410
|
ctx = get_context("spawn")
|
|
408
411
|
with OutputSuppressor():
|
|
@@ -433,7 +436,8 @@ def _process_random_execution_request(portal_init_jsparams:JsonSerializedObject)
|
|
|
433
436
|
portal_init_jsparams = update_jsparams(
|
|
434
437
|
portal_init_jsparams, max_n_workers=0)
|
|
435
438
|
portal = parameterizable.loadjs(portal_init_jsparams)
|
|
436
|
-
|
|
439
|
+
if not isinstance(portal, SwarmingPortal):
|
|
440
|
+
raise TypeError(f"Expected SwarmingPortal, got {type(portal).__name__}")
|
|
437
441
|
with portal:
|
|
438
442
|
call_signature:PureFnCallSignature|None = None
|
|
439
443
|
while True:
|
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
pythagoras/.DS_Store,sha256=4fJ-j5kHYgNLz5ki6QI2jkwoTuthfKswojnfe4naJ8U,8196
|
|
2
|
-
pythagoras/_010_basic_portals/__init__.py,sha256=
|
|
3
|
-
pythagoras/_010_basic_portals/basic_portal_core_classes.py,sha256=
|
|
4
|
-
pythagoras/_010_basic_portals/exceptions.py,sha256=CMtSyRb47YKZtANN-iCQBvvyYPKdKqomLygRE1fjSNk,1248
|
|
2
|
+
pythagoras/_010_basic_portals/__init__.py,sha256=fX7p16EhJErnO2uN4HIu-F1OdfmZ24IMc0qBg9hXgrw,1642
|
|
3
|
+
pythagoras/_010_basic_portals/basic_portal_core_classes.py,sha256=6ogcdLWuEG9KRgBBW2nXoN-6_sj2Yvj9trPcmx0E7xA,24709
|
|
5
4
|
pythagoras/_010_basic_portals/long_infoname.py,sha256=KXOmHfQ_5hdZNqfB3Cif2CQiZ3XI3UAOEXKl3DLLYF4,1366
|
|
6
|
-
pythagoras/_010_basic_portals/portal_tester.py,sha256=
|
|
5
|
+
pythagoras/_010_basic_portals/portal_tester.py,sha256=icm9ZsmYg3l0DAKC3l4vkCB76jthHWyR_8D9aiSVkuQ,3452
|
|
7
6
|
pythagoras/_010_basic_portals/post_init_metaclass.py,sha256=94FEVMCJBUReRb-fo2-LW8YWXUXw5lLLYlXMnlxHJuU,1495
|
|
8
|
-
pythagoras/_020_ordinary_code_portals/__init__.py,sha256=
|
|
9
|
-
pythagoras/_020_ordinary_code_portals/code_normalizer.py,sha256=
|
|
10
|
-
pythagoras/_020_ordinary_code_portals/
|
|
11
|
-
pythagoras/_020_ordinary_code_portals/
|
|
12
|
-
pythagoras/_020_ordinary_code_portals/
|
|
7
|
+
pythagoras/_020_ordinary_code_portals/__init__.py,sha256=z0-HUn9IWmL0c1FOmFlMzk0THBYwJuDw6FXpV8YeGUM,1321
|
|
8
|
+
pythagoras/_020_ordinary_code_portals/code_normalizer.py,sha256=vqZ93CXeHBAgiQe_0LZ7qRZ4eT4ZSZit04DjkDWTgGw,6973
|
|
9
|
+
pythagoras/_020_ordinary_code_portals/exceptions.py,sha256=hYvvXXANTRFKnRKGrOFZjIgwDwRXaUA9pxUmOSAOyog,274
|
|
10
|
+
pythagoras/_020_ordinary_code_portals/function_processing.py,sha256=ZlA9yVmZTa6Q_LxDoZidK8QJZkrqiFnX89xu45LwJ44,4931
|
|
11
|
+
pythagoras/_020_ordinary_code_portals/ordinary_decorator.py,sha256=O0WGZYMKtrf2oT8j7GYsrmkQ7PYjiDfTQrJxpjdzSkA,1545
|
|
12
|
+
pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py,sha256=6QkaBj0gigA7n0xW70KzSmo5Bt8tY_UpmQE5g1lHMLU,16050
|
|
13
13
|
pythagoras/_030_data_portals/__init__.py,sha256=f_F9DCmuVgPMgzwRjuNj6FI63S3oXu7lj3zU66Nw7Hc,1427
|
|
14
|
-
pythagoras/_030_data_portals/data_portal_core_classes.py,sha256=
|
|
14
|
+
pythagoras/_030_data_portals/data_portal_core_classes.py,sha256=mGOX2OLPiEXyIuBMR66IM1lVZS32Q0Q4NxbFwqPWeGc,22711
|
|
15
15
|
pythagoras/_030_data_portals/ready_and_get.py,sha256=lgkDygF4lFnZXcCvP5dmvzQX64wrZ8AnYJTI7v2ppng,4172
|
|
16
|
-
pythagoras/_030_data_portals/storable_decorator.py,sha256=
|
|
16
|
+
pythagoras/_030_data_portals/storable_decorator.py,sha256=zoJec_HBHFI5PBrF1khkPjaiviKTgdglr2F5HB42ySE,1292
|
|
17
17
|
pythagoras/_040_logging_code_portals/__init__.py,sha256=q2hVyOVgE-9Ru3ycilK98YS9Rat8tSc6erd7AtGxpaA,996
|
|
18
18
|
pythagoras/_040_logging_code_portals/exception_processing_tracking.py,sha256=DH1eeJAeVL6Fn-6sXBqx3Ocu2RXxVhLqdH1mvhM24VY,1850
|
|
19
19
|
pythagoras/_040_logging_code_portals/execution_environment_summary.py,sha256=-0ynNibGfRh3J1Sq-N9j7eN1FlGvetEBiW0L4K_qJ30,3813
|
|
20
20
|
pythagoras/_040_logging_code_portals/kw_args.py,sha256=j3Iao80gTBjPx6dk1Azd9D8pcdQ3QdfpkQtQq-4ATV0,4954
|
|
21
|
-
pythagoras/_040_logging_code_portals/logging_decorator.py,sha256
|
|
22
|
-
pythagoras/_040_logging_code_portals/logging_portal_core_classes.py,sha256=
|
|
21
|
+
pythagoras/_040_logging_code_portals/logging_decorator.py,sha256=byYawCcdu8fOYrz30faLuq3XlhPOSypC6YcUpuBlAyc,1985
|
|
22
|
+
pythagoras/_040_logging_code_portals/logging_portal_core_classes.py,sha256=nCYWp7u40kiek70i-l5uawZOaRQko1kJJweOdBQtzxk,35232
|
|
23
23
|
pythagoras/_040_logging_code_portals/notebook_checker.py,sha256=qO7zfMC20hM4tSxlqB7gy6WI4imWX4Xl7ojSwgeVu0A,871
|
|
24
24
|
pythagoras/_040_logging_code_portals/output_capturer.py,sha256=ohCp6qqxL7IuJGfnFuCIgj5Oc4HmC8c7uZGE_uzWkZk,4216
|
|
25
25
|
pythagoras/_040_logging_code_portals/uncaught_exceptions.py,sha256=vQrY1mOYdAeKaCmCCY1MUy4xoXurQkfwQuDA43giPl0,4564
|
|
26
26
|
pythagoras/_050_safe_code_portals/__init__.py,sha256=YR-V6W2WZ17SjqmTyY2xdY16xTVEEuLs2MddJj_WCZU,557
|
|
27
|
-
pythagoras/_050_safe_code_portals/safe_decorator.py,sha256=
|
|
28
|
-
pythagoras/_050_safe_code_portals/safe_portal_core_classes.py,sha256=
|
|
27
|
+
pythagoras/_050_safe_code_portals/safe_decorator.py,sha256=F0d_UXuiF0S7FGYYmjkBgIuSd5iBNn6Ty7CrHV65_4I,1785
|
|
28
|
+
pythagoras/_050_safe_code_portals/safe_portal_core_classes.py,sha256=1qMwR5RC4jzQVrcZ92ywHiDfCTFVzwPYZdPV1wH3a08,5850
|
|
29
29
|
pythagoras/_060_autonomous_code_portals/__init__.py,sha256=hnv_dxxRx8c7IDf1QgVYHfYoeVAz8oD9K0oWI_o9N20,1704
|
|
30
|
-
pythagoras/_060_autonomous_code_portals/autonomous_decorators.py,sha256=
|
|
31
|
-
pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py,sha256=
|
|
32
|
-
pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py,sha256=
|
|
30
|
+
pythagoras/_060_autonomous_code_portals/autonomous_decorators.py,sha256=wJ4Dm0Vnvfv_TlwJKTBrLfistAH2V85zk9xxOF0NWk0,4367
|
|
31
|
+
pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py,sha256=1T5zcujRz3scWlzV6OGwK7GZJpNf-mvFILjOuw6Q4rk,10377
|
|
32
|
+
pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py,sha256=CZzYo3vII3JdpPoAP9Rny3lYiK48zuw981A76NWGblQ,12020
|
|
33
33
|
pythagoras/_070_protected_code_portals/__init__.py,sha256=TvGcJaz20Qqsmv8m2pA4duBtFn_CdCKfkSbOSFoJS8k,989
|
|
34
34
|
pythagoras/_070_protected_code_portals/basic_pre_validators.py,sha256=N5E1Gz_ULafZ8RQ1MvCQ6y29rfRVTQw7NZTqZ-pMOPo,6570
|
|
35
35
|
pythagoras/_070_protected_code_portals/fn_arg_names_checker.py,sha256=HZJVhdyX2xNw0e7S9Wyz0jDun-5xBuhM71Tw2zHbYXc,1603
|
|
36
36
|
pythagoras/_070_protected_code_portals/list_flattener.py,sha256=9V1Xj_y5nOCXS2V9mcBFX6UsyDdOR108SBqxbC-Ziyk,1604
|
|
37
37
|
pythagoras/_070_protected_code_portals/package_manager.py,sha256=KbvEGfeKQsWIz0UogVUHfW6enbBmnqo1OjJz1xMTL4o,5437
|
|
38
|
-
pythagoras/_070_protected_code_portals/protected_decorators.py,sha256=
|
|
39
|
-
pythagoras/_070_protected_code_portals/protected_portal_core_classes.py,sha256=
|
|
38
|
+
pythagoras/_070_protected_code_portals/protected_decorators.py,sha256=U3rz4qab9XvcVBPMeS85GOpysERBpIGRnNRXTX-YUxc,4436
|
|
39
|
+
pythagoras/_070_protected_code_portals/protected_portal_core_classes.py,sha256=oygVKoXPVci8OhpiBAvgT1Uemj3COQSTx8-BUhdHQbg,24042
|
|
40
40
|
pythagoras/_070_protected_code_portals/system_utils.py,sha256=Uv111FaO33xAA9wZ2iwtW8Gf-FXJBP2ld1sMBmvsHdo,5124
|
|
41
41
|
pythagoras/_070_protected_code_portals/validation_succesful_const.py,sha256=DrM-Mf6dDLFJ7AmfzntD39Z23YMFfF6am78XU54AlnM,577
|
|
42
42
|
pythagoras/_080_pure_code_portals/__init__.py,sha256=m9fC8hK4ve5VOJJKywAbUcyZJ9v5KiovI0O-HsXZHCg,1302
|
|
43
|
-
pythagoras/_080_pure_code_portals/pure_core_classes.py,sha256=
|
|
43
|
+
pythagoras/_080_pure_code_portals/pure_core_classes.py,sha256=6UjCU28hDWD1CFeyzl9wFAWtRHc1Hea7yiCxV6-JLaI,27070
|
|
44
44
|
pythagoras/_080_pure_code_portals/pure_decorator.py,sha256=rmvtw_RpTbLjPTfy_2rdjfNSvV735hzTV_9q3drQKew,4036
|
|
45
|
-
pythagoras/_080_pure_code_portals/recursion_pre_validator.py,sha256=
|
|
45
|
+
pythagoras/_080_pure_code_portals/recursion_pre_validator.py,sha256=Wb8ZcAO1xlfOUci8EEklToF54Uqmwya6EKmk7DVYcOg,3833
|
|
46
46
|
pythagoras/_090_swarming_portals/__init__.py,sha256=TuA17PftTBudptAblNtBlD46BqUiitksOtf3y01QKm0,514
|
|
47
47
|
pythagoras/_090_swarming_portals/output_suppressor.py,sha256=IspRrfLt6pI9iuD8I1dfpnQLpRi5sO11pP5QvIGVoMo,1581
|
|
48
|
-
pythagoras/_090_swarming_portals/swarming_portals.py,sha256=
|
|
48
|
+
pythagoras/_090_swarming_portals/swarming_portals.py,sha256=Dx9aUnfqlb_rSd2YORiImmuTgbPEMITGRJ6JPQts45E,20197
|
|
49
49
|
pythagoras/_100_top_level_API/__init__.py,sha256=Jt6VWVCBygqnwl7_-s-jhdYp6masO_SuM2xQP4a96tk,505
|
|
50
50
|
pythagoras/_100_top_level_API/default_local_portal.py,sha256=SnykTpTXg1KuT1qwDnrAZ63lYshMy-0nNiUgoOVMxCs,339
|
|
51
51
|
pythagoras/_100_top_level_API/top_level_API.py,sha256=S2NXW4bfL98o6Txn6NM0EeBb1nzwFtPSl-yWNevAQIE,906
|
|
@@ -59,6 +59,6 @@ pythagoras/_900_project_stats_collector/__init__.py,sha256=Eagt-BhPPtBGgpMywx2lk
|
|
|
59
59
|
pythagoras/_900_project_stats_collector/project_analyzer.py,sha256=uhycFKjUIXEpYcZYnak3yn4JFhchl-oZ7wt6spFxhoY,3574
|
|
60
60
|
pythagoras/__init__.py,sha256=TMPtJdSi_WShCpJnsVVdO48Wcvs78GMbUi5gHc1eMLw,1233
|
|
61
61
|
pythagoras/core/__init__.py,sha256=yfamQZNt_7FL7vfxSoCTZq2ZL6rupp6qzcp9CHhTj3E,2674
|
|
62
|
-
pythagoras-0.24.
|
|
63
|
-
pythagoras-0.24.
|
|
64
|
-
pythagoras-0.24.
|
|
62
|
+
pythagoras-0.24.10.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
|
|
63
|
+
pythagoras-0.24.10.dist-info/METADATA,sha256=xImNZyZ7Laa9pqFAK0ICotHH5jN_aezdP1zey8WRZRU,7468
|
|
64
|
+
pythagoras-0.24.10.dist-info/RECORD,,
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
class PythagorasException(Exception):
|
|
2
|
-
"""Base exception class for all Pythagoras-related errors.
|
|
3
|
-
|
|
4
|
-
This is the base class for all custom exceptions raised by the Pythagoras
|
|
5
|
-
library. It provides a common interface and functionality for all
|
|
6
|
-
Pythagoras-specific error conditions.
|
|
7
|
-
|
|
8
|
-
Attributes:
|
|
9
|
-
message: A human-readable description of the error.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
def __init__(self, message):
|
|
13
|
-
"""Initialize the exception with an error message.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
message: A string describing the error that occurred.
|
|
17
|
-
"""
|
|
18
|
-
self.message = message
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class NonCompliantFunction(PythagorasException):
|
|
22
|
-
"""Exception raised when a function does not comply with Pythagoras requirements.
|
|
23
|
-
|
|
24
|
-
This exception is raised when a function fails to meet the compliance
|
|
25
|
-
requirements for use within the Pythagoras framework. This typically
|
|
26
|
-
occurs when functions cannot be properly serialized, analyzed, or
|
|
27
|
-
executed within the portal environment.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
def __init__(self, message):
|
|
31
|
-
"""Initialize the exception with an error message.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
message: A string describing why the function is non-compliant.
|
|
35
|
-
"""
|
|
36
|
-
PythagorasException.__init__(self, message)
|
|
File without changes
|