pythagoras 0.23.17__tar.gz → 0.23.19__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 (70) hide show
  1. {pythagoras-0.23.17 → pythagoras-0.23.19}/PKG-INFO +8 -14
  2. {pythagoras-0.23.17 → pythagoras-0.23.19}/README.md +7 -13
  3. {pythagoras-0.23.17 → pythagoras-0.23.19}/pyproject.toml +1 -1
  4. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_010_basic_portals/basic_portal_core_classes.py +149 -24
  5. pythagoras-0.23.19/src/pythagoras/_010_basic_portals/exceptions.py +36 -0
  6. pythagoras-0.23.19/src/pythagoras/_010_basic_portals/long_infoname.py +39 -0
  7. pythagoras-0.23.19/src/pythagoras/_010_basic_portals/not_picklable_class.py +36 -0
  8. pythagoras-0.23.19/src/pythagoras/_010_basic_portals/portal_tester.py +92 -0
  9. pythagoras-0.23.19/src/pythagoras/_010_basic_portals/post_init_metaclass.py +34 -0
  10. pythagoras-0.23.17/src/pythagoras/_010_basic_portals/exceptions.py +0 -8
  11. pythagoras-0.23.17/src/pythagoras/_010_basic_portals/long_infoname.py +0 -24
  12. pythagoras-0.23.17/src/pythagoras/_010_basic_portals/not_picklable_class.py +0 -8
  13. pythagoras-0.23.17/src/pythagoras/_010_basic_portals/portal_tester.py +0 -57
  14. pythagoras-0.23.17/src/pythagoras/_010_basic_portals/post_init_metaclass.py +0 -13
  15. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/.DS_Store +0 -0
  16. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_010_basic_portals/__init__.py +0 -0
  17. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_020_ordinary_code_portals/__init__.py +0 -0
  18. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_020_ordinary_code_portals/code_normalizer.py +0 -0
  19. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_020_ordinary_code_portals/function_processing.py +0 -0
  20. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +0 -0
  21. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +0 -0
  22. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_030_data_portals/__init__.py +0 -0
  23. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_030_data_portals/data_portal_core_classes.py +0 -0
  24. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_030_data_portals/ready_and_get.py +0 -0
  25. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_030_data_portals/storable_decorator.py +0 -0
  26. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/__init__.py +0 -0
  27. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/exception_processing_tracking.py +0 -0
  28. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/execution_environment_summary.py +0 -0
  29. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/kw_args.py +0 -0
  30. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/logging_decorator.py +0 -0
  31. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +0 -0
  32. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/notebook_checker.py +0 -0
  33. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/output_capturer.py +0 -0
  34. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_040_logging_code_portals/uncaught_exceptions.py +0 -0
  35. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_050_safe_code_portals/__init__.py +0 -0
  36. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_050_safe_code_portals/safe_decorator.py +0 -0
  37. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +0 -0
  38. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_060_autonomous_code_portals/__init__.py +0 -0
  39. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +0 -0
  40. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +0 -0
  41. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +0 -0
  42. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/__init__.py +0 -0
  43. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/basic_pre_validators.py +0 -0
  44. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +0 -0
  45. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/list_flattener.py +0 -0
  46. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/package_manager.py +0 -0
  47. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/protected_decorators.py +0 -0
  48. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +0 -0
  49. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/system_utils.py +0 -0
  50. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_070_protected_code_portals/validation_succesful_const.py +0 -0
  51. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_080_pure_code_portals/__init__.py +0 -0
  52. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_080_pure_code_portals/pure_core_classes.py +0 -0
  53. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_080_pure_code_portals/pure_decorator.py +0 -0
  54. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_080_pure_code_portals/recursion_pre_validator.py +0 -0
  55. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_090_swarming_portals/__init__.py +0 -0
  56. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_090_swarming_portals/output_suppressor.py +0 -0
  57. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_090_swarming_portals/swarming_portals.py +0 -0
  58. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_100_top_level_API/__init__.py +0 -0
  59. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_100_top_level_API/default_local_portal.py +0 -0
  60. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_100_top_level_API/top_level_API.py +0 -0
  61. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_800_signatures_and_converters/__init__.py +0 -0
  62. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +0 -0
  63. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +0 -0
  64. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_800_signatures_and_converters/hash_signatures.py +0 -0
  65. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_800_signatures_and_converters/node_signature.py +0 -0
  66. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_800_signatures_and_converters/random_signatures.py +0 -0
  67. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_900_project_stats_collector/__init__.py +0 -0
  68. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/_900_project_stats_collector/project_analyzer.py +0 -0
  69. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/__init__.py +0 -0
  70. {pythagoras-0.23.17 → pythagoras-0.23.19}/src/pythagoras/core/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythagoras
3
- Version: 0.23.17
3
+ Version: 0.23.19
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
@@ -72,16 +72,16 @@ Pythagoras elevates two popular techniques — memoization and parallelization
72
72
  to a global scale and then fuses them, unlocking performance and scalability
73
73
  that were previously out of reach.
74
74
 
75
- * [Pythagoras 101: Introduction to Memoization](https://colab.research.google.com/drive/1bvNXFP1BQJqhoS270Dz1lNT4jPCuj540)
75
+ * [Pythagoras 101: Introduction to Memoization](https://pythagoras.link/tutorial-101)
76
76
 
77
- * [Pythagoras 102: Parallelization Basics](https://colab.research.google.com/drive/1DZxgwoiTnyy1qE7T5JunU4GN4j0w6CVk)
77
+ * [Pythagoras 102: Parallelization Basics](https://pythagoras.link/tutorial-102)
78
78
 
79
79
  Drawing from many years of functional-programming practice,
80
80
  Pythagoras extends these proven ideas to the next level.
81
81
  In a Pythagoras environment, you can seamlessly employ your
82
82
  preferred functional patterns, augmented by new capabilities.
83
83
 
84
- * [Pythagoras 203: Work with Functions](https://colab.research.google.com/drive/1tlG-p-QnHI6p3K1mdGyHzPzwi6CGRg1a)
84
+ * [Pythagoras 203: Work with Functions](https://pythagoras.link/tutorial-203)
85
85
 
86
86
  **!!! BOOKMARK THIS PAGE AND COME BACK LATER, WE WILL PUBLISH MORE TUTORIALS SOON !!!**
87
87
 
@@ -113,15 +113,13 @@ Decorating a function:
113
113
  @pure()
114
114
  def my_long_running_function(a:float, b:float) -> float:
115
115
  from time import sleep # imports must be placed inside a pure function
116
- for i in range(5):
117
- sleep(1)
118
- print("waiting....")
116
+ sleep(5)
119
117
  return a+10*b
120
118
  ```
121
119
 
122
120
  Using a decorated function synchronously:
123
121
  ```python
124
- result = my_long_running_function(a=1, b=2)
122
+ result = my_long_running_function(a=1, b=2) # only named arguments are allowed
125
123
  ```
126
124
 
127
125
  Using a decorated function asynchronously:
@@ -139,11 +137,7 @@ Pre-conditions for executing a function:
139
137
  unused_cpu(cores=10)])
140
138
  def my_long_running_function(a:float, b:float) -> float:
141
139
  from time import sleep
142
- for i in range(5):
143
- sleep(1)
144
- print(
145
- "waiting...."
146
- )
140
+ sleep(5)
147
141
  return a+10*b
148
142
  ```
149
143
 
@@ -154,7 +148,7 @@ def factorial(n:int)->int:
154
148
  if n == 1:
155
149
  return 1
156
150
  else:
157
- return n*factorial(n=n-1)
151
+ return n*factorial(n=n-1) # only named arguments are allowed
158
152
  ```
159
153
 
160
154
  Partial function application:
@@ -20,16 +20,16 @@ Pythagoras elevates two popular techniques — memoization and parallelization
20
20
  to a global scale and then fuses them, unlocking performance and scalability
21
21
  that were previously out of reach.
22
22
 
23
- * [Pythagoras 101: Introduction to Memoization](https://colab.research.google.com/drive/1bvNXFP1BQJqhoS270Dz1lNT4jPCuj540)
23
+ * [Pythagoras 101: Introduction to Memoization](https://pythagoras.link/tutorial-101)
24
24
 
25
- * [Pythagoras 102: Parallelization Basics](https://colab.research.google.com/drive/1DZxgwoiTnyy1qE7T5JunU4GN4j0w6CVk)
25
+ * [Pythagoras 102: Parallelization Basics](https://pythagoras.link/tutorial-102)
26
26
 
27
27
  Drawing from many years of functional-programming practice,
28
28
  Pythagoras extends these proven ideas to the next level.
29
29
  In a Pythagoras environment, you can seamlessly employ your
30
30
  preferred functional patterns, augmented by new capabilities.
31
31
 
32
- * [Pythagoras 203: Work with Functions](https://colab.research.google.com/drive/1tlG-p-QnHI6p3K1mdGyHzPzwi6CGRg1a)
32
+ * [Pythagoras 203: Work with Functions](https://pythagoras.link/tutorial-203)
33
33
 
34
34
  **!!! BOOKMARK THIS PAGE AND COME BACK LATER, WE WILL PUBLISH MORE TUTORIALS SOON !!!**
35
35
 
@@ -61,15 +61,13 @@ Decorating a function:
61
61
  @pure()
62
62
  def my_long_running_function(a:float, b:float) -> float:
63
63
  from time import sleep # imports must be placed inside a pure function
64
- for i in range(5):
65
- sleep(1)
66
- print("waiting....")
64
+ sleep(5)
67
65
  return a+10*b
68
66
  ```
69
67
 
70
68
  Using a decorated function synchronously:
71
69
  ```python
72
- result = my_long_running_function(a=1, b=2)
70
+ result = my_long_running_function(a=1, b=2) # only named arguments are allowed
73
71
  ```
74
72
 
75
73
  Using a decorated function asynchronously:
@@ -87,11 +85,7 @@ Pre-conditions for executing a function:
87
85
  unused_cpu(cores=10)])
88
86
  def my_long_running_function(a:float, b:float) -> float:
89
87
  from time import sleep
90
- for i in range(5):
91
- sleep(1)
92
- print(
93
- "waiting...."
94
- )
88
+ sleep(5)
95
89
  return a+10*b
96
90
  ```
97
91
 
@@ -102,7 +96,7 @@ def factorial(n:int)->int:
102
96
  if n == 1:
103
97
  return 1
104
98
  else:
105
- return n*factorial(n=n-1)
99
+ return n*factorial(n=n-1) # only named arguments are allowed
106
100
  ```
107
101
 
108
102
  Partial function application:
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "pythagoras"
7
- version = "0.23.17"
7
+ version = "0.23.19"
8
8
  authors = [
9
9
  {name = "Volodymyr (Vlad) Pavlov", email = "vlpavlov@ieee.org"},
10
10
  ]
@@ -1,5 +1,6 @@
1
- """This modukle provides foundational functionality for wotk with portals,
2
- specifically for managing the portal stack and for accessing the current
1
+ """This module provides foundational functionality for work with portals.
2
+
3
+ This module specifically handles portal stack management and provides access to the current
3
4
  portal. It keeps track of all portals created in the system and manages
4
5
  the stack of entered ('active') portals. It also provides a method to
5
6
  clear all portals and their state.
@@ -7,7 +8,6 @@ clear all portals and their state.
7
8
 
8
9
  from __future__ import annotations
9
10
 
10
- import collections
11
11
  import random
12
12
  import sys
13
13
  from abc import abstractmethod
@@ -27,6 +27,14 @@ def _describe_persistent_characteristic(name, value) -> pd.DataFrame:
27
27
  A helper function used by the describe() method of BasicPortal
28
28
  and its subclasses. It creates a DataFrame with a single row
29
29
  containing the type, name, and value of the characteristic.
30
+
31
+ Args:
32
+ name: The name of the characteristic.
33
+ value: The value of the characteristic.
34
+
35
+ Returns:
36
+ A pandas DataFrame with columns 'type', 'name', and 'value',
37
+ containing a single row with type="Disk".
30
38
  """
31
39
  d = dict(
32
40
  type="Disk"
@@ -41,6 +49,14 @@ def _describe_runtime_characteristic(name, value) -> pd.DataFrame:
41
49
  A helper function used by the describe() method of BasicPortal
42
50
  and its subclasses. It creates a DataFrame with a single row
43
51
  containing the type, name, and value of the characteristic.
52
+
53
+ Args:
54
+ name: The name of the characteristic.
55
+ value: The value of the characteristic.
56
+
57
+ Returns:
58
+ A pandas DataFrame with columns 'type', 'name', and 'value',
59
+ containing a single row with type="Runtime".
44
60
  """
45
61
  d = dict(
46
62
  type = "Runtime"
@@ -54,16 +70,16 @@ BACKEND_TYPE_TXT = "Backend type"
54
70
 
55
71
 
56
72
  def _get_description_value_by_key(dataframe:pd.DataFrame, key:str) -> Any:
57
- """
58
- Retrieves the value corresponding to a given key from a DataFrame.
73
+ """Retrieve the value corresponding to a given key from a DataFrame.
59
74
 
60
- Parameters:
61
- - dataframe (pd.DataFrame): The input DataFrame with at least 4 columns.
62
- - key (str): The key to search for in the third column of the DataFrame.
75
+ Args:
76
+ dataframe: The input DataFrame with 4 columns.
77
+ It is supposed to be an output of portal.describe() method.
78
+ key: The key to search for in the third column of the DataFrame.
63
79
 
64
80
  Returns:
65
- - value (any): The value from the fourth column corresponding to the key.
66
- Returns None if the key is not found.
81
+ The value from the fourth column corresponding to the key.
82
+ Returns None if the key is not found.
67
83
  """
68
84
  # Check if the key exists in the 3rd column
69
85
  result = dataframe.loc[dataframe.iloc[:, 1] == key]
@@ -80,31 +96,51 @@ _active_portals_counters_stack: list = [int]
80
96
  _most_recently_created_portal: BasicPortal | None = None
81
97
 
82
98
  def get_number_of_known_portals() -> int:
83
- """Get the number of portals currently in the system."""
99
+ """Get the number of portals currently in the system.
100
+
101
+ Returns:
102
+ The total count of all known portals in the system.
103
+ """
84
104
  global _all_known_portals
85
105
  return len(_all_known_portals)
86
106
 
87
107
 
88
108
  def get_all_known_portals() -> list[BasicPortal]:
89
- """Get a list of all known portals."""
109
+ """Get a list of all known portals.
110
+
111
+ Returns:
112
+ A list containing all portal instances currently known to the system.
113
+ """
90
114
  global _all_known_portals
91
115
  return list(_all_known_portals.values())
92
116
 
93
117
 
94
118
  def get_number_of_portals_in_active_stack() -> int:
95
- """Get the number of portals in a stack."""
119
+ """Get the number of unique portals in the active stack.
120
+
121
+ Returns:
122
+ The count of unique portals currently in the active portal stack.
123
+ """
96
124
  global _active_portals_stack
97
125
  return len(set(_active_portals_stack))
98
126
 
99
127
 
100
128
  def get_depth_of_active_portal_stack() -> int:
101
- """Get the depth of the active portal stack."""
129
+ """Get the depth of the active portal stack.
130
+
131
+ Returns:
132
+ The total depth (sum of all counters) of the active portal stack.
133
+ """
102
134
  global _active_portals_counters_stack
103
135
  return sum(_active_portals_counters_stack)
104
136
 
105
137
 
106
138
  def get_most_recently_created_portal() -> BasicPortal | None:
107
- """Get the most recently created portal, or None if no portals exist."""
139
+ """Get the most recently created portal.
140
+
141
+ Returns:
142
+ The most recently created portal instance, or None if no portals exist.
143
+ """
108
144
  global _most_recently_created_portal
109
145
  return _most_recently_created_portal
110
146
 
@@ -117,6 +153,9 @@ def get_active_portal() -> BasicPortal:
117
153
  it finds the most recently created portal and makes it active.
118
154
  If there are currently no portals exist in the system,
119
155
  it creates and returns the default portal.
156
+
157
+ Returns:
158
+ The currently active portal instance.
120
159
  """
121
160
  global _most_recently_created_portal, _active_portals_stack
122
161
 
@@ -132,7 +171,11 @@ def get_active_portal() -> BasicPortal:
132
171
 
133
172
 
134
173
  def get_nonactive_portals() -> list[BasicPortal]:
135
- """Get a list of all portals that are not in the active stack."""
174
+ """Get a list of all portals that are not in the active stack.
175
+
176
+ Returns:
177
+ A list of portal instances that are not currently in the active portal stack.
178
+ """
136
179
  active_portal_str_id = get_active_portal()._str_id
137
180
  found_portals = []
138
181
  for portal_str_id, portal in _all_known_portals.items():
@@ -149,6 +192,9 @@ def get_default_portal_base_dir() -> str:
149
192
  Pythagoras connects to the default local portal
150
193
  when no other portal is specified in the
151
194
  program which uses Pythagoras.
195
+
196
+ Returns:
197
+ The absolute path to the default portal's base directory as a string.
152
198
  """
153
199
  home_directory = Path.home()
154
200
  target_directory = home_directory / ".pythagoras" / ".default_portal"
@@ -168,7 +214,7 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
168
214
  across multiple runs of the application, and across multiple computers.
169
215
 
170
216
  A Pythagoras-based application can have multiple portals,
171
- and there is usually a current (default) portal, accessible via
217
+ and there is usually a currently active one, accessible via
172
218
  get_active_portal().
173
219
 
174
220
  BasicPortal is a base class for all portal objects.
@@ -184,6 +230,17 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
184
230
 
185
231
 
186
232
  def __init__(self, root_dict:PersiDict|str|None = None):
233
+ """Initialize a BasicPortal instance.
234
+
235
+ Sets up the portal with a root dictionary for persistent storage.
236
+ If no root_dict is provided, uses the default portal base directory
237
+ to create a FileDirDict.
238
+
239
+ Args:
240
+ root_dict: The root dictionary for persistent storage, a path string,
241
+ or None to use the default location. If a string is provided,
242
+ it will be converted to a FileDirDict using that path.
243
+ """
187
244
  ParameterizableClass.__init__(self)
188
245
  if root_dict is None:
189
246
  root_dict = get_default_portal_base_dir()
@@ -197,7 +254,12 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
197
254
 
198
255
 
199
256
  def _post_init_hook(self) -> None:
200
- """This method is always called after all __init__() methods"""
257
+ """Execute post-initialization tasks for the portal.
258
+
259
+ This method is automatically called after all __init__() methods
260
+ complete. It registers the portal in the global registry and updates
261
+ the most recently created portal reference.
262
+ """
201
263
  global _most_recently_created_portal, _all_known_portals
202
264
  _all_known_portals[self._str_id] = self
203
265
  _most_recently_created_portal = self
@@ -205,7 +267,16 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
205
267
 
206
268
  def _get_linked_objects_ids(self
207
269
  , target_class: type | None = None) -> set[PObjectStrID]:
208
- """Get the set str_ids of objects, linked to the portal."""
270
+ """Get the set of string IDs of objects linked to this portal.
271
+
272
+ Args:
273
+ target_class: The class type to filter for, or None to include
274
+ all linked objects regardless of type.
275
+
276
+ Returns:
277
+ A set of string IDs representing objects linked to this portal,
278
+ optionally filtered by the specified target class.
279
+ """
209
280
  global _all_links_from_objects_to_portals
210
281
  result = set()
211
282
  for obj_str_id, portal_str_id in _all_links_from_objects_to_portals.items():
@@ -220,7 +291,16 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
220
291
 
221
292
 
222
293
  def get_linked_objects(self, target_class: type | None = None) -> list[PortalAwareClass]:
223
- """Get the list of objects, linked to the portal"""
294
+ """Get the list of objects linked to this portal.
295
+
296
+ Args:
297
+ target_class: The class type to filter for, or None to include
298
+ all linked objects regardless of type.
299
+
300
+ Returns:
301
+ A list of PortalAwareClass instances that are linked to this portal,
302
+ optionally filtered by the specified target class.
303
+ """
224
304
  found_linked_objects_ids = self._get_linked_objects_ids(target_class)
225
305
  found_linked_objects = []
226
306
  for obj_str_id in found_linked_objects_ids:
@@ -230,12 +310,27 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
230
310
 
231
311
 
232
312
  def get_number_of_linked_objects(self, target_class: type | None = None) -> int:
233
- """Get the number of linked portal-aware objects of the given class name."""
234
- return len(self._get_linked_objects_ids())
313
+ """Get the number of objects linked to this portal.
314
+
315
+ Args:
316
+ target_class: The class type to filter for, or None to include
317
+ all linked objects regardless of type.
318
+
319
+ Returns:
320
+ The count of portal-aware objects linked to this portal,
321
+ optionally filtered by the specified target class.
322
+ """
323
+ return len(self._get_linked_objects_ids(target_class))
235
324
 
236
325
 
237
326
  @property
238
327
  def entropy_infuser(self) -> random.Random:
328
+ """Get the shared random number generator for the portal system.
329
+
330
+ Returns:
331
+ A Random instance shared across all BasicPortal instances for
332
+ generating random values and entropy when needed.
333
+ """
239
334
  return BasicPortal._entropy_infuser
240
335
 
241
336
 
@@ -372,6 +467,16 @@ class PortalAwareClass(metaclass = PostInitMeta):
372
467
  _visited_portals: set[str] | None
373
468
 
374
469
  def __init__(self, portal:BasicPortal|None=None):
470
+ """Initialize a PortalAwareClass instance.
471
+
472
+ Sets up the object with an optional linked portal. If a portal is provided,
473
+ the object will be linked to that portal and will use it for all operations.
474
+ If no portal is provided, the object will use the currently active portal.
475
+
476
+ Args:
477
+ portal: The portal to link this object to, or None to use the
478
+ currently active portal for operations.
479
+ """
375
480
  assert portal is None or isinstance(portal, BasicPortal)
376
481
  self._linked_portal_at_init = portal
377
482
  # self._hash_id_cache = None
@@ -379,7 +484,12 @@ class PortalAwareClass(metaclass = PostInitMeta):
379
484
 
380
485
 
381
486
  def _post_init_hook(self):
382
- """ This method is called after all object's .__init__() methods."""
487
+ """Execute post-initialization tasks for the portal-aware object.
488
+
489
+ This method is automatically called after all object's __init__() methods
490
+ complete. It registers the object with its linked portal if one was provided
491
+ during initialization, establishing the connection between the object and portal.
492
+ """
383
493
  global _all_links_from_objects_to_portals
384
494
  global _all_activated_portal_aware_objects
385
495
  if self._linked_portal_at_init is not None:
@@ -427,6 +537,14 @@ class PortalAwareClass(metaclass = PostInitMeta):
427
537
 
428
538
 
429
539
  def _visit_portal(self, portal: BasicPortal) -> None:
540
+ """Visit a portal and register the object if this is the first visit.
541
+
542
+ Checks if the object has previously visited the specified portal.
543
+ If this is the first visit, registers the object with that portal.
544
+
545
+ Args:
546
+ portal: The BasicPortal instance to visit.
547
+ """
430
548
  if portal._str_id not in self._visited_portals:
431
549
  self._first_visit_to_portal(portal)
432
550
 
@@ -463,7 +581,14 @@ class PortalAwareClass(metaclass = PostInitMeta):
463
581
 
464
582
  @abstractmethod
465
583
  def __setstate__(self, state):
466
- """This method is called when the object is unpickled."""
584
+ """This method is called when the object is unpickled.
585
+
586
+ Restores the object's state from unpickling and resets portal-related
587
+ attributes to ensure proper initialization in the new environment.
588
+
589
+ Args:
590
+ state: The state dictionary used for unpickling the object.
591
+ """
467
592
  self._invalidate_cache()
468
593
  self._visited_portals = set()
469
594
  self._linked_portal_at_init = None
@@ -0,0 +1,36 @@
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)
@@ -0,0 +1,39 @@
1
+ from typing import Any
2
+
3
+ from persidict import replace_unsafe_chars
4
+
5
+
6
+ def get_long_infoname(x: Any, drop_unsafe_chars: bool = True) -> str:
7
+ """Build a string with extended information about an object and its type.
8
+
9
+ Creates a detailed identifier string that includes the module, class name,
10
+ and object name (if available) of the given object. This is useful for
11
+ creating unique identifiers for objects in the Pythagoras system.
12
+
13
+ Args:
14
+ x: The object for which to generate the information string.
15
+ drop_unsafe_chars: If True, replaces unsafe characters with underscores
16
+ using the persidict.replace_unsafe_chars function. Defaults to True.
17
+
18
+ Returns:
19
+ A string containing extended information about the object, including
20
+ its module, class name, and object name (when available). Unsafe
21
+ characters are replaced with underscores if drop_unsafe_chars is True.
22
+ """
23
+
24
+ name = str(type(x).__module__)
25
+
26
+ if hasattr(type(x), "__qualname__"):
27
+ name += "." + str(type(x).__qualname__)
28
+ else:
29
+ name += "." + str(type(x).__name__)
30
+
31
+ if hasattr(x, "__qualname__"):
32
+ name += "_" + str(x.__qualname__)
33
+ elif hasattr(x, "__name__"):
34
+ name += "_" + str(x.__name__)
35
+
36
+ if drop_unsafe_chars:
37
+ name = replace_unsafe_chars(name, replace_with="_")
38
+
39
+ return name
@@ -0,0 +1,36 @@
1
+ class NotPicklable:
2
+ """A mixin class that prevents objects from being pickled or unpickled.
3
+
4
+ This class provides a mechanism to explicitly prevent instances from being
5
+ serialized using Python's pickle module. Classes that inherit from this
6
+ mixin will raise TypeError exceptions when pickle attempts to serialize
7
+ or deserialize them.
8
+
9
+ This is useful for objects that contain non-serializable resources or
10
+ should not be persisted for security or architectural reasons.
11
+ """
12
+
13
+ def __getstate__(self):
14
+ """Prevent object serialization by raising TypeError.
15
+
16
+ This method is called by pickle when attempting to serialize the object.
17
+ It unconditionally raises a TypeError to prevent pickling.
18
+
19
+ Raises:
20
+ TypeError: Always raised to prevent the object from being pickled.
21
+ """
22
+ raise TypeError(f"{type(self).__name__} cannot be pickled")
23
+
24
+ def __setstate__(self, state):
25
+ """Prevent object deserialization by raising TypeError.
26
+
27
+ This method is called by pickle when attempting to deserialize the object.
28
+ It unconditionally raises a TypeError to prevent unpickling.
29
+
30
+ Args:
31
+ state: The state dictionary that would be used for unpickling.
32
+
33
+ Raises:
34
+ TypeError: Always raised to prevent the object from being unpickled.
35
+ """
36
+ raise TypeError(f"{type(self).__name__} cannot be unpickled")
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+ from .basic_portal_core_classes import (
3
+ BasicPortal, PortalType, _clear_all_portals)
4
+
5
+
6
+ class _PortalTester:
7
+ """A context manager for testing portal objects.
8
+
9
+ The class is used to test the portal objects.
10
+ It ensures that all portal objects are properly initialized and cleared
11
+ between tests. This class is not supposed to be used in application code,
12
+ it only exists for unit tests.
13
+ """
14
+ _current_instance:_PortalTester|None = None
15
+ _portal:BasicPortal|None = None
16
+
17
+ def __init__(self, portal_class: PortalType | None = None, *args, **kwargs):
18
+ """Initialize the portal tester.
19
+
20
+ Args:
21
+ portal_class: The portal class to test, or None for no portal.
22
+ Must be a subclass of BasicPortal if provided.
23
+ *args: Positional arguments to pass to the portal constructor.
24
+ **kwargs: Keyword arguments to pass to the portal constructor.
25
+
26
+ Raises:
27
+ Exception: If another _PortalTester instance is already active.
28
+ AssertionError: If portal_class is not a subclass of BasicPortal.
29
+ """
30
+ if _PortalTester._current_instance is not None:
31
+ raise Exception("_PortalTester can't be nested")
32
+ _PortalTester._current_instance = self
33
+
34
+ if portal_class is not None:
35
+ assert issubclass(portal_class, BasicPortal)
36
+ self.portal_class = portal_class
37
+ self.args = args
38
+ self.kwargs = kwargs
39
+
40
+
41
+ @property
42
+ def portal(self) -> BasicPortal | None:
43
+ """The portal object being tested.
44
+
45
+ Returns:
46
+ The current portal instance if one exists, None otherwise.
47
+ """
48
+ return self._portal
49
+
50
+ def __enter__(self):
51
+ """Enter the portal testing context.
52
+
53
+ Clears all existing portals and creates a new portal instance if
54
+ a portal class was specified during initialization. The created
55
+ portal is automatically entered as a context manager.
56
+
57
+ Returns:
58
+ The _PortalTester instance for use in context management.
59
+
60
+ Raises:
61
+ Exception: If another _PortalTester instance is already active.
62
+ """
63
+ if (_PortalTester._current_instance is not None
64
+ and _PortalTester._current_instance is not self):
65
+ raise Exception("_PortalTester can't be nested")
66
+ _PortalTester._current_instance = self
67
+
68
+ _clear_all_portals()
69
+
70
+ if self.portal_class is not None:
71
+ self._portal = self.portal_class(*self.args, **self.kwargs)
72
+ self.portal.__enter__()
73
+ return self
74
+
75
+ def __exit__(self, exc_type, exc_val, exc_tb):
76
+ """Exit the portal testing context.
77
+
78
+ Properly exits the portal context if one was created, clears all
79
+ portals from the system, and resets the current instance tracker.
80
+
81
+ Args:
82
+ exc_type: Exception type if an exception occurred, None otherwise.
83
+ exc_val: Exception value if an exception occurred, None otherwise.
84
+ exc_tb: Exception traceback if an exception occurred, None otherwise.
85
+ """
86
+ if self.portal_class is not None:
87
+ self.portal.__exit__(exc_type, exc_val, exc_tb)
88
+ self._portal = None
89
+
90
+ _clear_all_portals()
91
+
92
+ _PortalTester._current_instance = None
@@ -0,0 +1,34 @@
1
+ from abc import ABCMeta
2
+
3
+ class PostInitMeta(ABCMeta):
4
+ """Ensures method ._post_init_hook() is always called after the constructor.
5
+
6
+ This metaclass automatically calls the `_post_init_hook()` method on instances
7
+ after their construction is complete. This ensures that any post-initialization
8
+ logic is consistently executed for all instances of classes using this metaclass.
9
+ """
10
+
11
+ def __call__(self, *args, **kwargs):
12
+ """Create and initialize an instance, then call its post-init hook.
13
+
14
+ This method overrides the default instance creation process to ensure
15
+ that `_post_init_hook()` is called after the regular constructor.
16
+ It also includes validation logic for portal-aware objects.
17
+
18
+ Args:
19
+ *args: Positional arguments to pass to the class constructor.
20
+ **kwargs: Keyword arguments to pass to the class constructor.
21
+
22
+ Returns:
23
+ The fully initialized instance with post-init hook executed.
24
+
25
+ Raises:
26
+ AssertionError: If portal-aware object validation fails.
27
+ """
28
+ instance = super().__call__(*args, **kwargs)
29
+ instance._post_init_hook()
30
+ #TODO: remove/improve this check at some point in the future
31
+ if hasattr(instance, '_visited_portals'):
32
+ assert hasattr(instance, '_linked_portal_at_init')
33
+ assert len(instance._visited_portals) - int(instance._linked_portal_at_init is not None) == 0
34
+ return instance
@@ -1,8 +0,0 @@
1
- class PythagorasException(Exception):
2
- def __init__(self, message):
3
- self.message = message
4
-
5
-
6
- class NonCompliantFunction(PythagorasException):
7
- def __init__(self, message):
8
- PythagorasException.__init__(self, message)
@@ -1,24 +0,0 @@
1
- from typing import Any
2
-
3
- from persidict import replace_unsafe_chars
4
-
5
-
6
- def get_long_infoname(x:Any, drop_unsafe_chars:bool = True) -> str:
7
- """Build a string with extended information about an object and its type"""
8
-
9
- name = str(type(x).__module__)
10
-
11
- if hasattr(type(x), "__qualname__"):
12
- name += "." + str(type(x).__qualname__)
13
- else:
14
- name += "." + str(type(x).__name__)
15
-
16
- if hasattr(x, "__qualname__"):
17
- name += "_" + str(x.__qualname__)
18
- elif hasattr(x, "__name__"):
19
- name += "_" + str(x.__name__)
20
-
21
- if drop_unsafe_chars:
22
- name = replace_unsafe_chars(name, replace_with="_")
23
-
24
- return name
@@ -1,8 +0,0 @@
1
- class NotPicklable:
2
- def __getstate__(self):
3
- """This method is called when the object is pickled."""
4
- raise TypeError(f"{type(self).__name__} cannot be pickled")
5
-
6
- def __setstate__(self, state):
7
- """This method is called when the object is unpickled."""
8
- raise TypeError(f"{type(self).__name__} cannot be unpickled")
@@ -1,57 +0,0 @@
1
- from __future__ import annotations
2
- from .basic_portal_core_classes import (
3
- BasicPortal, PortalType, _clear_all_portals)
4
-
5
-
6
- class _PortalTester:
7
- """A context manager for testing portal objects.
8
-
9
- The class is used to test the portal objects.
10
- It ensures that all portal objects are properly initialized and cleared
11
- between tests. This class is not supposed to be used in application code,
12
- it only exists for unit tests.
13
- """
14
- _current_instance:_PortalTester|None = None
15
- _portal:BasicPortal|None = None
16
-
17
- def __init__(self,portal_class:PortalType|None = None,*args,**kwargs):
18
-
19
- if _PortalTester._current_instance is not None:
20
- raise Exception("_PortalTester can't be nested")
21
- _PortalTester._current_instance = self
22
-
23
- if portal_class is not None:
24
- assert issubclass(portal_class, BasicPortal)
25
- self.portal_class = portal_class
26
- self.args = args
27
- self.kwargs = kwargs
28
-
29
-
30
- @property
31
- def portal(self) -> BasicPortal|None:
32
- """The portal object being tested."""
33
- return self._portal
34
-
35
-
36
- def __enter__(self):
37
- if (_PortalTester._current_instance is not None
38
- and _PortalTester._current_instance is not self):
39
- raise Exception("_PortalTester can't be nested")
40
- _PortalTester._current_instance = self
41
-
42
- _clear_all_portals()
43
-
44
- if self.portal_class is not None:
45
- self._portal = self.portal_class(*self.args,**self.kwargs)
46
- self.portal.__enter__()
47
- return self
48
-
49
- def __exit__(self, exc_type, exc_val, exc_tb):
50
-
51
- if self.portal_class is not None:
52
- self.portal.__exit__(exc_type, exc_val, exc_tb)
53
- self._portal = None
54
-
55
- _clear_all_portals()
56
-
57
- _PortalTester._current_instance = None
@@ -1,13 +0,0 @@
1
- from abc import ABCMeta
2
-
3
- class PostInitMeta(ABCMeta):
4
- """Ensures method ._post_init_hook() is always called after the constructor.
5
- """
6
- def __call__(self, *args, **kwargs):
7
- instance = super().__call__(*args, **kwargs)
8
- instance._post_init_hook()
9
- #TODO: remove/improve this check at some point in the future
10
- if hasattr(instance, '_visited_portals'):
11
- assert hasattr(instance, '_linked_portal_at_init')
12
- assert len(instance._visited_portals) - int(instance._linked_portal_at_init is not None) == 0
13
- return instance