pythagoras 0.22.1__tar.gz → 0.23.0__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 (76) hide show
  1. {pythagoras-0.22.1 → pythagoras-0.23.0}/PKG-INFO +1 -1
  2. {pythagoras-0.22.1 → pythagoras-0.23.0}/pyproject.toml +1 -1
  3. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/basic_portal_core_classes.py +3 -3
  4. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +4 -4
  5. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/__init__.py +11 -8
  6. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/data_portal_core_classes.py +32 -28
  7. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/ready_and_get.py +21 -8
  8. pythagoras-0.23.0/src/pythagoras/_040_logging_code_portals/__init__.py +22 -0
  9. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/exception_processing_tracking.py +6 -4
  10. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/kw_args.py +1 -1
  11. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +3 -5
  12. pythagoras-0.23.0/src/pythagoras/_050_safe_code_portals/__init__.py +15 -0
  13. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_050_safe_code_portals/safe_decorator.py +1 -1
  14. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +13 -7
  15. pythagoras-0.23.0/src/pythagoras/_060_autonomous_code_portals/__init__.py +43 -0
  16. pythagoras-0.23.0/src/pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +72 -0
  17. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +18 -6
  18. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +1 -1
  19. pythagoras-0.23.0/src/pythagoras/_070_protected_code_portals/__init__.py +22 -0
  20. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/basic_pre_validators.py +8 -8
  21. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/package_manager.py +0 -1
  22. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/protected_decorators.py +2 -2
  23. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +55 -21
  24. pythagoras-0.22.1/src/pythagoras/_070_protected_code_portals/OK_const.py → pythagoras-0.23.0/src/pythagoras/_070_protected_code_portals/validation_succesful_const.py +4 -2
  25. pythagoras-0.23.0/src/pythagoras/_080_pure_code_portals/__init__.py +28 -0
  26. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_080_pure_code_portals/pure_core_classes.py +32 -19
  27. pythagoras-0.23.0/src/pythagoras/_080_pure_code_portals/pure_decorator.py +56 -0
  28. pythagoras-0.23.0/src/pythagoras/_090_swarming_portals/__init__.py +12 -0
  29. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_090_swarming_portals/swarming_portals.py +18 -0
  30. pythagoras-0.23.0/src/pythagoras/_100_top_level_API/default_local_portal.py +8 -0
  31. pythagoras-0.23.0/src/pythagoras/_900_project_stats_collector/__init__.py +1 -0
  32. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_900_project_stats_collector/project_analyzer.py +1 -0
  33. pythagoras-0.23.0/src/pythagoras/__init__.py +31 -0
  34. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/core/__init__.py +1 -1
  35. pythagoras-0.22.1/src/pythagoras/_040_logging_code_portals/__init__.py +0 -28
  36. pythagoras-0.22.1/src/pythagoras/_050_safe_code_portals/__init__.py +0 -2
  37. pythagoras-0.22.1/src/pythagoras/_060_autonomous_code_portals/__init__.py +0 -54
  38. pythagoras-0.22.1/src/pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +0 -91
  39. pythagoras-0.22.1/src/pythagoras/_070_protected_code_portals/__init__.py +0 -6
  40. pythagoras-0.22.1/src/pythagoras/_080_pure_code_portals/__init__.py +0 -42
  41. pythagoras-0.22.1/src/pythagoras/_080_pure_code_portals/pure_decorator.py +0 -35
  42. pythagoras-0.22.1/src/pythagoras/_090_swarming_portals/__init__.py +0 -1
  43. pythagoras-0.22.1/src/pythagoras/_100_top_level_API/default_local_portal.py +0 -6
  44. pythagoras-0.22.1/src/pythagoras/_900_project_stats_collector/__init__.py +0 -0
  45. pythagoras-0.22.1/src/pythagoras/__init__.py +0 -39
  46. {pythagoras-0.22.1 → pythagoras-0.23.0}/README.md +0 -0
  47. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/.DS_Store +0 -0
  48. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/__init__.py +0 -0
  49. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/exceptions.py +0 -0
  50. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/long_infoname.py +0 -0
  51. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/not_picklable_class.py +0 -0
  52. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/portal_tester.py +0 -0
  53. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/post_init_metaclass.py +0 -0
  54. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/__init__.py +0 -0
  55. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/code_normalizer.py +0 -0
  56. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/function_processing.py +0 -0
  57. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +0 -0
  58. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/storable_decorator.py +0 -0
  59. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/execution_environment_summary.py +0 -0
  60. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/logging_decorator.py +0 -0
  61. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/notebook_checker.py +0 -0
  62. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/output_capturer.py +0 -0
  63. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/uncaught_exceptions.py +0 -0
  64. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +0 -0
  65. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/list_flattener.py +0 -0
  66. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/system_utils.py +0 -0
  67. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/validator_fn_classes.py +0 -0
  68. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_090_swarming_portals/output_suppressor.py +0 -0
  69. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_100_top_level_API/__init__.py +0 -0
  70. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_100_top_level_API/top_level_API.py +0 -0
  71. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/__init__.py +0 -0
  72. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +0 -0
  73. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +0 -0
  74. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/hash_signatures.py +0 -0
  75. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/node_signatures.py +0 -0
  76. {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/random_signatures.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythagoras
3
- Version: 0.22.1
3
+ Version: 0.23.0
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.22.01"
7
+ version = "0.23.0"
8
8
  authors = [
9
9
  {name = "Volodymyr (Vlad) Pavlov", email = "vlpavlov@ieee.org"},
10
10
  ]
@@ -123,7 +123,7 @@ def get_active_portal() -> BasicPortal:
123
123
  return _active_portals_stack[-1]
124
124
 
125
125
  if _most_recently_created_portal is None:
126
- sys.modules["pythagoras"].instantiate_default_local_portal()
126
+ sys.modules["pythagoras"]._instantiate_default_local_portal()
127
127
 
128
128
  _active_portals_stack.append(_most_recently_created_portal)
129
129
  _active_portals_counters_stack.append(1)
@@ -290,9 +290,9 @@ class BasicPortal(NotPicklable,ParameterizableClass, metaclass = PostInitMeta):
290
290
 
291
291
 
292
292
  def _invalidate_cache(self) -> None:
293
- """Invalidate the object's attribute cache.
293
+ """Invalidate the portal's attribute cache.
294
294
 
295
- If the object's attribute named ATTR is cached,
295
+ If the portal's attribute named ATTR is cached,
296
296
  its cached value will be stored in an attribute named _ATTR_cache
297
297
  This method should delete all such attributes.
298
298
  """
@@ -15,7 +15,7 @@ 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 function's source code in a 'canonical' form.
18
+ """Return a function's source code in a 'canonical' form.
19
19
 
20
20
  Remove all comments, docstrings and empty lines;
21
21
  standardize code formatting based on PEP 8.
@@ -201,9 +201,9 @@ class OrdinaryFn(PortalAwareClass):
201
201
 
202
202
 
203
203
  def _invalidate_cache(self):
204
- """Invalidate the object's attribute cache.
204
+ """Invalidate the function's attribute cache.
205
205
 
206
- If the object's attribute named ATTR is cached,
206
+ If the function's attribute named ATTR is cached,
207
207
  its cached value will be stored in an attribute named _ATTR_cache
208
208
  This method should delete all such attributes.
209
209
  """
@@ -263,7 +263,7 @@ class OrdinaryFn(PortalAwareClass):
263
263
 
264
264
  def __setstate__(self, state):
265
265
  """This method is called when the object is unpickled."""
266
- PortalAwareClass.__setstate__(self, state)
266
+ super().__setstate__(state)
267
267
  self._source_code = state["source_code"]
268
268
 
269
269
 
@@ -3,7 +3,7 @@
3
3
  The most important classes in this sub-package are
4
4
  DataPortal and ValueAddr.
5
5
 
6
- A DataPortal is a container for storing and retrieving immutable values.
6
+ A DataPortal is a container for storing and retrieving values.
7
7
  In distributed applications, multiple application sessions / processes
8
8
  can access the same DataPortal, which enables them to interact
9
9
  via passing values through the portal.
@@ -12,18 +12,21 @@ A ValueAddr is a unique identifier for an immutable value.
12
12
  Two objects with exactly the same type and value will always have
13
13
  exactly the same ValueAddr-es.
14
14
 
15
- A ValueAddr consists of 2 strings: a descriptor, and a hash.
15
+ Conceptually, ValueAddr consists of 2 strings: a descriptor, and a hash signature.
16
16
  A descriptor contains human-readable information about an object's type.
17
17
  A hash string contains the object's hash signature.
18
18
 
19
+ Under the hood, the hash signature is further split into 3 strings:
20
+ a shard, a subshard and a hash tail.
21
+ This is done to address limitations of some file systems
22
+ and to optimize work sith cloud storage (e.g. S3).
23
+
19
24
  Typically, a DataPortal is implemented as
20
- a shared directory on a file system (e.g. Amazon EFS),
21
- or as a shared bucket in a cloud storage (e.g. Amazon S3).
22
- In this case, a ValueAddr becomes a part of file path
23
- or a URL (e.g. a hash serves as a filename,
25
+ a shared directory on a file system (e.g., Amazon EFS),
26
+ or as a shared bucket in a cloud storage (e.g., Amazon S3).
27
+ In this case, a ValueAddr becomes a part of a file path
28
+ or a URL (e.g., a hash serves as a filename,
24
29
  and a prefix is a folder name).
25
-
26
- DataPortal is a subclass of OrdinaryCodePortal.
27
30
  """
28
31
 
29
32
 
@@ -8,7 +8,6 @@ from parameterizable import sort_dict_by_keys
8
8
  from persidict import PersiDict, SafeStrTuple, replace_unsafe_chars, DELETE_CURRENT
9
9
  from persidict import KEEP_CURRENT, Joker
10
10
 
11
- from .._010_basic_portals import BasicPortal
12
11
  from .._010_basic_portals import get_active_portal, get_nonactive_portals
13
12
  from .._800_signatures_and_converters import get_hash_signature
14
13
 
@@ -41,7 +40,7 @@ def get_nonactive_data_portals() -> list[DataPortal]:
41
40
  class DataPortal(OrdinaryCodePortal):
42
41
  """A portal that persistently stores values.
43
42
 
44
- Values are accessible via their hash_address-es,
43
+ Immutable values are accessible via their hash_address-es,
45
44
  which are unique identifiers of the values.
46
45
 
47
46
  If the current portal does not contain a specific value,
@@ -51,8 +50,11 @@ class DataPortal(OrdinaryCodePortal):
51
50
 
52
51
  A portal can serve as a context manager, enabling the use of the
53
52
  'with' statement to support portal-aware code blocks. If some code is
54
- supposed to explicitly read anything from a portal, it should be wrapped
55
- in a 'with' statement that marks the portal as the current.
53
+ supposed to explicitly read anything from (or save to) a portal,
54
+ it should be wrapped in a 'with' statement that
55
+ marks the portal as active for the duration of the code block.
56
+
57
+ DataPortal also supports random consistency checks.
56
58
  """
57
59
 
58
60
  _value_store: WriteOnceDict | None
@@ -190,6 +192,7 @@ class DataPortal(OrdinaryCodePortal):
190
192
 
191
193
 
192
194
  class StorableFn(OrdinaryFn):
195
+ """An ordinary function that can be persistently stored in a DataPortal."""
193
196
 
194
197
  _addr_cache: ValueAddr
195
198
  _ephemeral_config_params_at_init: dict[str, Any] | None
@@ -219,13 +222,6 @@ class StorableFn(OrdinaryFn):
219
222
  return OrdinaryFn.portal.__get__(self)
220
223
 
221
224
 
222
- # @portal.setter
223
- # def portal(self, new_portal: DataPortal) -> None:
224
- # if not isinstance(new_portal, DataPortal):
225
- # raise TypeError("portal must be a DataPortal instance")
226
- # OrdinaryFn.portal.__set__(self, new_portal)
227
-
228
-
229
225
  def _get_config_setting(self, key: SafeStrTuple, portal:DataPortal) -> Any:
230
226
  if not isinstance(key, (str,SafeStrTuple)):
231
227
  raise TypeError("key must be a SafeStrTuple or a string")
@@ -258,9 +254,9 @@ class StorableFn(OrdinaryFn):
258
254
 
259
255
 
260
256
  def _invalidate_cache(self):
261
- """Invalidate the object's attribute cache.
257
+ """Invalidate the function's attribute cache.
262
258
 
263
- If the object's attribute named ATTR is cached,
259
+ If the function's attribute named ATTR is cached,
264
260
  its cached value will be stored in an attribute named _ATTR_cache
265
261
  This method should delete all such attributes.
266
262
  """
@@ -286,11 +282,15 @@ class HashAddr(SafeStrTuple):
286
282
  Two objects with exactly the same type and value will always have
287
283
  exactly the same HashAddr-es.
288
284
 
289
- A HashAddr consists of 2 components: a descriptor, and a hash.
290
- A descriptor contains human-readable information about an object's type.
291
- A hash string contains the object's hash signature. It may contain
292
- an optional suffix, which provides additional human-readable
293
- information about the object's structure / value.
285
+ Conceptually, HashAddr consists of 2 components: a descriptor,
286
+ and a hash signature. A descriptor contains human-readable information
287
+ about an object's type. A hash signature string contains
288
+ the object's sha256 value, encoded in base-32.
289
+
290
+ Under the hood, the hash signature is further split into 3 strings:
291
+ a shard, a subshard and a hash tail.
292
+ This is done to address limitations of some file systems
293
+ and to optimize work sith cloud storage (e.g. S3).
294
294
  """
295
295
 
296
296
  def __init__(self, descriptor:str
@@ -362,7 +362,7 @@ class HashAddr(SafeStrTuple):
362
362
  , hash_signature:str
363
363
  , assert_readiness:bool=True
364
364
  ) -> HashAddr:
365
- """(Re)construct address from text representations of descriptor and hash"""
365
+ """(Re)construct address from str versions of a descriptor and a hash"""
366
366
 
367
367
  if not isinstance(descriptor, str) or not isinstance(hash_signature, str):
368
368
  raise TypeError("descriptor and hash_signature must be strings")
@@ -382,7 +382,7 @@ class HashAddr(SafeStrTuple):
382
382
  @property
383
383
  @abstractmethod
384
384
  def ready(self) -> bool:
385
- """Check if address points to a value that is ready to be retrieved."""
385
+ """Check if the address points to a value that is ready to be retrieved."""
386
386
  # TODO: decide whether we need .ready() at the base class
387
387
  raise NotImplementedError
388
388
 
@@ -417,12 +417,13 @@ class ValueAddr(HashAddr):
417
417
  """A globally unique address of an immutable value.
418
418
 
419
419
  ValueAddr is a universal global identifier of any (constant) value.
420
+
420
421
  Using only the value's hash should (theoretically) be enough to
421
- uniquely address all possible data objects that the humanity will create
422
- in the foreseeable future (see, for example ipfs.io).
422
+ uniquely address all possible data objects that humanity will create
423
+ in the foreseeable future (see, for example, ipfs.io).
423
424
 
424
425
  However, an address also includes a descriptor with an optional suffix.
425
- It makes it easier for humans to interpret an address,
426
+ It makes it easier for humans to interpret an address
426
427
  and further decreases collision risk.
427
428
  """
428
429
  _containing_portals: set[str]
@@ -436,8 +437,8 @@ class ValueAddr(HashAddr):
436
437
  descriptor = data_value_addr.descriptor
437
438
  hash_signature = data_value_addr.hash_signature
438
439
  HashAddr.__init__(self
439
- , descriptor=descriptor
440
- , hash_signature=hash_signature)
440
+ , descriptor=descriptor
441
+ , hash_signature=hash_signature)
441
442
  return
442
443
 
443
444
  assert not isinstance(data, HashAddr), (
@@ -447,8 +448,8 @@ class ValueAddr(HashAddr):
447
448
  descriptor = self._build_descriptor(data)
448
449
  hash_signature = self._build_hash_signature(data)
449
450
  HashAddr.__init__(self
450
- , descriptor=descriptor
451
- , hash_signature=hash_signature)
451
+ , descriptor=descriptor
452
+ , hash_signature=hash_signature)
452
453
 
453
454
  self._value_cache = data
454
455
 
@@ -583,7 +584,10 @@ class ValueAddr(HashAddr):
583
584
  ) -> HashAddr:
584
585
  """(Re)construct address from text representations of descriptor and hash"""
585
586
 
586
- address = super().from_strings(descriptor=descriptor, hash_signature=hash_signature, assert_readiness=False)
587
+ address = super().from_strings(
588
+ descriptor=descriptor
589
+ , hash_signature=hash_signature
590
+ , assert_readiness=False)
587
591
  address._containing_portals = set()
588
592
  if assert_readiness:
589
593
  if not address.ready:
@@ -1,6 +1,13 @@
1
+ from typing import Any
2
+
1
3
  from .data_portal_core_classes import HashAddr
2
4
 
3
- def ready(obj, seen=None):
5
+ def ready(obj) -> bool:
6
+ """Return True if all objects in the data structure are ready."""
7
+ return _ready(obj)
8
+
9
+ def _ready(obj, seen=None):
10
+ """Return True if all objects in the data structure are ready."""
4
11
  if seen is None:
5
12
  seen = set()
6
13
 
@@ -12,14 +19,20 @@ def ready(obj, seen=None):
12
19
  if isinstance(obj, HashAddr):
13
20
  return obj.ready
14
21
  elif isinstance(obj, (list,tuple)):
15
- return all(ready(item, seen) for item in obj)
22
+ return all(_ready(item, seen) for item in obj)
16
23
  elif isinstance(obj, dict):
17
- return all(ready(value, seen) for key, value in obj.items())
24
+ return all(_ready(value, seen) for key, value in obj.items())
18
25
  else:
19
26
  return True
20
27
 
21
28
 
22
- def get(obj, seen=None):
29
+ def get(obj:Any) -> Any:
30
+ """Return copy of data structure with addresses replaced with objects."""
31
+ return _get(obj)
32
+
33
+
34
+ def _get(obj:Any, seen=None)->Any:
35
+ """Return copy of data structure with addresses replaced with objects."""
23
36
  if seen is None:
24
37
  seen = dict()
25
38
 
@@ -34,12 +47,12 @@ def get(obj, seen=None):
34
47
  seen[id(obj)] = placeholder
35
48
 
36
49
  if isinstance(obj, list):
37
- result = [get(item, seen) for item in obj]
50
+ result = [_get(item, seen) for item in obj]
38
51
  # Update the placeholder with the actual values
39
52
  placeholder.extend(result)
40
53
  return placeholder
41
54
  elif isinstance(obj, dict):
42
- result = {key: get(value, seen) for key, value in obj.items()}
55
+ result = {key: _get(value, seen) for key, value in obj.items()}
43
56
  # Update the placeholder with the actual values
44
57
  placeholder.update(result)
45
58
  return placeholder
@@ -47,9 +60,9 @@ def get(obj, seen=None):
47
60
  if isinstance(obj, HashAddr):
48
61
  result = obj.get()
49
62
  elif isinstance(obj, tuple):
50
- result = tuple(get(item, seen) for item in obj)
63
+ result = tuple(_get(item, seen) for item in obj)
51
64
  else:
52
65
  result = obj
53
66
 
54
67
  seen[id(obj)] = result
55
- return result
68
+ return result
@@ -0,0 +1,22 @@
1
+ """Classes and functions to work with application-level logging.
2
+
3
+ The main class in this sub-package is LoggingCodePortal, which extends DataPortal
4
+ to provide application-level logging capabilities for events and exceptions.
5
+ 'Application-level' means that the events and exceptions are logged into
6
+ location(s) that is(are) the same across the entire application.
7
+
8
+ LoggingCodePortal provides three attributes, _run_history, _crash_history,
9
+ and _event_history`, which are persistent dictionaries (PersiDict-s) that store
10
+ the exceptions and logged / recorded events.
11
+
12
+ Functions log_exception() and `log_event() are provided to log
13
+ exceptions and events. These methods are designed to be
14
+ called from anywhere in the application, and they will log the exception
15
+ or event into the best suitable LoggingPortal.
16
+ """
17
+
18
+ from .logging_portal_core_classes import *
19
+ from .kw_args import KwArgs, PackedKwArgs, UnpackedKwArgs
20
+ from .execution_environment_summary import *
21
+ from .logging_decorator import logging
22
+
@@ -1,22 +1,24 @@
1
1
  def _exception_needs_to_be_processed(exc_type, exc_value, trace_back) -> bool:
2
+ """Chack if the exception needs to be logged by Pythagoras"""
2
3
  if exc_type is None:
3
4
  return False
4
5
 
5
6
  if hasattr(exc_value, "__notes__"):
6
- if "__suppress_logging__" in exc_value.__notes__:
7
+ if "__suppress_pythagoras_logging__" in exc_value.__notes__:
7
8
  return False
8
9
  else:
9
10
  return True
10
11
 
11
- if hasattr(exc_value, "__suppress_logging__"):
12
+ if hasattr(exc_value, "__suppress_pythagoras_logging__"):
12
13
  return False
13
14
 
14
15
  return True
15
16
 
16
17
 
17
18
  def _mark_exception_as_processed(exc_type, exc_value, trace_back) -> None:
19
+ """Mark the exception as processed by Pythagoras"""
18
20
  if hasattr(exc_value, "add_note"):
19
21
  exc_value.add_note(
20
- "__suppress_logging__")
22
+ "__suppress_pythagoras_logging__")
21
23
  else:
22
- exc_value.__suppress_logging__ = True
24
+ exc_value.__suppress_pythagoras_logging__ = True
@@ -7,7 +7,7 @@ from parameterizable import sort_dict_by_keys
7
7
  class KwArgs(dict):
8
8
  """ A class that encapsulates keyword arguments for a function call.
9
9
 
10
- It allows to "normalize" the dictionary by sorting the keys
10
+ It allows "normalizing" the dictionary by sorting the keys
11
11
  and replacing values with their hash addresses
12
12
  in order to always get the same hash values
13
13
  for the same lists of arguments.
@@ -75,7 +75,6 @@ class LoggingFn(StorableFn):
75
75
  return StorableFn.portal.__get__(self)
76
76
 
77
77
 
78
-
79
78
  class LoggingFnCallSignature:
80
79
  """A signature of a call to a (logging) function.
81
80
 
@@ -125,9 +124,9 @@ class LoggingFnCallSignature:
125
124
 
126
125
 
127
126
  def _invalidate_cache(self):
128
- """Invalidate the object's attribute cache.
127
+ """Invalidate the function's attribute cache.
129
128
 
130
- If the object's attribute named ATTR is cached,
129
+ If the function's attribute named ATTR is cached,
131
130
  its cached value will be stored in an attribute named _ATTR_cache
132
131
  This method should delete all such attributes.
133
132
  """
@@ -389,7 +388,6 @@ class LoggingFnExecutionRecord(NotPicklable):
389
388
  f"{self.call_signature.fn_name} execution results.")
390
389
 
391
390
 
392
-
393
391
  class LoggingFnExecutionFrame(NotPicklable):
394
392
  call_stack: list[LoggingFnExecutionFrame] = []
395
393
 
@@ -512,7 +510,7 @@ class LoggingCodePortal(DataPortal):
512
510
  from which the event or exception is raised.
513
511
 
514
512
  The class provides two dictionaries, `_crash_history` and `event_log`,
515
- to store the exceptions history and event log respectively.
513
+ to store the exception history and event log respectively.
516
514
 
517
515
  Static methods `log_exception` and `log_event` are provided to log
518
516
  exceptions and events. These methods are designed to be
@@ -0,0 +1,15 @@
1
+ """Classes and functions that allow safe execution of code.
2
+
3
+ The main classes in this sub-package are SafeCodePortal and SafeFn,
4
+ which extend LoggingCodePortal and LoggingFn
5
+ to provide safe execution capabilities for logging functions.
6
+
7
+ SafeFn functions can't access (hence can't harm) any data/devices outside
8
+ the function's local scope and the portal.
9
+
10
+ This functionality has not been implemented yet.
11
+ It will be done soon by integrating https://pypi.org/project/RestrictedPython/
12
+ """
13
+
14
+ from .safe_portal_core_classes import *
15
+ from .safe_decorator import *
@@ -7,7 +7,7 @@ from .safe_portal_core_classes import SafeFn, SafeCodePortal
7
7
 
8
8
 
9
9
  class safe(logging):
10
- """A decorator that converts a Python function into an SafeFn object.
10
+ """A decorator that converts a Python function into a SafeFn object.
11
11
  """
12
12
 
13
13
  def __init__(self
@@ -1,3 +1,16 @@
1
+ """Classes and functions that allow safe execution of code.
2
+
3
+ The main classes in this sub-package are SafeCodePortal and SafeFn,
4
+ which extend LoggingCodePortal and LoggingFn
5
+ to provide safe execution capabilities for logging functions.
6
+
7
+ SafeFn functions can't access (hence can't harm) any data/devices outside
8
+ the function's local scope and the portal.
9
+
10
+ This functionality has not been implemented yet.
11
+ It will be done soon by integrating https://pypi.org/project/RestrictedPython/
12
+ """
13
+
1
14
  from __future__ import annotations
2
15
 
3
16
  from typing import Callable
@@ -49,11 +62,4 @@ class SafeFn(LoggingFn):
49
62
  return LoggingFn.portal.__get__(self)
50
63
 
51
64
 
52
- # @portal.setter
53
- # def portal(self, new_portal: SafeCodePortal) -> None:
54
- # if not isinstance(new_portal, SafeCodePortal):
55
- # raise TypeError("portal must be a LoggingCodePortal instance")
56
- # LoggingFn.portal.__set__(self, new_portal)
57
-
58
-
59
65
  register_parameterizable_class(SafeCodePortal)
@@ -0,0 +1,43 @@
1
+ """Classes and utilities to work with autonomous functions.
2
+
3
+ In essence, an 'autonomous' function contains self-sufficient code
4
+ that does not depend on external imports or definitions. All required
5
+ imports should be done inside the function body.
6
+ Only ordinary functions can be autonomous.
7
+
8
+ Autonomous functions are always allowed to use the built-in objects
9
+ (functions, types, variables), as well as global objects,
10
+ explicitly imported inside the function body. An autonomous function
11
+ is allowed to use other autonomous functions if they are passed as
12
+ input arguments to the function.
13
+
14
+ Autonomous functions are not allowed to:
15
+
16
+ * use global objects, imported or defined outside the function body
17
+ (except built-in objects);
18
+ * use yield (yield from) statements;
19
+ * use nonlocal variables, referencing the outside objects.
20
+
21
+ Autonomous functions can have nested functions and classes.
22
+
23
+ Only ordinary functions can be autonomous. Asynchronous functions, closures,
24
+ class methods, and lambda functions cannot be autonomous.
25
+
26
+ Autonomous functions support partial application of arguments:
27
+ the process of pre-filling some arguments of a function,
28
+ producing a new autonomous function that takes the remaining arguments.
29
+
30
+ This module defines a decorator which is used to
31
+ inform Pythagoras that a function is intended to be autonomous
32
+ and to enforce autonomicity requirements.
33
+
34
+ Applying a decorator to a function ensures both static and runtime autonomicity
35
+ checks are performed for the function. Static checks happen at the time
36
+ of decoration, while runtime checks happen at the time of function execution.
37
+ """
38
+
39
+
40
+ from .autonomous_portal_core_classes import AutonomousFn, AutonomousCodePortal
41
+
42
+ from .autonomous_decorators import autonomous
43
+
@@ -0,0 +1,72 @@
1
+ """Support for work with autonomous functions.
2
+
3
+ In essence, an 'autonomous' function contains self-sufficient code
4
+ that does not depend on external imports or definitions. All required
5
+ imports should be done inside the function body.
6
+ Only ordinary functions can be autonomous.
7
+
8
+ Autonomous functions are always allowed to use the built-in objects
9
+ (functions, types, variables), as well as global objects,
10
+ explicitly imported inside the function body. An autonomous function
11
+ is allowed to use other autonomous functions if they are passed as
12
+ input arguments to the function.
13
+
14
+ Autonomous functions are not allowed to:
15
+
16
+ * use global objects, imported or defined outside the function body
17
+ (except built-in objects);
18
+ * use yield (yield from) statements;
19
+ * use nonlocal variables, referencing the outside objects.
20
+
21
+ Autonomous functions can have nested functions and classes.
22
+
23
+ Only ordinary functions can be autonomous. Asynchronous functions, closures,
24
+ class methods, and lambda functions cannot be autonomous.
25
+
26
+ Autonomous functions support partial application of arguments:
27
+ the process of pre-filling some arguments of a function,
28
+ producing a new autonomous function that takes the remaining arguments.
29
+
30
+ This module defines a decorator which is used to
31
+ inform Pythagoras that a function is intended to be autonomous
32
+ and to enforce autonomicity requirements.
33
+
34
+ Applying a decorator to a function ensures both static and runtime autonomicity
35
+ checks are performed for the function. Static checks happen at the time
36
+ of decoration, while runtime checks happen at the time of function execution.
37
+ """
38
+ from typing import Callable
39
+
40
+ from .._050_safe_code_portals import safe
41
+ from .autonomous_portal_core_classes import AutonomousFn, AutonomousCodePortal
42
+ from persidict import Joker, KEEP_CURRENT
43
+
44
+
45
+ class autonomous(safe):
46
+ """Decorator for enforcing autonomicity requirements for functions.
47
+
48
+ An autonomous function is only allowed to use the built-in objects
49
+ (functions, types, variables), as well as global objects,
50
+ accessible via import statements inside the function body.
51
+ """
52
+ _fixed_args: dict|None
53
+
54
+ def __init__(self
55
+ , fixed_kwargs: dict | None = None
56
+ , excessive_logging: bool|Joker = KEEP_CURRENT
57
+ , portal: AutonomousCodePortal | None = None
58
+ ):
59
+ assert isinstance(portal, AutonomousCodePortal) or portal is None
60
+ assert isinstance(fixed_kwargs, dict) or fixed_kwargs is None
61
+ safe.__init__(self=self
62
+ , portal=portal
63
+ , excessive_logging=excessive_logging)
64
+ self._fixed_kwargs = fixed_kwargs
65
+
66
+
67
+ def __call__(self, fn: Callable|str) -> AutonomousFn:
68
+ wrapper = AutonomousFn(fn
69
+ ,portal=self._portal
70
+ ,fixed_kwargs=self._fixed_kwargs
71
+ ,excessive_logging=self._excessive_logging)
72
+ return wrapper
@@ -5,7 +5,7 @@ from typing import Callable, Any
5
5
 
6
6
  from persidict import PersiDict, Joker, KEEP_CURRENT
7
7
  from .._020_ordinary_code_portals.code_normalizer import _pythagoras_decorator_names
8
- from .. import DataPortal
8
+ from .._030_data_portals import DataPortal
9
9
  from .._040_logging_code_portals import KwArgs
10
10
 
11
11
  from .._060_autonomous_code_portals.names_usage_analyzer import (
@@ -14,9 +14,6 @@ from .._060_autonomous_code_portals.names_usage_analyzer import (
14
14
  from .._050_safe_code_portals.safe_portal_core_classes import (
15
15
  SafeFn, SafeCodePortal)
16
16
 
17
- import pythagoras as pth
18
-
19
-
20
17
  class AutonomousCodePortal(SafeCodePortal):
21
18
 
22
19
  def __init__(self
@@ -36,7 +33,7 @@ class AutonomousFn(SafeFn):
36
33
  _fixed_kwargs_packed: KwArgs | None
37
34
 
38
35
  def __init__(self, fn: Callable|str|SafeFn
39
- , fixed_kwargs: dict|None = None
36
+ , fixed_kwargs: dict[str,Any]|None = None
40
37
  , excessive_logging: bool|Joker = KEEP_CURRENT
41
38
  , portal: AutonomousCodePortal|None = None):
42
39
  super().__init__(fn=fn
@@ -106,6 +103,14 @@ class AutonomousFn(SafeFn):
106
103
 
107
104
 
108
105
  def fix_kwargs(self, **kwargs) -> AutonomousFn:
106
+ """Create a new function by pre-filling some arguments.
107
+
108
+ This is called a partial application in functional programming
109
+ It allows creating specialized functions from general ones by
110
+ transforming a function with multiple parameters
111
+ into another function with fewer parameters by fixing some arguments.
112
+ """
113
+
109
114
  overlapping_keys = set(kwargs.keys()) & set(self.fixed_kwargs.keys())
110
115
  assert len(overlapping_keys) == 0
111
116
  new_fixed_kwargs = {**self.fixed_kwargs,**kwargs}
@@ -139,7 +144,14 @@ class AutonomousFn(SafeFn):
139
144
 
140
145
 
141
146
  def _invalidate_cache(self):
147
+ """Invalidate the function's attribute cache.
148
+
149
+ If the function's attribute named ATTR is cached,
150
+ its cached value will be stored in an attribute named _ATTR_cache
151
+ This method should delete all such attributes.
152
+ """
142
153
  super()._invalidate_cache()
143
154
  if hasattr(self, "_fixed_kwargs_cached"):
144
- assert hasattr(self, "_fixed_kwargs_packed"), "Premature cache invalidation: fixed_kwargs_packed is missing."
155
+ assert (hasattr(self, "_fixed_kwargs_packed")
156
+ , "Premature cache invalidation: fixed_kwargs_packed is missing.")
145
157
  del self._fixed_kwargs_cached
@@ -14,7 +14,7 @@ class NamesUsedInFunction:
14
14
  self.accessible = set() # all names, currently accessable within the function
15
15
 
16
16
  class NamesUsageAnalyzer(ast.NodeVisitor):
17
- """Collect data needed to analyze function autonomy.
17
+ """Collect data needed to analyze function autonomicity.
18
18
 
19
19
  This class is a visitor of an AST (Abstract Syntax Tree) that collects data
20
20
  needed to analyze function autonomy.