pythagoras 0.24.2__py3-none-any.whl → 0.24.4__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.
Files changed (24) hide show
  1. pythagoras/.DS_Store +0 -0
  2. pythagoras/_010_basic_portals/__init__.py +0 -1
  3. pythagoras/_010_basic_portals/basic_portal_core_classes.py +2 -14
  4. pythagoras/_020_ordinary_code_portals/code_normalizer.py +37 -9
  5. pythagoras/_020_ordinary_code_portals/function_processing.py +58 -15
  6. pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +14 -0
  7. pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +196 -24
  8. pythagoras/_030_data_portals/data_portal_core_classes.py +74 -22
  9. pythagoras/_030_data_portals/ready_and_get.py +45 -4
  10. pythagoras/_030_data_portals/storable_decorator.py +18 -1
  11. pythagoras/_040_logging_code_portals/exception_processing_tracking.py +30 -2
  12. pythagoras/_040_logging_code_portals/execution_environment_summary.py +60 -24
  13. pythagoras/_040_logging_code_portals/kw_args.py +74 -12
  14. pythagoras/_040_logging_code_portals/logging_decorator.py +23 -1
  15. pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +368 -15
  16. pythagoras/_040_logging_code_portals/notebook_checker.py +9 -1
  17. pythagoras/_040_logging_code_portals/uncaught_exceptions.py +40 -0
  18. pythagoras/_050_safe_code_portals/safe_decorator.py +27 -1
  19. pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +87 -11
  20. pythagoras/_090_swarming_portals/swarming_portals.py +4 -6
  21. {pythagoras-0.24.2.dist-info → pythagoras-0.24.4.dist-info}/METADATA +3 -3
  22. {pythagoras-0.24.2.dist-info → pythagoras-0.24.4.dist-info}/RECORD +23 -24
  23. pythagoras/_010_basic_portals/not_picklable_class.py +0 -36
  24. {pythagoras-0.24.2.dist-info → pythagoras-0.24.4.dist-info}/WHEEL +0 -0
@@ -14,10 +14,7 @@ from .._800_signatures_and_converters import get_hash_signature
14
14
  from .._010_basic_portals.basic_portal_core_classes import (
15
15
  _describe_persistent_characteristic
16
16
  , _describe_runtime_characteristic)
17
- from .._020_ordinary_code_portals import (
18
- get_normalized_function_source
19
- ,OrdinaryCodePortal
20
- ,OrdinaryFn)
17
+ from .._020_ordinary_code_portals import OrdinaryCodePortal ,OrdinaryFn
21
18
  from persidict import WriteOnceDict
22
19
 
23
20
  TOTAL_VALUES_TXT = "Values, total"
@@ -25,36 +22,63 @@ PROBABILITY_OF_CHECKS_TXT = "Probability of consistency checks"
25
22
 
26
23
 
27
24
  def get_active_data_portal() -> DataPortal:
25
+ """Return the currently active DataPortal.
26
+
27
+ Returns:
28
+ DataPortal: The portal that is active in the current context ("with" block).
29
+
30
+ Raises:
31
+ AssertionError: If the active portal is not an instance of DataPortal.
32
+ """
28
33
  portal = get_active_portal()
29
34
  assert isinstance(portal, DataPortal)
30
35
  return portal
31
36
 
32
37
 
33
38
  def get_nonactive_data_portals() -> list[DataPortal]:
34
- """Get a list of all nonactive DataPortals"""
39
+ """Return all known DataPortals that are not currently active.
40
+
41
+ Returns:
42
+ list[DataPortal]: A list of DataPortal instances that are available to
43
+ the runtime but are not the current active portal stack.
44
+
45
+ Raises:
46
+ AssertionError: If any returned portal is not an instance of DataPortal.
47
+ """
35
48
  portals = get_nonactive_portals()
36
49
  assert all(isinstance(p, DataPortal) for p in portals)
37
50
  return portals
38
51
 
39
52
 
40
53
  class DataPortal(OrdinaryCodePortal):
41
- """A portal that persistently stores values.
42
-
43
- Immutable values are accessible via their hash_address-es,
44
- which are unique identifiers of the values.
54
+ """A portal that persistently stores and retrieves immutable values.
55
+
56
+ A DataPortal is responsible for addressing, storing, and retrieving
57
+ values by their content-derived addresses. It exposes a context manager
58
+ interface so that code running within a "with portal:" block treats that
59
+ portal as the active one.
60
+
61
+ Behavior overview:
62
+ - Content-addressed storage: immutable values are referenced by a
63
+ HashAddr/ValueAddr that is derived from the value's bytes and a
64
+ human-readable descriptor.
65
+ - Transparent fetch and replication: if a value is not present in the
66
+ active portal but exists in any other known portal, it is fetched
67
+ and copied into the active portal on demand.
68
+ - Config settings: portal-specific and function-specific settings are
69
+ persisted in a dedicated config store.
70
+ - Consistency checks: the underlying persistent dictionary can perform
71
+ random, probabilistic consistency checks controlled by the
72
+ p_consistency_checks parameter.
73
+
74
+ Note:
75
+ Use the portal as a context manager whenever code performs I/O with
76
+ the portal (reading or storing values):
45
77
 
46
- If the current portal does not contain a specific value,
47
- referenced by a hash_address, but this value can be retrieved
48
- from another portal known to the program,
49
- the value will be automatically copied to the current portal.
50
-
51
- A portal can serve as a context manager, enabling the use of the
52
- 'with' statement to support portal-aware code blocks. If some code is
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.
78
+ with portal:
79
+ addr = ValueAddr(data)
80
+ value = addr.get()
56
81
 
57
- DataPortal also supports random consistency checks.
58
82
  """
59
83
 
60
84
  _value_store: WriteOnceDict | None
@@ -67,6 +91,18 @@ class DataPortal(OrdinaryCodePortal):
67
91
  , root_dict: PersiDict|str|None = None
68
92
  , p_consistency_checks: float|Joker = KEEP_CURRENT
69
93
  ):
94
+ """Initialize a DataPortal.
95
+
96
+ Args:
97
+ root_dict: Prototype PersiDict or a path/URI used to create
98
+ a persistent dictionary for internal stores. If None, uses
99
+ the parent's default.
100
+ p_consistency_checks: Probability in [0, 1] or KEEP_CURRENT Joker
101
+ that controls random consistency checks of the value store.
102
+
103
+ Raises:
104
+ ValueError: If p_consistency_checks is not in [0, 1] and not a Joker.
105
+ """
70
106
  OrdinaryCodePortal.__init__(self, root_dict = root_dict)
71
107
  del root_dict
72
108
  self._auxiliary_config_params_at_init = dict()
@@ -102,14 +138,24 @@ class DataPortal(OrdinaryCodePortal):
102
138
 
103
139
 
104
140
  def _post_init_hook(self) -> None:
105
- """Hook to be called after all __init__ methods are done"""
141
+ """Finalize initialization after __init__ completes across the MRO.
142
+
143
+ Ensures that auxiliary configuration parameters are persisted and that
144
+ the value store is configured according to the portal's
145
+ p_consistency_checks setting.
146
+ """
106
147
  super()._post_init_hook()
107
148
  self._persist_initial_config_params()
108
149
  self._value_store.p_consistency_checks = self.p_consistency_checks
109
150
 
110
151
 
111
152
  def get_params(self) -> dict:
112
- """Get the portal's configuration parameters"""
153
+ """Return the portal's configuration parameters.
154
+
155
+ Returns:
156
+ dict: A sorted dictionary of base parameters augmented with
157
+ auxiliary config entries defined at initialization.
158
+ """
113
159
  params = super().get_params()
114
160
  params.update(self._auxiliary_config_params_at_init)
115
161
  sorted_params = sort_dict_by_keys(params)
@@ -201,6 +247,12 @@ class StorableFn(OrdinaryFn):
201
247
  , fn: Callable | str
202
248
  , portal: DataPortal | None = None
203
249
  ):
250
+ """Create a storable wrapper around an ordinary function.
251
+
252
+ Args:
253
+ fn: A Python function or its source code (normalized) as a string.
254
+ portal: Optional DataPortal to bind the function to.
255
+ """
204
256
  OrdinaryFn.__init__(self, fn=fn, portal=portal)
205
257
  self._auxiliary_config_params_at_init = dict()
206
258
 
@@ -5,11 +5,32 @@ from .data_portal_core_classes import HashAddr
5
5
 
6
6
 
7
7
  def ready(obj) -> bool:
8
- """Return True if all objects in the data structure are ready."""
8
+ """Check readiness of a possibly nested data structure.
9
+
10
+ Recursively inspects the structure and returns True only if every
11
+ HashAddr/ValueAddr contained is ready. Strings and bytes-like objects are
12
+ treated as atomic. Lists, tuples, and dicts are traversed recursively.
13
+
14
+ Args:
15
+ obj: Any Python object, possibly a nested combination of lists, tuples,
16
+ and dicts containing HashAddr/ValueAddr objects.
17
+
18
+ Returns:
19
+ bool: True if all contained addresses are ready; False otherwise.
20
+ """
9
21
  return _ready_impl(obj)
10
22
 
11
23
  def _ready_impl(obj, seen=None):
12
- """Return True if all objects in the data structure are ready."""
24
+ """Implementation helper for ready() with cycle protection.
25
+
26
+ Args:
27
+ obj: Object under inspection.
28
+ seen: A set of object ids already visited to prevent infinite loops
29
+ on cyclic graphs.
30
+
31
+ Returns:
32
+ bool: True if obj and all nested addresses are ready.
33
+ """
13
34
  if seen is None:
14
35
  seen = set()
15
36
  elif id(obj) in seen:
@@ -36,12 +57,32 @@ def _ready_impl(obj, seen=None):
36
57
 
37
58
 
38
59
  def get(obj:Any) -> Any:
39
- """Return copy of data structure with addresses replaced with objects."""
60
+ """Deep-copy a structure, replacing HashAddr/ValueAddr with values.
61
+
62
+ Traverses the structure and replaces every address with the value
63
+ obtained via .get(), preserving container topology. Handles cycles by
64
+ memoizing objects during traversal.
65
+
66
+ Args:
67
+ obj: Any Python object or nested structure containing addresses.
68
+
69
+ Returns:
70
+ Any: A copy of obj with all addresses resolved into concrete values.
71
+ """
40
72
  return _get_impl(obj)
41
73
 
42
74
 
43
75
  def _get_impl(obj:Any, seen=None)->Any:
44
- """Return copy of data structure with addresses replaced with objects."""
76
+ """Implementation helper for get() with cycle protection and memoization.
77
+
78
+ Args:
79
+ obj: Object to resolve.
80
+ seen: A dict mapping visited object ids to their resolved copies,
81
+ used to preserve identities and break cycles.
82
+
83
+ Returns:
84
+ Any: The resolved object or a structure containing resolved values.
85
+ """
45
86
  if seen is None:
46
87
  seen = dict()
47
88
 
@@ -4,15 +4,32 @@ from .._020_ordinary_code_portals import ordinary
4
4
  from .data_portal_core_classes import DataPortal, StorableFn
5
5
 
6
6
  class storable(ordinary):
7
- """A decorator that converts a Python function into a StorableFn object.
7
+ """Decorator that converts a Python function into a StorableFn.
8
+
9
+ When applied, the function is wrapped into a StorableFn that can be
10
+ addressed and stored in a DataPortal.
8
11
  """
9
12
 
10
13
  def __init__(self
11
14
  , portal: DataPortal | None = None):
15
+ """Create a storable decorator bound to an optional DataPortal.
16
+
17
+ Args:
18
+ portal: The DataPortal to use as default when wrapping functions.
19
+ If None, the active portal at call time will be used.
20
+ """
12
21
  assert isinstance(portal, DataPortal) or portal is None
13
22
  ordinary.__init__(self=self, portal=portal)
14
23
 
15
24
  def __call__(self,fn:Callable)->StorableFn:
25
+ """Wrap the given function into a StorableFn.
26
+
27
+ Args:
28
+ fn: The ordinary Python function to wrap.
29
+
30
+ Returns:
31
+ StorableFn: A storable function bound to the configured portal.
32
+ """
16
33
  wrapper = StorableFn(fn
17
34
  , portal=self._portal)
18
35
  return wrapper
@@ -1,5 +1,20 @@
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
+ """Determine whether an exception should be logged by Pythagoras.
3
+
4
+ Args:
5
+ exc_type: The exception class (type of the raised exception). May be None.
6
+ exc_value: The exception instance.
7
+ trace_back: The traceback object associated with the exception.
8
+
9
+ Returns:
10
+ bool: True if the exception has not yet been marked as processed by
11
+ Pythagoras and should be logged; False otherwise.
12
+
13
+ Notes:
14
+ Pythagoras marks exceptions it already handled to avoid duplicate
15
+ logging. This function checks for such marks using either
16
+ Exception.add_note (Python 3.11+) or a fallback attribute.
17
+ """
3
18
  if exc_type is None:
4
19
  return False
5
20
 
@@ -16,7 +31,20 @@ def _exception_needs_to_be_processed(exc_type, exc_value, trace_back) -> bool:
16
31
 
17
32
 
18
33
  def _mark_exception_as_processed(exc_type, exc_value, trace_back) -> None:
19
- """Mark the exception as processed by Pythagoras"""
34
+ """Mark an exception as already processed by Pythagoras.
35
+
36
+ This mark prevents duplicate logging of the same exception by subsequent
37
+ handlers.
38
+
39
+ Args:
40
+ exc_type: The exception class (type of the raised exception). Unused.
41
+ exc_value: The exception instance to mark.
42
+ trace_back: The traceback object associated with the exception. Unused.
43
+
44
+ Side Effects:
45
+ - Mutates the exception by adding a note (preferred) or by setting
46
+ an attribute `__suppress_pythagoras_logging__ = True`.
47
+ """
20
48
  if hasattr(exc_value, "add_note"):
21
49
  exc_value.add_note(
22
50
  "__suppress_pythagoras_logging__")
@@ -9,46 +9,82 @@ from .notebook_checker import is_executed_in_notebook
9
9
  from .._010_basic_portals import BasicPortal
10
10
 
11
11
 
12
- def build_execution_environment_summary()-> Dict:
13
- """Capture core information about execution environment.
12
+ def build_execution_environment_summary() -> Dict:
13
+ """Build a snapshot of the current execution environment.
14
14
 
15
- The function is intended to be used to log environment information
16
- to help debug (distributed) applications.
15
+ Gathers system, process, and Python runtime metadata useful for diagnosing
16
+ issues, particularly in distributed or long-running applications.
17
+
18
+ Returns:
19
+ Dict: A dictionary containing environment details such as hostname,
20
+ user, process ID, OS/platform, Python version, CPU/memory stats,
21
+ working directory, local timezone, and whether execution is inside a
22
+ Jupyter notebook.
17
23
  """
18
24
  cwd = os.getcwd()
19
25
 
20
26
  execution_environment_summary = dict(
21
- hostname = socket.gethostname()
22
- ,user = getuser()
23
- ,pid = os.getpid()
24
- ,platform = platform.platform()
25
- ,python_implementation = platform.python_implementation()
26
- ,python_version = platform.python_version()
27
- ,processor = platform.processor()
28
- ,cpu_count = psutil.cpu_count()
29
- ,cpu_load_avg = psutil.getloadavg()
30
- # ,cuda_gpu_count=torch.cuda.device_count()
31
- ,disk_usage = psutil.disk_usage(cwd)
32
- ,virtual_memory = psutil.virtual_memory()
33
- ,working_directory = cwd
34
- ,local_timezone = datetime.now().astimezone().tzname()
35
- ,is_in_notebook = is_executed_in_notebook()
36
- )
27
+ hostname=socket.gethostname(),
28
+ user=getuser(),
29
+ pid=os.getpid(),
30
+ platform=platform.platform(),
31
+ python_implementation=platform.python_implementation(),
32
+ python_version=platform.python_version(),
33
+ processor=platform.processor(),
34
+ cpu_count=psutil.cpu_count(),
35
+ cpu_load_avg=psutil.getloadavg(),
36
+ # cuda_gpu_count=torch.cuda.device_count(),
37
+ disk_usage=psutil.disk_usage(cwd),
38
+ virtual_memory=psutil.virtual_memory(),
39
+ working_directory=cwd,
40
+ local_timezone=datetime.now().astimezone().tzname(),
41
+ is_in_notebook=is_executed_in_notebook(),
42
+ )
37
43
 
38
44
  return execution_environment_summary
39
45
 
40
- def make_unique_name(suggested_name:str, existing_names) -> str:
41
- """Make a name unique by adding a random suffix to it."""
46
+ def make_unique_name(suggested_name: str, existing_names) -> str:
47
+ """Return a unique name based on the suggestion and a set of existing names.
48
+
49
+ If the suggested name already exists, appends a random numeric
50
+ suffix until a unique candidate is found.
51
+
52
+ Args:
53
+ suggested_name: Preferred base name.
54
+ existing_names: A collection supporting membership check (e.g., dict,
55
+ set, list) used to determine collisions.
56
+
57
+ Returns:
58
+ str: A name guaranteed to not be present in existing_names at the
59
+ time of checking.
60
+ """
42
61
  candidate = suggested_name
43
62
  entropy_infuser = BasicPortal._entropy_infuser
44
63
  while candidate in existing_names:
45
64
  candidate = suggested_name + "_"
46
- random_number = entropy_infuser.randint(1,10_000_000_000)
65
+ random_number = entropy_infuser.randint(1, 10_000_000_000)
47
66
  candidate += str(random_number)
48
67
  return candidate
49
68
 
50
69
  def add_execution_environment_summary(*args, **kwargs):
51
- """Add execution environment summary to kwargs. """
70
+ """Augment keyword arguments with an execution environment summary.
71
+
72
+ Optionally also adds positional messages under a unique `message_list` key
73
+ when args are provided. This is primarily used to enrich logged events with
74
+ contextual runtime information.
75
+
76
+ Args:
77
+ *args: Optional messages or payloads to attach under `message_list`.
78
+ **kwargs: The keyword arguments dictionary to be augmented. Mutated in
79
+ place by adding a unique key for the environment summary.
80
+
81
+ Returns:
82
+ dict: The same kwargs dict, with additional keys:
83
+ - execution_environment_summary*: A unique key containing the env
84
+ summary built by build_execution_environment_summary().
85
+ - message_list*: A unique key containing the provided args (if any).
86
+ Asterisks denote keys may be suffixed to ensure uniqueness.
87
+ """
52
88
  context_param_name = "execution_environment_summary"
53
89
  context_param_name = make_unique_name(
54
90
  suggested_name=context_param_name, existing_names=kwargs)
@@ -6,24 +6,37 @@ from .._030_data_portals import DataPortal, ValueAddr
6
6
  from parameterizable import sort_dict_by_keys
7
7
 
8
8
  class KwArgs(dict):
9
- """ A class that encapsulates keyword arguments for a function call.
9
+ """Container for keyword arguments with deterministic ordering and packing.
10
10
 
11
- It allows "normalizing" the dictionary by sorting the keys
12
- and replacing values with their hash addresses
13
- in order to always get the same hash values
14
- for the same lists of arguments.
11
+ Provides utilities to sort keys deterministically and to pack/unpack values
12
+ to and from ValueAddr instances so that argument sets can be compared and
13
+ hashed reliably across runs.
15
14
  """
16
15
 
17
16
 
18
17
  def __init__(self, *args, **kargs):
18
+ """Create a KwArgs mapping with deterministically sorted keys.
19
+
20
+ Args:
21
+ *args: Positional arguments accepted by dict().
22
+ **kargs: Keyword arguments accepted by dict().
23
+ """
19
24
  dict.__init__(self)
20
25
  tmp_dict = dict(*args, **kargs)
21
26
  tmp_dict = sort_dict_by_keys(tmp_dict)
22
27
  self.update(tmp_dict)
23
28
 
24
29
 
25
- def sort(self, inplace:bool) -> KwArgs:
26
- """Sort the keys in the dictionary."""
30
+ def sort(self, inplace: bool) -> KwArgs:
31
+ """Return a version with keys sorted, optionally in place.
32
+
33
+ Args:
34
+ inplace: If True, sorts this instance and returns it. If False,
35
+ returns a new KwArgs instance with sorted keys.
36
+
37
+ Returns:
38
+ KwArgs: The sorted KwArgs (self when inplace=True, otherwise a new instance).
39
+ """
27
40
  if inplace:
28
41
  sorted_dict = sort_dict_by_keys(self)
29
42
  self.clear()
@@ -34,7 +47,18 @@ class KwArgs(dict):
34
47
 
35
48
 
36
49
  def __setitem__(self, key, value):
37
- """Overridden to ensure only string keys are allowed."""
50
+ """Set an item enforcing KwArgs invariants.
51
+
52
+ Enforces that keys are strings and values are not KwArgs themselves.
53
+
54
+ Args:
55
+ key: The key to set; must be a str.
56
+ value: The value to associate with the key.
57
+
58
+ Raises:
59
+ KeyError: If the key is not a string.
60
+ ValueError: If the value is a KwArgs (nested KwArgs are disallowed).
61
+ """
38
62
  if not isinstance(key, str):
39
63
  raise KeyError("Keys must be strings in KwArgs.")
40
64
  if isinstance(value, KwArgs):
@@ -43,15 +67,24 @@ class KwArgs(dict):
43
67
 
44
68
 
45
69
  def __reduce__(self):
46
- """ Sort the keys before pickling."""
70
+ """Support pickling by sorting keys first for stable serialization.
71
+
72
+ Returns:
73
+ tuple: Standard pickle reduce tuple from dict.__reduce__().
74
+ """
47
75
  self.sort(inplace=True)
48
76
  return super().__reduce__()
49
77
 
50
78
 
51
79
  def unpack(self) -> UnpackedKwArgs:
52
- """ Restore values based on their hash addresses."""
80
+ """Return a copy with all ValueAddr values resolved to raw values.
81
+
82
+ Returns:
83
+ UnpackedKwArgs: A new mapping where each ValueAddr is replaced with
84
+ its underlying value via ValueAddr.get().
85
+ """
53
86
  unpacked_copy = dict()
54
- for k,v in self.items():
87
+ for k, v in self.items():
55
88
  if isinstance(v, ValueAddr):
56
89
  unpacked_copy[k] = v.get()
57
90
  else:
@@ -61,7 +94,20 @@ class KwArgs(dict):
61
94
 
62
95
 
63
96
  def pack(self, store = True) -> PackedKwArgs:
64
- """ Replace values with their hash addresses."""
97
+ """Pack values into ValueAddr handles, optionally storing them.
98
+
99
+ Each argument value is replaced by a ValueAddr pointing to either the
100
+ stored value (when store=True) or a non-stored address (when store=False)
101
+ that can still serve as a stable identifier for hashing/equality.
102
+
103
+ Args:
104
+ store: If True, values are stored in the active portal and the
105
+ returned addresses persist across sessions. If False, produces
106
+ non-stored addresses suitable for transient signatures.
107
+
108
+ Returns:
109
+ PackedKwArgs: A new mapping with values converted to ValueAddr.
110
+ """
65
111
  packed_copy = dict()
66
112
  if store:
67
113
  portal = get_active_portal()
@@ -77,10 +123,26 @@ class KwArgs(dict):
77
123
 
78
124
 
79
125
  class PackedKwArgs(KwArgs):
126
+ """KwArgs where all values are ValueAddr instances.
127
+
128
+ This is the packed form produced by KwArgs.pack().
129
+ """
80
130
  def __init__(self,*args, **kargs):
131
+ """Construct a PackedKwArgs mapping.
132
+
133
+ Accepts the same arguments as dict/KwArgs.
134
+ """
81
135
  super().__init__(*args, **kargs)
82
136
 
83
137
 
84
138
  class UnpackedKwArgs(KwArgs):
139
+ """KwArgs where all values are raw (non-ValueAddr) objects.
140
+
141
+ This is the unpacked form produced by KwArgs.unpack().
142
+ """
85
143
  def __init__(self,*args, **kargs):
144
+ """Construct an UnpackedKwArgs mapping.
145
+
146
+ Accepts the same arguments as dict/KwArgs.
147
+ """
86
148
  super().__init__(*args, **kargs)
@@ -6,19 +6,41 @@ from .._030_data_portals import storable
6
6
  from .logging_portal_core_classes import LoggingCodePortal, LoggingFn
7
7
 
8
8
  class logging(storable):
9
- """A decorator that converts a Python function into a LoggingFn object.
9
+ """Decorator that converts a Python function into a LoggingFn.
10
+
11
+ When applied, the target function is wrapped into a LoggingFn that records
12
+ execution attempts, results, outputs, crashes, and events via a
13
+ LoggingCodePortal.
10
14
  """
11
15
  _excessive_logging: bool|Joker
12
16
 
13
17
  def __init__(self
14
18
  , excessive_logging:bool|Joker = KEEP_CURRENT
15
19
  , portal: LoggingCodePortal | None = None):
20
+ """Initialize the logging decorator.
21
+
22
+ Args:
23
+ excessive_logging: If True, the wrapped function will capture
24
+ detailed per-execution artifacts (attempt context, outputs,
25
+ and results). If KEEP_CURRENT, inherits from the wrapped
26
+ LoggingFn or the active portal.
27
+ portal: Optional LoggingCodePortal to bind the wrapped function to.
28
+ If None, the active portal at execution time is used.
29
+ """
16
30
  assert isinstance(excessive_logging, (bool,Joker))
17
31
  assert isinstance(portal, LoggingCodePortal) or portal is None
18
32
  storable.__init__(self=self, portal=portal)
19
33
  self._excessive_logging = excessive_logging
20
34
 
21
35
  def __call__(self,fn:Callable)->LoggingFn:
36
+ """Wrap the function into a LoggingFn.
37
+
38
+ Args:
39
+ fn: A plain Python callable to decorate.
40
+
41
+ Returns:
42
+ LoggingFn: The logging-enabled wrapper for the given function.
43
+ """
22
44
  wrapper = LoggingFn(fn
23
45
  , excessive_logging=self._excessive_logging
24
46
  , portal=self._portal)