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.
- {pythagoras-0.22.1 → pythagoras-0.23.0}/PKG-INFO +1 -1
- {pythagoras-0.22.1 → pythagoras-0.23.0}/pyproject.toml +1 -1
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/basic_portal_core_classes.py +3 -3
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +4 -4
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/__init__.py +11 -8
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/data_portal_core_classes.py +32 -28
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/ready_and_get.py +21 -8
- pythagoras-0.23.0/src/pythagoras/_040_logging_code_portals/__init__.py +22 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/exception_processing_tracking.py +6 -4
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/kw_args.py +1 -1
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +3 -5
- pythagoras-0.23.0/src/pythagoras/_050_safe_code_portals/__init__.py +15 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_050_safe_code_portals/safe_decorator.py +1 -1
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +13 -7
- pythagoras-0.23.0/src/pythagoras/_060_autonomous_code_portals/__init__.py +43 -0
- pythagoras-0.23.0/src/pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +72 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +18 -6
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +1 -1
- pythagoras-0.23.0/src/pythagoras/_070_protected_code_portals/__init__.py +22 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/basic_pre_validators.py +8 -8
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/package_manager.py +0 -1
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/protected_decorators.py +2 -2
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +55 -21
- 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
- pythagoras-0.23.0/src/pythagoras/_080_pure_code_portals/__init__.py +28 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_080_pure_code_portals/pure_core_classes.py +32 -19
- pythagoras-0.23.0/src/pythagoras/_080_pure_code_portals/pure_decorator.py +56 -0
- pythagoras-0.23.0/src/pythagoras/_090_swarming_portals/__init__.py +12 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_090_swarming_portals/swarming_portals.py +18 -0
- pythagoras-0.23.0/src/pythagoras/_100_top_level_API/default_local_portal.py +8 -0
- pythagoras-0.23.0/src/pythagoras/_900_project_stats_collector/__init__.py +1 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_900_project_stats_collector/project_analyzer.py +1 -0
- pythagoras-0.23.0/src/pythagoras/__init__.py +31 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/core/__init__.py +1 -1
- pythagoras-0.22.1/src/pythagoras/_040_logging_code_portals/__init__.py +0 -28
- pythagoras-0.22.1/src/pythagoras/_050_safe_code_portals/__init__.py +0 -2
- pythagoras-0.22.1/src/pythagoras/_060_autonomous_code_portals/__init__.py +0 -54
- pythagoras-0.22.1/src/pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +0 -91
- pythagoras-0.22.1/src/pythagoras/_070_protected_code_portals/__init__.py +0 -6
- pythagoras-0.22.1/src/pythagoras/_080_pure_code_portals/__init__.py +0 -42
- pythagoras-0.22.1/src/pythagoras/_080_pure_code_portals/pure_decorator.py +0 -35
- pythagoras-0.22.1/src/pythagoras/_090_swarming_portals/__init__.py +0 -1
- pythagoras-0.22.1/src/pythagoras/_100_top_level_API/default_local_portal.py +0 -6
- pythagoras-0.22.1/src/pythagoras/_900_project_stats_collector/__init__.py +0 -0
- pythagoras-0.22.1/src/pythagoras/__init__.py +0 -39
- {pythagoras-0.22.1 → pythagoras-0.23.0}/README.md +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/.DS_Store +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/__init__.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/exceptions.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/long_infoname.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/not_picklable_class.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/portal_tester.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_010_basic_portals/post_init_metaclass.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/__init__.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/code_normalizer.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/function_processing.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/storable_decorator.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/execution_environment_summary.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/logging_decorator.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/notebook_checker.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/output_capturer.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_040_logging_code_portals/uncaught_exceptions.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/list_flattener.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/system_utils.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_070_protected_code_portals/validator_fn_classes.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_090_swarming_portals/output_suppressor.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_100_top_level_API/__init__.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_100_top_level_API/top_level_API.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/__init__.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/hash_signatures.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/node_signatures.py +0 -0
- {pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_800_signatures_and_converters/random_signatures.py +0 -0
|
@@ -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"].
|
|
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
|
|
293
|
+
"""Invalidate the portal's attribute cache.
|
|
294
294
|
|
|
295
|
-
If the
|
|
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
|
|
204
|
+
"""Invalidate the function's attribute cache.
|
|
205
205
|
|
|
206
|
-
If the
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
21
|
-
or as a shared bucket in a cloud storage (e.g
|
|
22
|
-
In this case, a ValueAddr becomes a part of file path
|
|
23
|
-
or a URL (e.g
|
|
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
|
|
{pythagoras-0.22.1 → pythagoras-0.23.0}/src/pythagoras/_030_data_portals/data_portal_core_classes.py
RENAMED
|
@@ -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
|
-
|
|
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,
|
|
55
|
-
in a 'with' statement that
|
|
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
|
|
257
|
+
"""Invalidate the function's attribute cache.
|
|
262
258
|
|
|
263
|
-
If the
|
|
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
|
-
|
|
290
|
-
A descriptor contains human-readable information
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
440
|
-
|
|
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
|
-
|
|
451
|
-
|
|
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(
|
|
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
|
|
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(
|
|
22
|
+
return all(_ready(item, seen) for item in obj)
|
|
16
23
|
elif isinstance(obj, dict):
|
|
17
|
-
return all(
|
|
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
|
|
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 = [
|
|
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:
|
|
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(
|
|
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 "
|
|
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, "
|
|
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
|
-
"
|
|
22
|
+
"__suppress_pythagoras_logging__")
|
|
21
23
|
else:
|
|
22
|
-
exc_value.
|
|
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
|
|
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
|
|
127
|
+
"""Invalidate the function's attribute cache.
|
|
129
128
|
|
|
130
|
-
If the
|
|
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
|
|
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 *
|
|
@@ -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")
|
|
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
|
|
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.
|