pythagoras 0.22.1__py3-none-any.whl → 0.23.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythagoras/_010_basic_portals/basic_portal_core_classes.py +3 -3
- pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +4 -4
- pythagoras/_030_data_portals/__init__.py +11 -8
- pythagoras/_030_data_portals/data_portal_core_classes.py +32 -28
- pythagoras/_030_data_portals/ready_and_get.py +21 -8
- pythagoras/_040_logging_code_portals/__init__.py +7 -13
- pythagoras/_040_logging_code_portals/exception_processing_tracking.py +6 -4
- pythagoras/_040_logging_code_portals/kw_args.py +1 -1
- pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +3 -5
- pythagoras/_050_safe_code_portals/__init__.py +13 -0
- pythagoras/_050_safe_code_portals/safe_decorator.py +1 -1
- pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +13 -7
- pythagoras/_060_autonomous_code_portals/__init__.py +14 -25
- pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +21 -40
- pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +18 -6
- pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +1 -1
- pythagoras/_070_protected_code_portals/__init__.py +17 -1
- pythagoras/_070_protected_code_portals/basic_pre_validators.py +8 -8
- pythagoras/_070_protected_code_portals/package_manager.py +0 -1
- pythagoras/_070_protected_code_portals/protected_decorators.py +2 -2
- pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +55 -21
- pythagoras/_070_protected_code_portals/{OK_const.py → validation_succesful_const.py} +4 -2
- pythagoras/_080_pure_code_portals/__init__.py +4 -18
- pythagoras/_080_pure_code_portals/pure_core_classes.py +32 -19
- pythagoras/_080_pure_code_portals/pure_decorator.py +27 -6
- pythagoras/_090_swarming_portals/__init__.py +11 -0
- pythagoras/_090_swarming_portals/swarming_portals.py +18 -0
- pythagoras/_100_top_level_API/default_local_portal.py +4 -2
- pythagoras/_900_project_stats_collector/__init__.py +1 -0
- pythagoras/_900_project_stats_collector/project_analyzer.py +1 -0
- pythagoras/__init__.py +13 -21
- pythagoras/core/__init__.py +1 -1
- {pythagoras-0.22.1.dist-info → pythagoras-0.23.0.dist-info}/METADATA +1 -1
- {pythagoras-0.22.1.dist-info → pythagoras-0.23.0.dist-info}/RECORD +35 -35
- {pythagoras-0.22.1.dist-info → pythagoras-0.23.0.dist-info}/WHEEL +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
|
|
|
@@ -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
|
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
"""Classes and functions to work with application-level logging.
|
|
2
2
|
|
|
3
|
-
The main class in this sub-package is LoggingCodePortal, which extends
|
|
3
|
+
The main class in this sub-package is LoggingCodePortal, which extends DataPortal
|
|
4
4
|
to provide application-level logging capabilities for events and exceptions.
|
|
5
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
|
-
and does(do) not depend on the specific function from which
|
|
8
|
-
the even or exception is originated.
|
|
6
|
+
location(s) that is(are) the same across the entire application.
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
which are persistent dictionaries (PersiDict-s) that store
|
|
12
|
-
the exceptions
|
|
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.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Functions log_exception() and `log_event() are provided to log
|
|
15
13
|
exceptions and events. These methods are designed to be
|
|
16
14
|
called from anywhere in the application, and they will log the exception
|
|
17
|
-
or event into
|
|
18
|
-
those that have been registered with the current
|
|
19
|
-
stack of nested 'with' statements.
|
|
20
|
-
|
|
21
|
-
The class also supports logging uncaught exceptions globally.
|
|
15
|
+
or event into the best suitable LoggingPortal.
|
|
22
16
|
"""
|
|
23
17
|
|
|
24
18
|
from .logging_portal_core_classes import *
|
|
@@ -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
|
|
@@ -1,2 +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
|
+
|
|
1
14
|
from .safe_portal_core_classes import *
|
|
2
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)
|
|
@@ -1,46 +1,35 @@
|
|
|
1
1
|
"""Classes and utilities to work with autonomous functions.
|
|
2
2
|
|
|
3
|
-
In
|
|
3
|
+
In essence, an 'autonomous' function contains self-sufficient code
|
|
4
4
|
that does not depend on external imports or definitions. All required
|
|
5
|
-
imports should be done inside the function body.
|
|
6
|
-
can be autonomous.
|
|
5
|
+
imports should be done inside the function body.
|
|
6
|
+
Only ordinary functions can be autonomous.
|
|
7
7
|
|
|
8
8
|
Autonomous functions are always allowed to use the built-in objects
|
|
9
9
|
(functions, types, variables), as well as global objects,
|
|
10
10
|
explicitly imported inside the function body. An autonomous function
|
|
11
|
-
is allowed to use other autonomous functions
|
|
12
|
-
|
|
13
|
-
if they belong to the same island.
|
|
14
|
-
|
|
15
|
-
An island is namespace that groups autonomous functions together.
|
|
16
|
-
An autonomous function can use other autonomous functions from the same island
|
|
17
|
-
without explicitly importing them.
|
|
11
|
+
is allowed to use other autonomous functions if they are passed as
|
|
12
|
+
input arguments to the function.
|
|
18
13
|
|
|
19
14
|
Autonomous functions are not allowed to:
|
|
20
15
|
|
|
21
16
|
* use global objects, imported or defined outside the function body
|
|
22
|
-
(except built-in objects
|
|
23
|
-
from the same island);
|
|
17
|
+
(except built-in objects);
|
|
24
18
|
* use yield (yield from) statements;
|
|
25
19
|
* use nonlocal variables, referencing the outside objects.
|
|
26
20
|
|
|
27
|
-
If an autonomous function is using other autonomous functions
|
|
28
|
-
from the same island, it is called "loosely autonomous function".
|
|
29
|
-
Otherwise, it is called "strictly autonomous function".
|
|
30
|
-
|
|
31
21
|
Autonomous functions can have nested functions and classes.
|
|
32
22
|
|
|
33
|
-
Only
|
|
34
|
-
class methods and lambda functions cannot be autonomous.
|
|
23
|
+
Only ordinary functions can be autonomous. Asynchronous functions, closures,
|
|
24
|
+
class methods, and lambda functions cannot be autonomous.
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@autonomous() and @strictly_autonomous() .
|
|
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.
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
44
33
|
|
|
45
34
|
Applying a decorator to a function ensures both static and runtime autonomicity
|
|
46
35
|
checks are performed for the function. Static checks happen at the time
|
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
"""Support for work with autonomous functions.
|
|
2
2
|
|
|
3
|
-
In essence, an autonomous function contains self-sufficient code
|
|
4
|
-
that does not depend on external imports or definitions.
|
|
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.
|
|
5
7
|
|
|
6
8
|
Autonomous functions are always allowed to use the built-in objects
|
|
7
9
|
(functions, types, variables), as well as global objects,
|
|
8
10
|
explicitly imported inside the function body. An autonomous function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
is allowed to use other autonomous functions if they are passed as
|
|
12
|
+
input arguments to the function.
|
|
11
13
|
|
|
12
14
|
Autonomous functions are not allowed to:
|
|
15
|
+
|
|
13
16
|
* use global objects, imported or defined outside the function body
|
|
14
|
-
(except built-in objects
|
|
15
|
-
other autonomous functions from the same island);
|
|
17
|
+
(except built-in objects);
|
|
16
18
|
* use yield (yield from) statements;
|
|
17
19
|
* use nonlocal variables, referencing the outside objects.
|
|
18
20
|
|
|
19
|
-
If an autonomous function is allowed to use other autonomous functions,
|
|
20
|
-
it is called "loosely autonomous function". Otherwise, it is called
|
|
21
|
-
"strictly autonomous function".
|
|
22
|
-
|
|
23
21
|
Autonomous functions can have nested functions and classes.
|
|
24
22
|
|
|
25
|
-
Only ordinary functions can be autonomous. Asynchronous functions,
|
|
26
|
-
class methods and lambda functions cannot be autonomous.
|
|
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.
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
and to enforce autonomicity requirements
|
|
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.
|
|
31
37
|
"""
|
|
32
38
|
from typing import Callable
|
|
33
39
|
|
|
@@ -59,31 +65,6 @@ class autonomous(safe):
|
|
|
59
65
|
|
|
60
66
|
|
|
61
67
|
def __call__(self, fn: Callable|str) -> AutonomousFn:
|
|
62
|
-
"""Decorator for autonomous functions.
|
|
63
|
-
|
|
64
|
-
It does both static and dynamic checks for autonomous functions.
|
|
65
|
-
|
|
66
|
-
Static checks: it checks whether the function uses any global
|
|
67
|
-
non-built-in objects which do not have associated import statements
|
|
68
|
-
inside the function. If allow_idempotent==True,
|
|
69
|
-
global idempotent functions are also allowed.
|
|
70
|
-
The decorator also checks whether the function is using
|
|
71
|
-
any non-local objects variables, and whether the function
|
|
72
|
-
has yield / yield from statements in its code. If static checks fail,
|
|
73
|
-
the decorator throws a FunctionAutonomicityError exception.
|
|
74
|
-
|
|
75
|
-
Dynamic checks: during the execution time it hides all the global
|
|
76
|
-
and non-local objects from the function, except the built-in ones
|
|
77
|
-
(and idempotent ones, if allow_idempotent==True).
|
|
78
|
-
If a function tries to use a non-built-in
|
|
79
|
-
(and non-idempotent, if allow_idempotent==True)
|
|
80
|
-
object without explicitly importing it inside the function body,
|
|
81
|
-
it will result in raising an exception.
|
|
82
|
-
|
|
83
|
-
Currently, neither static nor dynamic checks are guaranteed to catch
|
|
84
|
-
all possible violations of function autonomy requirements.
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
68
|
wrapper = AutonomousFn(fn
|
|
88
69
|
,portal=self._portal
|
|
89
70
|
,fixed_kwargs=self._fixed_kwargs
|