pythagoras 0.24.3__tar.gz → 0.24.6__tar.gz

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