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
@@ -5,10 +5,10 @@ import traceback
5
5
  from typing import Callable, Any
6
6
 
7
7
  import pandas as pd
8
- # from parameterizable import register_parameterizable_class
8
+ from parameterizable import NotPicklableClass
9
9
  from persidict import PersiDict, KEEP_CURRENT, Joker
10
10
 
11
- from .._010_basic_portals import NotPicklable, get_active_portal
11
+ from .._010_basic_portals import get_active_portal
12
12
  from .._010_basic_portals.basic_portal_core_classes import (
13
13
  _describe_persistent_characteristic, _describe_runtime_characteristic)
14
14
 
@@ -32,6 +32,18 @@ from .._800_signatures_and_converters.random_signatures import (
32
32
 
33
33
 
34
34
  class LoggingFn(StorableFn):
35
+ """A function wrapper that logs executions, outputs, events, and crashes.
36
+
37
+ LoggingFn wraps a callable and, when executed within a LoggingCodePortal,
38
+ records execution attempts, results, captured stdout/stderr, raised
39
+ exceptions, and custom events. It also supports an excessive_logging mode
40
+ that enables storing rich per-call artifacts.
41
+
42
+ Attributes:
43
+ _auxiliary_config_params_at_init (dict): Internal configuration store
44
+ inherited from StorableFn. Includes the 'excessive_logging' flag
45
+ when provided.
46
+ """
35
47
 
36
48
  # _excessive_logging_at_init: bool | Joker
37
49
 
@@ -40,6 +52,19 @@ class LoggingFn(StorableFn):
40
52
  , excessive_logging: bool|Joker = KEEP_CURRENT
41
53
  , portal: LoggingCodePortal | None = None
42
54
  ):
55
+ """Initialize a LoggingFn wrapper.
56
+
57
+ Args:
58
+ fn: A callable to wrap or a string with a function's source code.
59
+ excessive_logging: If True, capture detailed per-execution
60
+ artifacts (attempt context, outputs, results). If KEEP_CURRENT,
61
+ will inherit the setting from another LoggingFn when cloning.
62
+ portal: Optional LoggingCodePortal to bind this function to. If
63
+ omitted, uses the active portal during execution.
64
+
65
+ Raises:
66
+ TypeError: If excessive_logging is not a bool or Joker.
67
+ """
43
68
  super().__init__(fn=fn, portal=portal)
44
69
 
45
70
  if not isinstance(excessive_logging, (bool, Joker)):
@@ -56,15 +81,43 @@ class LoggingFn(StorableFn):
56
81
 
57
82
  @property
58
83
  def excessive_logging(self) -> bool:
84
+ """Whether rich per-execution logging is enabled for this function.
85
+
86
+ Returns:
87
+ bool: True if excessive logging is enabled for this function (from
88
+ its own config or inherited via the portal); False otherwise.
89
+ """
59
90
  value = self._get_config_setting("excessive_logging", self.portal)
60
91
  return bool(value)
61
92
 
62
93
 
63
94
  def get_signature(self, arguments:dict) -> LoggingFnCallSignature:
95
+ """Create a call signature for this function and the given arguments.
96
+
97
+ Args:
98
+ arguments: A mapping of keyword arguments for the call. Values may
99
+ be raw or ValueAddr; they will be normalized and packed.
100
+
101
+ Returns:
102
+ LoggingFnCallSignature: A signature object uniquely identifying
103
+ the combination of function and arguments.
104
+ """
64
105
  return LoggingFnCallSignature(self, arguments)
65
106
 
66
107
 
67
108
  def execute(self,**kwargs):
109
+ """Execute the wrapped function and log artifacts via the portal.
110
+
111
+ Args:
112
+ **kwargs: Keyword arguments to pass to the wrapped function.
113
+
114
+ Returns:
115
+ Any: The result returned by the wrapped function.
116
+
117
+ Side Effects:
118
+ - Registers an execution attempt and, if enabled, captures
119
+ stdout/stderr and stores the result and output.
120
+ """
68
121
  with self.portal:
69
122
  packed_kwargs = KwArgs(**kwargs).pack()
70
123
  fn_call_signature = self.get_signature(packed_kwargs)
@@ -76,6 +129,12 @@ class LoggingFn(StorableFn):
76
129
 
77
130
  @property
78
131
  def portal(self) -> LoggingCodePortal:
132
+ """The LoggingCodePortal associated with this function.
133
+
134
+ Returns:
135
+ LoggingCodePortal: The portal used for storage and logging during
136
+ execution.
137
+ """
79
138
  return super().portal
80
139
 
81
140
 
@@ -109,6 +168,12 @@ class LoggingFnCallSignature:
109
168
 
110
169
  @property
111
170
  def portal(self):
171
+ """Portal associated with the underlying function.
172
+
173
+ Returns:
174
+ LoggingCodePortal: The portal used to store and retrieve logging
175
+ artifacts for this call signature.
176
+ """
112
177
  return self.fn.portal
113
178
 
114
179
 
@@ -146,6 +211,11 @@ class LoggingFnCallSignature:
146
211
 
147
212
  @property
148
213
  def fn(self) -> LoggingFn:
214
+ """Resolve and cache the wrapped LoggingFn instance.
215
+
216
+ Returns:
217
+ LoggingFn: The function associated with this call signature.
218
+ """
149
219
  if not hasattr(self, "_fn_cache") or self._fn_cache is None:
150
220
  self._fn_cache = self.fn_addr.get(expected_type=LoggingFn)
151
221
  return self._fn_cache
@@ -153,6 +223,11 @@ class LoggingFnCallSignature:
153
223
 
154
224
  @property
155
225
  def fn_name(self) -> str:
226
+ """Name of the wrapped function.
227
+
228
+ Returns:
229
+ str: The function's name, cached after first access.
230
+ """
156
231
  if not hasattr(self, "_fn_name_cache") or self._fn_name_cache is None:
157
232
  self._fn_name_cache = self.fn.name
158
233
  return self._fn_name_cache
@@ -160,16 +235,31 @@ class LoggingFnCallSignature:
160
235
 
161
236
  @property
162
237
  def fn_addr(self) -> ValueAddr:
238
+ """Address of the wrapped LoggingFn in the portal storage.
239
+
240
+ Returns:
241
+ ValueAddr: The persisted address pointing to the LoggingFn.
242
+ """
163
243
  return self._fn_addr
164
244
 
165
245
 
166
246
  @property
167
247
  def kwargs_addr(self) -> ValueAddr:
248
+ """Address of the packed keyword arguments in portal storage.
249
+
250
+ Returns:
251
+ ValueAddr: The persisted address pointing to the packed kwargs.
252
+ """
168
253
  return self._kwargs_addr
169
254
 
170
255
 
171
256
  @property
172
257
  def packed_kwargs(self) -> PackedKwArgs:
258
+ """Packed keyword arguments for this call signature.
259
+
260
+ Returns:
261
+ PackedKwArgs: The packed kwargs, fetched from storage if not cached.
262
+ """
173
263
  if (not hasattr(self,"_packed_kwargs_cache")
174
264
  or self._packed_kwargs_cache is None):
175
265
  with self.portal:
@@ -180,10 +270,21 @@ class LoggingFnCallSignature:
180
270
 
181
271
  @property
182
272
  def excessive_logging(self) -> bool:
273
+ """Whether excessive logging is enabled for the underlying function.
274
+
275
+ Returns:
276
+ bool: True if excessive logging is enabled, False otherwise.
277
+ """
183
278
  return self.fn.excessive_logging
184
279
 
185
280
 
186
281
  def __hash_signature_descriptor__(self) -> str:
282
+ """Descriptor string contributing to the address hash.
283
+
284
+ Returns:
285
+ str: A lowercase string combining the function name and this class
286
+ name, used as part of the address hashing scheme.
287
+ """
187
288
  descriptor = self.fn_name
188
289
  descriptor += "_" + self.__class__.__name__
189
290
  descriptor = descriptor.lower()
@@ -191,11 +292,21 @@ class LoggingFnCallSignature:
191
292
 
192
293
 
193
294
  def execute(self) -> Any:
295
+ """Execute the underlying function with stored arguments.
296
+
297
+ Returns:
298
+ Any: The function's return value.
299
+ """
194
300
  return self.fn.execute(**self.packed_kwargs.unpack())
195
301
 
196
302
 
197
303
  @property
198
304
  def addr(self) -> ValueAddr:
305
+ """Address uniquely identifying this call signature.
306
+
307
+ Returns:
308
+ ValueAddr: Address of the LoggingFnCallSignature persisted in portal.
309
+ """
199
310
  if hasattr(self, "_addr_cache") and self._addr_cache is not None:
200
311
  return self._addr_cache
201
312
  with self.portal:
@@ -205,6 +316,11 @@ class LoggingFnCallSignature:
205
316
 
206
317
  @property
207
318
  def execution_attempts(self) -> PersiDict:
319
+ """Timeline of execution attempts metadata for this call.
320
+
321
+ Returns:
322
+ PersiDict: Append-only JSON sub-dictionary keyed by timestamped IDs.
323
+ """
208
324
  with self.portal as portal:
209
325
  attempts_path = self.addr + ["attempts"]
210
326
  attempts = portal._run_history.json.get_subdict(attempts_path)
@@ -213,6 +329,11 @@ class LoggingFnCallSignature:
213
329
 
214
330
  @property
215
331
  def last_execution_attempt(self) -> Any:
332
+ """Most recent execution attempt metadata, if any.
333
+
334
+ Returns:
335
+ Any: Latest attempt metadata dict, or None if no attempts exist.
336
+ """
216
337
  with self.portal:
217
338
  attempts = self.execution_attempts
218
339
  timeline = attempts.newest_values(1)
@@ -225,6 +346,11 @@ class LoggingFnCallSignature:
225
346
 
226
347
  @property
227
348
  def execution_results(self) -> PersiDict:
349
+ """Timeline of execution results for this call.
350
+
351
+ Returns:
352
+ PersiDict: Append-only PKL sub-dictionary storing returned values.
353
+ """
228
354
  with self.portal as portal:
229
355
  results_path = self.addr + ["results"]
230
356
  results = portal._run_history.pkl.get_subdict(results_path)
@@ -233,6 +359,11 @@ class LoggingFnCallSignature:
233
359
 
234
360
  @property
235
361
  def last_execution_result(self) -> Any:
362
+ """Most recent execution result, if any.
363
+
364
+ Returns:
365
+ Any: Latest return value, or None if no results exist.
366
+ """
236
367
  with self.portal:
237
368
  results = self.execution_results
238
369
  timeline = results.newest_values(1)
@@ -245,6 +376,11 @@ class LoggingFnCallSignature:
245
376
 
246
377
  @property
247
378
  def execution_outputs(self) -> PersiDict:
379
+ """Timeline of captured stdout/stderr/logging output for this call.
380
+
381
+ Returns:
382
+ PersiDict: Append-only TXT sub-dictionary of captured output strings.
383
+ """
248
384
  with self.portal as portal:
249
385
  outputs_path = self.addr + ["outputs"]
250
386
  outputs = portal._run_history.txt.get_subdict(outputs_path)
@@ -253,6 +389,11 @@ class LoggingFnCallSignature:
253
389
 
254
390
  @property
255
391
  def last_execution_output(self) -> Any:
392
+ """Most recent captured combined output, if any.
393
+
394
+ Returns:
395
+ Any: Latest captured output string, or None if no outputs exist.
396
+ """
256
397
  with self.portal:
257
398
  outputs = self.execution_outputs
258
399
  timeline = outputs.newest_values(1)
@@ -265,6 +406,11 @@ class LoggingFnCallSignature:
265
406
 
266
407
  @property
267
408
  def crashes(self) -> PersiDict:
409
+ """Timeline of crashes (exceptions) observed during this call.
410
+
411
+ Returns:
412
+ PersiDict: Append-only JSON sub-dictionary of exception payloads.
413
+ """
268
414
  with self.portal as portal:
269
415
  crashes_path = self.addr + ["crashes"]
270
416
  crashes = portal._run_history.json.get_subdict(crashes_path)
@@ -273,6 +419,11 @@ class LoggingFnCallSignature:
273
419
 
274
420
  @property
275
421
  def last_crash(self) -> Any:
422
+ """Most recent crash payload, if any.
423
+
424
+ Returns:
425
+ Any: Latest exception payload dict, or None if no crashes exist.
426
+ """
276
427
  with self.portal as portal:
277
428
  crashes = self.crashes
278
429
  timeline = crashes.newest_values(1)
@@ -285,6 +436,11 @@ class LoggingFnCallSignature:
285
436
 
286
437
  @property
287
438
  def events(self) -> PersiDict:
439
+ """Timeline of user events emitted during this call.
440
+
441
+ Returns:
442
+ PersiDict: Append-only JSON sub-dictionary of event payloads.
443
+ """
288
444
  with self.portal as portal:
289
445
  events_path = self.addr + ["events"]
290
446
  events = portal._run_history.json.get_subdict(events_path)
@@ -293,6 +449,11 @@ class LoggingFnCallSignature:
293
449
 
294
450
  @property
295
451
  def last_event(self) -> Any:
452
+ """Most recent user event payload, if any.
453
+
454
+ Returns:
455
+ Any: Latest event payload dict, or None if no events exist.
456
+ """
296
457
  with self.portal:
297
458
  events = self.events
298
459
  timeline = events.newest_values(1)
@@ -305,6 +466,12 @@ class LoggingFnCallSignature:
305
466
 
306
467
  @property
307
468
  def execution_records(self) -> list[LoggingFnExecutionRecord]:
469
+ """All execution sessions derived from attempts for this call.
470
+
471
+ Returns:
472
+ list[LoggingFnExecutionRecord]: Records constructed from stored
473
+ attempt IDs for convenient access to artifacts per run session.
474
+ """
308
475
  with self.portal:
309
476
  result = []
310
477
  for k in self.execution_attempts:
@@ -313,15 +480,18 @@ class LoggingFnCallSignature:
313
480
  return result
314
481
 
315
482
 
316
- class LoggingFnExecutionRecord(NotPicklable):
317
- """ A record of one full function execution session.
483
+ class LoggingFnExecutionRecord(NotPicklableClass):
484
+ """Record of a single function execution session.
318
485
 
319
- It provides access to all information logged during the
320
- execution session, which includes information about the execution context
321
- (environment), function arguments, its output (everything that was
322
- printed to stdout/stderr during the execution attempt), any crashes
323
- (exceptions) and events fired, and an actual result of the execution
324
- created by a 'return' statement within the function code.
486
+ Provides convenient accessors to all artifacts logged for one particular
487
+ execution of a LoggingFn: environment context, captured output, crashes,
488
+ events, and the returned result value.
489
+
490
+ Attributes:
491
+ call_signature (LoggingFnCallSignature): The function-call identity
492
+ this record belongs to.
493
+ session_id (str): Unique identifier for the execution session ("run_*"),
494
+ shared by all artifacts emitted during this run.
325
495
  """
326
496
  call_signature: LoggingFnCallSignature
327
497
  session_id: str
@@ -329,17 +499,34 @@ class LoggingFnExecutionRecord(NotPicklable):
329
499
  self
330
500
  , call_signature: LoggingFnCallSignature
331
501
  , session_id: str):
502
+ """Construct an execution record.
503
+
504
+ Args:
505
+ call_signature: The call signature the record is associated with.
506
+ session_id: The unique ID of the execution session.
507
+ """
332
508
  self.call_signature = call_signature
333
509
  self.session_id = session_id
334
510
 
335
511
 
336
512
  @property
337
513
  def portal(self):
514
+ """LoggingCodePortal used to resolve underlying artifacts.
515
+
516
+ Returns:
517
+ LoggingCodePortal: The portal associated with the call signature.
518
+ """
338
519
  return self.call_signature.portal
339
520
 
340
521
 
341
522
  @property
342
523
  def output(self) -> str|None:
524
+ """Combined stdout/stderr/logging output captured for the session.
525
+
526
+ Returns:
527
+ str | None: The captured text output, or None when no output
528
+ was recorded for this session.
529
+ """
343
530
  with self.portal:
344
531
  execution_outputs = self.call_signature.execution_outputs
345
532
  for k in execution_outputs:
@@ -350,6 +537,12 @@ class LoggingFnExecutionRecord(NotPicklable):
350
537
 
351
538
  @property
352
539
  def attempt_context(self)-> dict|None:
540
+ """Environment/context snapshot captured at attempt start.
541
+
542
+ Returns:
543
+ dict | None: The environment summary dict for this session, or
544
+ None if not present (e.g., excessive logging disabled).
545
+ """
353
546
  with self.portal:
354
547
  execution_attempts = self.call_signature.execution_attempts
355
548
  for k in execution_attempts:
@@ -360,6 +553,11 @@ class LoggingFnExecutionRecord(NotPicklable):
360
553
 
361
554
  @property
362
555
  def crashes(self) -> list[dict]:
556
+ """All exceptions recorded during the session.
557
+
558
+ Returns:
559
+ list[dict]: A list of exception payload dicts in chronological order.
560
+ """
363
561
  result = []
364
562
  with self.portal:
365
563
  crashes = self.call_signature.crashes
@@ -371,6 +569,11 @@ class LoggingFnExecutionRecord(NotPicklable):
371
569
 
372
570
  @property
373
571
  def events(self) -> list[dict]:
572
+ """All events recorded during the session.
573
+
574
+ Returns:
575
+ list[dict]: A list of event payload dicts in chronological order.
576
+ """
374
577
  result = []
375
578
  with self.portal:
376
579
  events = self.call_signature.events
@@ -382,6 +585,14 @@ class LoggingFnExecutionRecord(NotPicklable):
382
585
 
383
586
  @property
384
587
  def result(self)->Any:
588
+ """Return value produced by the function in this session.
589
+
590
+ Returns:
591
+ Any: The object returned by the wrapped function for the run.
592
+
593
+ Raises:
594
+ ValueError: If there is no stored result for this session ID.
595
+ """
385
596
  with self.portal:
386
597
  execution_results = self.call_signature.execution_results
387
598
  for k in execution_results:
@@ -392,7 +603,17 @@ class LoggingFnExecutionRecord(NotPicklable):
392
603
  f"{self.call_signature.fn_name} execution results.")
393
604
 
394
605
 
395
- class LoggingFnExecutionFrame(NotPicklable):
606
+ class LoggingFnExecutionFrame(NotPicklableClass):
607
+ """Context manager managing a single function execution with logging.
608
+
609
+ When used as a context, optionally captures stdout/stderr/logging,
610
+ registers attempt metadata, stores results and output, and routes
611
+ exceptions/events to both per-call artifacts and portal-level histories.
612
+
613
+ Attributes:
614
+ call_stack (list[LoggingFnExecutionFrame]): Class-level stack of active
615
+ frames (last item is the current execution frame).
616
+ """
396
617
  call_stack: list[LoggingFnExecutionFrame] = []
397
618
 
398
619
  session_id: str
@@ -404,6 +625,12 @@ class LoggingFnExecutionFrame(NotPicklable):
404
625
  context_used: bool
405
626
 
406
627
  def __init__(self, fn_call_signature: LoggingFnCallSignature):
628
+ """Initialize the execution frame for a specific function call.
629
+
630
+ Args:
631
+ fn_call_signature: The call signature identifying the function and
632
+ its (packed) arguments.
633
+ """
407
634
  with fn_call_signature.portal:
408
635
  self.session_id = "run_"+get_random_signature()
409
636
  self.fn_call_signature = fn_call_signature
@@ -421,40 +648,84 @@ class LoggingFnExecutionFrame(NotPicklable):
421
648
 
422
649
  @property
423
650
  def portal(self) -> LoggingCodePortal:
651
+ """Portal associated with the current execution frame.
652
+
653
+ Returns:
654
+ LoggingCodePortal: The portal used for logging within this frame.
655
+ """
424
656
  return self.fn.portal
425
657
 
426
658
 
427
659
  @property
428
660
  def fn(self) -> LoggingFn:
661
+ """The LoggingFn being executed in this frame.
662
+
663
+ Returns:
664
+ LoggingFn: The wrapped function object.
665
+ """
429
666
  return self.fn_call_signature.fn
430
667
 
431
668
 
432
669
  @property
433
670
  def fn_name(self) -> str:
671
+ """Name of the function being executed.
672
+
673
+ Returns:
674
+ str: The wrapped function's name.
675
+ """
434
676
  return self.fn_call_signature.fn_name
435
677
 
436
678
 
437
679
  @property
438
680
  def excessive_logging(self) -> bool:
681
+ """Whether the frame should capture detailed artifacts.
682
+
683
+ Returns:
684
+ bool: True if excessive logging is enabled for the function.
685
+ """
439
686
  return self.fn.excessive_logging
440
687
 
441
688
 
442
689
  @property
443
690
  def fn_addr(self) -> ValueAddr:
691
+ """Address of the wrapped function in storage.
692
+
693
+ Returns:
694
+ ValueAddr: The persisted address of the LoggingFn.
695
+ """
444
696
  return self.fn_call_signature.fn_addr
445
697
 
446
698
 
447
699
  @property
448
700
  def packed_args(self) -> PackedKwArgs:
701
+ """Packed keyword arguments used for this execution.
702
+
703
+ Returns:
704
+ PackedKwArgs: The packed argument mapping.
705
+ """
449
706
  return self.fn_call_signature.packed_kwargs
450
707
 
451
708
 
452
709
  @property
453
710
  def kwargs_addr(self) -> ValueAddr:
711
+ """Address of the packed arguments in storage.
712
+
713
+ Returns:
714
+ ValueAddr: The persisted address of the packed kwargs.
715
+ """
454
716
  return self.fn_call_signature.kwargs_addr
455
717
 
456
718
 
457
719
  def __enter__(self):
720
+ """Enter the execution frame context.
721
+
722
+ Performs sanity checks, enters the portal context, optionally starts
723
+ capturing output, pushes the frame onto the call stack, and registers
724
+ an execution attempt if excessive logging is enabled.
725
+
726
+ Returns:
727
+ LoggingFnExecutionFrame: This frame instance for use as a context var.
728
+ """
458
729
  assert not self.context_used, (
459
730
  "An instance of PureFnExecutionFrame can be used only once.")
460
731
  self.context_used = True
@@ -471,6 +742,10 @@ class LoggingFnExecutionFrame(NotPicklable):
471
742
 
472
743
 
473
744
  def _register_execution_attempt(self):
745
+ """Record an execution attempt with environment and source snapshot.
746
+
747
+ No-op when excessive logging is disabled.
748
+ """
474
749
  if not self.excessive_logging:
475
750
  return
476
751
  execution_attempts = self.fn_call_signature.execution_attempts
@@ -481,6 +756,14 @@ class LoggingFnExecutionFrame(NotPicklable):
481
756
 
482
757
 
483
758
  def _register_execution_result(self, result: Any):
759
+ """Store the function's return value into the results timeline.
760
+
761
+ Args:
762
+ result: The value returned by the wrapped function.
763
+
764
+ Notes:
765
+ No-op when excessive logging is disabled.
766
+ """
484
767
  if not self.excessive_logging:
485
768
  return
486
769
  execution_results = self.fn_call_signature.execution_results
@@ -489,6 +772,17 @@ class LoggingFnExecutionFrame(NotPicklable):
489
772
 
490
773
 
491
774
  def __exit__(self, exc_type, exc_value, trace_back):
775
+ """Exit the execution frame context.
776
+
777
+ Ensures the current exception (if any) is logged, finalizes output
778
+ capture and stores it, pops the frame from the call stack, and exits
779
+ the underlying portal context.
780
+
781
+ Args:
782
+ exc_type: Exception class raised within the context, if any.
783
+ exc_value: Exception instance raised within the context, if any.
784
+ trace_back: Traceback object associated with the exception, if any.
785
+ """
492
786
  log_exception()
493
787
  if isinstance(self.output_capturer, OutputCapturer):
494
788
  self.output_capturer.__exit__(exc_type, exc_value, traceback)
@@ -536,6 +830,22 @@ class LoggingCodePortal(DataPortal):
536
830
  , p_consistency_checks: float|Joker = KEEP_CURRENT
537
831
  , excessive_logging: bool|Joker = KEEP_CURRENT
538
832
  ):
833
+ """Construct a LoggingCodePortal.
834
+
835
+ Args:
836
+ root_dict: PersiDict instance or filesystem path serving as the
837
+ storage root. When None, a default in-memory or configured
838
+ PersiDict is used by the base DataPortal.
839
+ p_consistency_checks: Probability [0..1] to run consistency checks
840
+ on storage operations; KEEP_CURRENT inherits existing setting.
841
+ excessive_logging: If True, functions executed via this portal will
842
+ store detailed artifacts (attempts/results/outputs). If
843
+ KEEP_CURRENT, the setting is inherited when cloning or
844
+ otherwise unspecified.
845
+
846
+ Raises:
847
+ TypeError: If excessive_logging is not a bool or Joker.
848
+ """
539
849
  super().__init__(root_dict=root_dict
540
850
  , p_consistency_checks=p_consistency_checks)
541
851
  del root_dict
@@ -577,17 +887,35 @@ class LoggingCodePortal(DataPortal):
577
887
 
578
888
 
579
889
  def __exit__(self, exc_type, exc_val, exc_tb):
890
+ """Exit the portal context, ensuring any active exception is logged.
891
+
892
+ Args:
893
+ exc_type: Exception class raised within the portal context, if any.
894
+ exc_val: Exception instance, if any.
895
+ exc_tb: Traceback object, if any.
896
+ """
580
897
  log_exception()
581
898
  super().__exit__(exc_type, exc_val, exc_tb)
582
899
 
583
900
 
584
901
  @property
585
902
  def excessive_logging(self) -> bool:
903
+ """Whether this portal captures detailed per-call artifacts.
904
+
905
+ Returns:
906
+ bool: True if excessive logging is enabled, False otherwise.
907
+ """
586
908
  return bool(self._get_config_setting("excessive_logging"))
587
909
 
588
910
 
589
911
  def describe(self) -> pd.DataFrame:
590
- """Get a DataFrame describing the portal's current state"""
912
+ """Summarize the portal's current persistent and runtime state.
913
+
914
+ Returns:
915
+ pandas.DataFrame: A table with key characteristics, including
916
+ total crashes logged, today's crashes, and whether excessive
917
+ logging is enabled, combined with the base DataPortal summary.
918
+ """
591
919
  all_params = [super().describe()]
592
920
  all_params.append(_describe_persistent_characteristic(
593
921
  EXCEPTIONS_TOTAL_TXT, len(self._crash_history)))
@@ -603,7 +931,12 @@ class LoggingCodePortal(DataPortal):
603
931
 
604
932
 
605
933
  def _clear(self) -> None:
606
- """Clear the portal's state"""
934
+ """Clear the portal's internal state and unregister handlers.
935
+
936
+ Side Effects:
937
+ - Drops references to crash/event/run histories.
938
+ - Unregisters global uncaught exception handlers.
939
+ """
607
940
  self._crash_history = None
608
941
  self._event_log = None
609
942
  self._run_history = None
@@ -611,10 +944,17 @@ class LoggingCodePortal(DataPortal):
611
944
  super()._clear()
612
945
 
613
946
 
614
- # register_parameterizable_class(LoggingCodePortal)
947
+ def log_exception() -> None:
948
+ """Log the currently handled exception to the active LoggingCodePortal.
615
949
 
950
+ Captures the exception from sys.exc_info(), enriches it with execution
951
+ environment context, and stores it in the portal-level crash history. If
952
+ called during a function execution with excessive logging enabled, also
953
+ stores the event under the function's per-call crash log.
616
954
 
617
- def log_exception() -> None:
955
+ Returns:
956
+ None
957
+ """
618
958
  exc_type, exc_value, trace_back = sys.exc_info()
619
959
  if not _exception_needs_to_be_processed(
620
960
  exc_type, exc_value, trace_back):
@@ -642,6 +982,19 @@ def log_exception() -> None:
642
982
 
643
983
 
644
984
  def log_event(*args, **kwargs):
985
+ """Record an application event to the active LoggingCodePortal.
986
+
987
+ Adds environment context and optional positional messages to the event
988
+ payload. When called during a function execution, also attaches the event
989
+ to that call's event log.
990
+
991
+ Args:
992
+ *args: Optional positional messages to include in the event payload.
993
+ **kwargs: Key-value pairs forming the body of the event.
994
+
995
+ Returns:
996
+ None
997
+ """
645
998
  if len(LoggingFnExecutionFrame.call_stack):
646
999
  frame = LoggingFnExecutionFrame.call_stack[-1]
647
1000
  event_id = frame.session_id + "_event_" + str(frame.event_counter)