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.
Files changed (27) hide show
  1. pythagoras/_010_basic_portals/__init__.py +0 -1
  2. pythagoras/_010_basic_portals/basic_portal_core_classes.py +21 -15
  3. pythagoras/_010_basic_portals/portal_tester.py +3 -2
  4. pythagoras/_020_ordinary_code_portals/__init__.py +1 -0
  5. pythagoras/_020_ordinary_code_portals/code_normalizer.py +14 -14
  6. pythagoras/_020_ordinary_code_portals/exceptions.py +11 -0
  7. pythagoras/_020_ordinary_code_portals/function_processing.py +10 -10
  8. pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +2 -1
  9. pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +11 -9
  10. pythagoras/_030_data_portals/data_portal_core_classes.py +13 -11
  11. pythagoras/_030_data_portals/storable_decorator.py +3 -1
  12. pythagoras/_040_logging_code_portals/logging_decorator.py +4 -2
  13. pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +14 -13
  14. pythagoras/_050_safe_code_portals/safe_decorator.py +2 -1
  15. pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +4 -2
  16. pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +5 -3
  17. pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +23 -17
  18. pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +18 -15
  19. pythagoras/_070_protected_code_portals/protected_decorators.py +4 -2
  20. pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +15 -8
  21. pythagoras/_080_pure_code_portals/pure_core_classes.py +22 -15
  22. pythagoras/_080_pure_code_portals/recursion_pre_validator.py +8 -4
  23. pythagoras/_090_swarming_portals/swarming_portals.py +17 -13
  24. {pythagoras-0.24.8.dist-info → pythagoras-0.24.10.dist-info}/METADATA +1 -1
  25. {pythagoras-0.24.8.dist-info → pythagoras-0.24.10.dist-info}/RECORD +26 -26
  26. pythagoras/_010_basic_portals/exceptions.py +0 -36
  27. {pythagoras-0.24.8.dist-info → pythagoras-0.24.10.dist-info}/WHEEL +0 -0
@@ -32,7 +32,6 @@ be subclassed to provide additional functionality.
32
32
  """
33
33
 
34
34
  from .post_init_metaclass import *
35
- from .exceptions import *
36
35
  from .basic_portal_core_classes import *
37
36
  from .portal_tester import _PortalTester
38
37
  from .long_infoname import *
@@ -65,8 +65,8 @@ def _describe_runtime_characteristic(name, value) -> pd.DataFrame:
65
65
  return pd.DataFrame(d)
66
66
 
67
67
 
68
- BASE_DIRECTORY_TXT = "Base directory"
69
- BACKEND_TYPE_TXT = "Backend type"
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
- assert isinstance(target_directory_str, str)
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
- assert isinstance(obj_str_id, str)
284
- assert isinstance(portal_str_id, str)
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
- BASE_DIRECTORY_TXT, self._root_dict.base_dir))
392
+ _BASE_DIRECTORY_TXT, self._root_dict.base_dir))
390
393
  all_params.append(_describe_persistent_characteristic(
391
- BACKEND_TYPE_TXT, self._root_dict.__class__.__name__))
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
- assert _active_portals_stack[-1] == self, (
415
- "Inconsistent state of the portal stack. "
416
- + "Most probably, portal.__enter__() method was called explicitly "
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
- assert portal is None or isinstance(portal, BasicPortal)
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
- assert isinstance(portal_str_id, str)
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
- assert self._str_id in _all_activated_portal_aware_objects
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
- assert self.is_activated
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
- AssertionError: If portal_class is not a subclass of BasicPortal.
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
- assert issubclass(portal_class, BasicPortal)
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
@@ -28,4 +28,5 @@ from .ordinary_portal_core_classes import *
28
28
  from .function_processing import *
29
29
  from .ordinary_decorator import *
30
30
  from .code_normalizer import *
31
+ from .exceptions import *
31
32
 
@@ -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
- NonCompliantFunction: If the function has multiple decorators when
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
- 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.
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
- assert callable(a_func) or isinstance(a_func, str)
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
- assert line.startswith(chars_to_remove)
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
- assert isinstance(code_ast, ast.Module)
101
- assert isinstance(code_ast.body[0], ast.FunctionDef), (
102
- f"{type(code_ast.body[0])=}")
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 NonCompliantFunction(
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
- assert pth_dec_counter == 1
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 .._010_basic_portals.exceptions import NonCompliantFunction
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
- NonCompliantFunction: If the callable violates any of the ordinarity
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 NonCompliantFunction(f"{name} must be callable.")
107
+ raise FunctionError(f"{name} must be callable.")
108
108
 
109
109
  if not inspect.isfunction(a_func):
110
- raise NonCompliantFunction(f"The function {name} is not ordinary."
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 NonCompliantFunction(f"The function {name} can't be "
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 NonCompliantFunction(f"The function {name} can't be a closure,"
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 NonCompliantFunction(f"The function {name} can't be lambda,"
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 NonCompliantFunction("Pythagoras only allows functions "
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 NonCompliantFunction(f"The function {name} can't be "
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 NonCompliantFunction(f"The function {name} can't have "
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
- assert portal is None or isinstance(portal, OrdinaryCodePortal)
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
- NonCompliantFunction: If the function is not compliant with Pythagoras'
36
+ FunctionError: If the function is not compliant with Pythagoras'
36
37
  ordinarity rules or multiple decorators are present.
37
- AssertionError: If input type is invalid or integrity checks fail.
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
- REGISTERED_FUNCTIONS_TXT = "Registered functions"
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
- REGISTERED_FUNCTIONS_TXT, self.get_number_of_linked_functions()))
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
- NonCompliantFunction: If the provided function source is not
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
- AssertionError: If positional arguments are supplied.
372
+ TypeError: If positional arguments are supplied.
372
373
  """
373
- assert len(args) == 0, (f"Function {self.name} can't"
374
- + " be called with positional arguments,"
375
- + " only keyword arguments are allowed.")
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
- TOTAL_VALUES_TXT = "Values, total"
21
- PROBABILITY_OF_CHECKS_TXT = "Probability of consistency checks"
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
- AssertionError: If the active portal is not an instance of DataPortal.
31
+ TypeError: If the active portal is not an instance of DataPortal.
32
32
  """
33
33
  portal = get_active_portal()
34
- assert isinstance(portal, DataPortal)
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
- AssertionError: If any returned portal is not an instance of DataPortal.
47
+ TypeError: If any returned portal is not an instance of DataPortal.
47
48
  """
48
49
  portals = get_nonactive_portals()
49
- assert all(isinstance(p, DataPortal) for p in portals)
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
- TOTAL_VALUES_TXT, len(self._value_store)))
217
+ _TOTAL_VALUES_TXT, len(self._value_store)))
215
218
  all_params.append(_describe_runtime_characteristic(
216
- PROBABILITY_OF_CHECKS_TXT, self.p_consistency_checks))
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
- assert not isinstance(data, HashAddr), (
497
- "get_ValueAddr is the only way to "
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
- assert isinstance(portal, DataPortal) or portal is None
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
- assert isinstance(excessive_logging, (bool,Joker))
31
- assert isinstance(portal, LoggingCodePortal) or portal is None
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
- assert isinstance(fn, LoggingFn)
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
- assert not self.context_used, (
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
- assert self.exception_counter == 0, (
736
- "An instance of PureFnExecutionFrame can be used only once.")
737
- assert self.event_counter == 0, (
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
- EXCEPTIONS_TOTAL_TXT = "Exceptions, total"
801
- EXCEPTIONS_TODAY_TXT = "Exceptions, today"
802
- EXCESSIVE_LOGGING_TXT = "Excessive logging"
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
- EXCEPTIONS_TOTAL_TXT, len(self._crash_history)))
925
+ _EXCEPTIONS_TOTAL_TXT, len(self._crash_history)))
925
926
  all_params.append(_describe_persistent_characteristic(
926
- EXCEPTIONS_TODAY_TXT
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
- EXCESSIVE_LOGGING_TXT, self.excessive_logging))
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
- assert isinstance(portal, SafeCodePortal) or portal is None
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
- assert isinstance(fn, SafeFn)
76
- assert isinstance(arguments, dict)
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
- AssertionError: If portal is not an AutonomousCodePortal or None, or
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
- assert isinstance(portal, AutonomousCodePortal) or portal is None
81
- assert isinstance(fixed_kwargs, dict) or fixed_kwargs is None
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
- assert isinstance(fn, AutonomousFn)
55
- assert isinstance(arguments, dict)
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
- AssertionError: If static analysis detects violations of autonomy
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
- assert self.source_code == normalized_source
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
- assert len(nonlocal_names) == 0, (f"Function {self.name}"
121
- + f" is not autonomous, it uses external nonlocal"
122
- + f" objects: {analyzer.names.explicitly_nonlocal_unbound_deep}")
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
- assert analyzer.n_yelds == 0, (f"Function {self.name}"
125
- + f" is not autonomous, it uses yield statements")
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
- assert len(import_required) == 0, (f"Function {self.name}"
138
- + f" is not autonomous, it uses global"
139
- + f" objects {import_required}"
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
- AssertionError: If provided kwargs overlap with fixed kwargs.
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
- assert len(overlapping_keys) == 0
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
- AssertionError: If any of the provided kwargs overlap with already
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
- assert len(overlapping_keys) == 0
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
- AssertionError: If the input is not a single regular function (e.g., a
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
- assert lines[line_num].startswith("def "),("This action can only"
293
- + " be applied to conventional functions,"
294
- + " not to instances of callable classes, "
295
- + " not to lambda functions, "
296
- + " not to async functions.")
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
- assert isinstance(tree, ast.Module), (f"Only one high lever"
299
- + f" function definition is allowed to be processed."
300
- + f" The following code is not allowed: {normalized_source}")
301
- assert isinstance(tree.body[0], ast.FunctionDef), (f"Only one high lever"
302
- + f" function definition is allowed to be processed."
303
- + f" The following code is not allowed: {normalized_source}")
304
- assert len(tree.body)==1, (f"Only one high lever"
305
- + f" function definition is allowed to be processed."
306
- + f" The following code is not allowed: {normalized_source}")
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
- assert isinstance(portal, ProtectedCodePortal) or portal is None
72
- assert isinstance(fixed_kwargs, dict) or fixed_kwargs is None
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
- assert validator_type in {PreValidatorFn, PostValidatorFn}
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
- assert isinstance(validators, list)
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
- assert isinstance(fn, ProtectedFn)
398
- assert isinstance(arguments, dict)
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
- -> ProtectedFnCallSignature | ValidationSuccessFlag | None:
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
- assert set(kwargs) == self.get_allowed_kwargs_names()
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
- CACHED_EXECUTION_RESULTS_TXT = "Cached execution results"
51
- EXECUTION_QUEUE_SIZE_TXT = "Execution queue size"
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
- CACHED_EXECUTION_RESULTS_TXT, len(self._execution_results)))
116
+ _CACHED_EXECUTION_RESULTS_TXT, len(self._execution_results)))
117
117
  all_params.append(_describe_persistent_characteristic(
118
- EXECUTION_QUEUE_SIZE_TXT, len(self._execution_requests)))
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
- assert isinstance(fn, PureFn)
144
- assert isinstance(arguments, dict)
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
- assert isinstance(list_of_kwargs, (list, tuple))
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
- assert isinstance(kwargs, dict)
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
- assert isinstance(fn, PureFn)
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
- assert self._ready_cache
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
- assert timeout is None or timeout >= 0
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
- DEFAULT_EXECUTION_TIME = 10 #TODO: move to portal config
676
- MAX_EXECUTION_ATTEMPTS = 5
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 > MAX_EXECUTION_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
- > DEFAULT_EXECUTION_TIME*(2**n_past_attempts)):
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
- assert param_name in unpacked_kwargs
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
- assert isinstance(param_value, int)
49
- assert param_value >= 0
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
- assert isinstance(name, str)
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
- BACKGROUND_WORKERS_TXT = "Background workers"
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
- assert isinstance(max_n_workers, (int, Joker))
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 or parent_process_start_time is None:
121
- assert parent_process_id is None
122
- assert parent_process_start_time is None
123
- self._auxiliary_config_params_at_init["max_n_workers"
124
- ] = max_n_workers
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
- assert max_n_workers == 0
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
- BACKGROUND_WORKERS_TXT, self.max_n_workers))
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
- assert isinstance(portal, SwarmingPortal)
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
- assert isinstance(portal, SwarmingPortal)
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
- assert isinstance(portal, SwarmingPortal)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythagoras
3
- Version: 0.24.8
3
+ Version: 0.24.10
4
4
  Summary: Planet-scale distributed computing in Python.
5
5
  Keywords: cloud,ML,AI,serverless,distributed,parallel,machine-learning,deep-learning,pythagoras
6
6
  Author: Volodymyr (Vlad) Pavlov
@@ -1,51 +1,51 @@
1
1
  pythagoras/.DS_Store,sha256=4fJ-j5kHYgNLz5ki6QI2jkwoTuthfKswojnfe4naJ8U,8196
2
- pythagoras/_010_basic_portals/__init__.py,sha256=L6vahRANApOIIdzw3wuAIZtJRijepneOevXnlhrZmj4,1668
3
- pythagoras/_010_basic_portals/basic_portal_core_classes.py,sha256=UcvgxajJdtb7bgNkhUrehjN6Hd39iCNFtVqeC71qYoU,23979
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=x6HiJ3GW9XWplnsT6Ob7QCy2J_JPgGpdaJ8QRyFH-e8,3353
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=p3kSqaQYj0xlhk9BwptFgA1USdTbfHkAB5Q9MH-ANI0,1295
9
- pythagoras/_020_ordinary_code_portals/code_normalizer.py,sha256=GJXvBX85W230OcwNAg8XXHAE4m39Q70piJ9g2jcIr4Q,6616
10
- pythagoras/_020_ordinary_code_portals/function_processing.py,sha256=b9Y4vNf1KSRkpUfS9UmWj3N_CNuPDgUMXdnR3IeyAoM,5021
11
- pythagoras/_020_ordinary_code_portals/ordinary_decorator.py,sha256=ETmy-HKRzCJfzTPOI-gelQT2tlJpVLRu4I85yv02vuo,1437
12
- pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py,sha256=C5nobGkYZBi2IHc1Pjfxjx_8h_ajF3Sf9Jq3yxFIR68,15963
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=D_Zg-q15II7IwweEGdBAMhGt09DYN_v_G0XFb5K3WB4,22459
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=u70K8J44NTlofc4HmKHtxxbwKdUkBz6BDsIWds4dALQ,1160
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=-BGduV2U5SJ40qW8afRCUMrzYv5Pf_CgB4HlSEGDmlA,1766
22
- pythagoras/_040_logging_code_portals/logging_portal_core_classes.py,sha256=PPtYlI9PB9N3A8WrC5HrLD7MijofZWqUpaQgbwOqEN4,35094
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=AYvX7-km2reRMZ55ndO_2IS2SfHbnpyFv79AVwGg7Po,1681
28
- pythagoras/_050_safe_code_portals/safe_portal_core_classes.py,sha256=naY4R91N5bcCq8C_-YeBqhrr6UG8zkEQ5t8C3O8ytOw,5673
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=RFNHplYQR9L2w7YxDu7A2nqpvkReAF9cEU-76neXkNQ,4155
31
- pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py,sha256=Nj4TPML94eS0WThoOfaqZhlPJu6RttvDg69VT86WPlg,9881
32
- pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py,sha256=arfAuFBY4Dx9Zmf0b3x-axrd35QY2Kg_2jHJC8ek3p8,11977
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=BrVcFiMHR4nD_dZgmlGkkMi-7ee3FqYFRCwl_nT35-E,4221
39
- pythagoras/_070_protected_code_portals/protected_portal_core_classes.py,sha256=nclgUd02KySQ1ffFSvLWMbonGcbq0SpJy2x3jLw5sAU,23419
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=GniCEvJtEwYsqIxtz0QVh8wcgTHoisVuBKkIJKkz144,26381
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=qrVBK0U6YM08tQ4rsTlKNkGbqmF3Wj2yGrnWLCAP7BY,3461
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=aLHo8iFjEmd-3psFOkiYg9vCKApUD_Wv0-poxQEFu1U,19624
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.8.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
63
- pythagoras-0.24.8.dist-info/METADATA,sha256=yrcNe2LC_-vrLl3BsyPGTyPOJPjmjBMByp2JqDjXpZ4,7467
64
- pythagoras-0.24.8.dist-info/RECORD,,
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)