pythagoras 0.24.6__py3-none-any.whl → 0.24.7__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.
@@ -51,6 +51,12 @@ CACHED_EXECUTION_RESULTS_TXT = "Cached execution results"
51
51
  EXECUTION_QUEUE_SIZE_TXT = "Execution queue size"
52
52
 
53
53
  class PureCodePortal(ProtectedCodePortal):
54
+ """Portal that manages execution and caching for pure functions.
55
+
56
+ The portal extends ProtectedCodePortal with two persistent dictionaries:
57
+ - execution_results: append-only, stores ValueAddr of function outputs
58
+ - execution_requests: mutable, tracks pending execution requests
59
+ """
54
60
 
55
61
  _execution_results: PersiDict | None
56
62
  _execution_requests: PersiDict | None
@@ -60,6 +66,14 @@ class PureCodePortal(ProtectedCodePortal):
60
66
  , p_consistency_checks: float | Joker = KEEP_CURRENT
61
67
  , excessive_logging: bool | Joker = KEEP_CURRENT
62
68
  ):
69
+ """Initialize a PureCodePortal instance.
70
+
71
+ Args:
72
+ root_dict: Backing persistent dictionary or path used to create it.
73
+ p_consistency_checks: Probability [0..1] to re-check cached
74
+ results for consistency. KEEP_CURRENT to inherit.
75
+ excessive_logging: Verbosity flag; KEEP_CURRENT to inherit.
76
+ """
63
77
  ProtectedCodePortal.__init__(self
64
78
  , root_dict=root_dict
65
79
  , p_consistency_checks=p_consistency_checks
@@ -89,7 +103,13 @@ class PureCodePortal(ProtectedCodePortal):
89
103
 
90
104
 
91
105
  def describe(self) -> pd.DataFrame:
92
- """Get a DataFrame describing the portal's current state"""
106
+ """Describe the portal state as a DataFrame.
107
+
108
+ Returns:
109
+ pandas.DataFrame: Concatenated report that includes base portal
110
+ parameters plus counts of cached execution results and queued
111
+ execution requests.
112
+ """
93
113
  all_params = [super().describe()]
94
114
 
95
115
  all_params.append(_describe_persistent_characteristic(
@@ -102,6 +122,7 @@ class PureCodePortal(ProtectedCodePortal):
102
122
  return result
103
123
 
104
124
  def _clear(self):
125
+ """Release references to backing dicts and clear base portal state."""
105
126
  self._execution_results = None
106
127
  self._execution_requests = None
107
128
  super()._clear()
@@ -113,6 +134,12 @@ class PureFnCallSignature(ProtectedFnCallSignature):
113
134
  _execution_results_addr_cache: PureFnExecutionResultAddr | None
114
135
 
115
136
  def __init__(self, fn: PureFn, arguments: dict):
137
+ """Create a signature object for a specific PureFn call.
138
+
139
+ Args:
140
+ fn: The pure function being called.
141
+ arguments: Keyword arguments for the call.
142
+ """
116
143
  assert isinstance(fn, PureFn)
117
144
  assert isinstance(arguments, dict)
118
145
  super().__init__(fn, arguments)
@@ -132,6 +159,12 @@ class PureFnCallSignature(ProtectedFnCallSignature):
132
159
 
133
160
 
134
161
  class PureFn(ProtectedFn):
162
+ """Wrapper around a callable that provides pure-function semantics.
163
+
164
+ A PureFn executes inside a PureCodePortal, caches results by call
165
+ signature, and exposes convenience APIs to request execution, run
166
+ immediately, and retrieve results via address objects.
167
+ """
135
168
 
136
169
  def __init__(self, fn: Callable | str
137
170
  , pre_validators: list[AutonomousFn] | List[Callable] | None = None
@@ -139,6 +172,17 @@ class PureFn(ProtectedFn):
139
172
  , excessive_logging: bool | Joker = KEEP_CURRENT
140
173
  , fixed_kwargs: dict | None = None
141
174
  , portal: PureCodePortal | None = None):
175
+ """Construct a PureFn.
176
+
177
+ Args:
178
+ fn: Target callable.
179
+ pre_validators: Optional list of pre-execution validators.
180
+ post_validators: Optional list of post-execution validators.
181
+ excessive_logging: Verbosity flag; KEEP_CURRENT to inherit.
182
+ fixed_kwargs: Mapping of argument names to fixed values injected
183
+ into each call.
184
+ portal: Optional PureCodePortal to bind this PureFn to.
185
+ """
142
186
  ProtectedFn.__init__(self
143
187
  , fn=fn
144
188
  , portal = portal
@@ -149,21 +193,45 @@ class PureFn(ProtectedFn):
149
193
 
150
194
 
151
195
  def get_address(self, **kwargs) -> PureFnExecutionResultAddr:
152
- """Get the address of the result of the function with the given arguments."""
196
+ """Build an address object for a call with the given arguments.
197
+
198
+ Args:
199
+ **kwargs: Keyword arguments to pass to the function.
200
+
201
+ Returns:
202
+ PureFnExecutionResultAddr: Address referencing the (cached or
203
+ future) result corresponding to the provided arguments.
204
+ """
153
205
  with self.portal:
154
206
  packed_kwargs = KwArgs(**kwargs).pack()
155
207
  return PureFnExecutionResultAddr(self, packed_kwargs)
156
208
 
157
209
 
158
210
  def get_signature(self, arguments: dict) -> PureFnCallSignature:
211
+ """Build a call signature for the given arguments.
212
+
213
+ Args:
214
+ arguments: Keyword arguments for a potential call.
215
+
216
+ Returns:
217
+ PureFnCallSignature: The signature object identifying the call.
218
+ """
159
219
  return PureFnCallSignature(self, arguments)
160
220
 
161
221
 
162
222
  def swarm(self, **kwargs) -> PureFnExecutionResultAddr:
163
- """ Request execution of the function with the given arguments.
223
+ """Request background function execution for the given arguments.
224
+
225
+ The function is not executed immediately; instead, an execution request
226
+ is recorded in the portal. The returned address can later be used to
227
+ check readiness or retrieve the value.
164
228
 
165
- The function is executed in the background. The result can be
166
- retrieved later using the returned address.
229
+ Args:
230
+ **kwargs: Keyword arguments to pass to the underlying function.
231
+
232
+ Returns:
233
+ PureFnExecutionResultAddr: Address that identifies the pending (or
234
+ already cached) execution result for these arguments.
167
235
  """
168
236
  with self.portal:
169
237
  result_address = self.get_address(**kwargs)
@@ -171,10 +239,16 @@ class PureFn(ProtectedFn):
171
239
  return result_address
172
240
 
173
241
  def run(self, **kwargs) -> PureFnExecutionResultAddr:
174
- """ Execute the function with the given arguments.
242
+ """Execute immediately and return the result address.
243
+
244
+ The function is executed synchronously within the current process.
245
+
246
+ Args:
247
+ **kwargs: Keyword arguments to pass to the underlying function.
175
248
 
176
- The function is executed immediately. The result can be
177
- retrieved later using the returned address.
249
+ Returns:
250
+ PureFnExecutionResultAddr: Address of the computed value (which can
251
+ be used to fetch logs/metadata or the value again if needed).
178
252
  """
179
253
  with self.portal:
180
254
  result_address = self.get_address(**kwargs)
@@ -183,12 +257,18 @@ class PureFn(ProtectedFn):
183
257
 
184
258
 
185
259
  def execute(self, **kwargs) -> Any:
186
- """ Execute the function with the given arguments.
260
+ """Execute the function with the given arguments and return the result.
261
+
262
+ The function is executed immediately and its result is memoized by the
263
+ portal. Subsequent calls with identical arguments return the cached
264
+ value (optionally performing consistency checks depending on the
265
+ portal's p_consistency_checks setting).
266
+
267
+ Args:
268
+ **kwargs: Keyword arguments to pass to the underlying function.
187
269
 
188
- The function is executed immediately, and the result is returned.
189
- The result is memoized, so the function is actually executed
190
- only the first time it's called; subsequent calls return the
191
- cached result.
270
+ Returns:
271
+ Any: The computed result value.
192
272
  """
193
273
 
194
274
  with self.portal as portal:
@@ -224,6 +304,16 @@ class PureFn(ProtectedFn):
224
304
  self
225
305
  , list_of_kwargs:list[dict]
226
306
  ) -> list[PureFnExecutionResultAddr]:
307
+ """Queue background execution for a batch of argument sets.
308
+
309
+ Args:
310
+ list_of_kwargs: A list of keyword-argument mappings. Each mapping
311
+ represents one call to the function.
312
+
313
+ Returns:
314
+ list[PureFnExecutionResultAddr]: Addresses for all requested
315
+ executions, in the same order as the input list.
316
+ """
227
317
  assert isinstance(list_of_kwargs, (list, tuple))
228
318
  for kwargs in list_of_kwargs:
229
319
  assert isinstance(kwargs, dict)
@@ -244,6 +334,17 @@ class PureFn(ProtectedFn):
244
334
  self
245
335
  , list_of_kwargs:list[dict]
246
336
  ) -> list[PureFnExecutionResultAddr]:
337
+ """Execute a batch of calls immediately and return their addresses.
338
+
339
+ Execution is performed in a shuffled order.
340
+
341
+ Args:
342
+ list_of_kwargs: A list of keyword-argument mappings for each call.
343
+
344
+ Returns:
345
+ list[PureFnExecutionResultAddr]: Addresses for the executed calls,
346
+ in the same order as the input list.
347
+ """
247
348
  with self.portal:
248
349
  addrs = self.swarm_list(list_of_kwargs)
249
350
  addrs_workspace = copy(addrs)
@@ -275,6 +376,13 @@ class PureFn(ProtectedFn):
275
376
 
276
377
  @property
277
378
  def portal(self) -> PureCodePortal:
379
+ """Active PureCodePortal used for this function execution.
380
+
381
+ Returns:
382
+ PureCodePortal: The portal that governs execution and persistence
383
+ for this PureFn (falls back to the current active portal if this
384
+ function isn't explicitly bound).
385
+ """
278
386
  return super().portal
279
387
 
280
388
 
@@ -297,6 +405,12 @@ class PureFnExecutionResultAddr(HashAddr):
297
405
  _ready_cache: bool | None
298
406
 
299
407
  def __init__(self, fn: PureFn, arguments:dict[str, Any]):
408
+ """Create an address for a pure-function execution result.
409
+
410
+ Args:
411
+ fn: The PureFn whose execution result is addressed.
412
+ arguments: The keyword arguments for the call (packed dict).
413
+ """
300
414
  assert isinstance(fn, PureFn)
301
415
  with fn.portal as portal:
302
416
  self._kwargs_cache = KwArgs(**arguments)
@@ -339,6 +453,7 @@ class PureFnExecutionResultAddr(HashAddr):
339
453
 
340
454
  @property
341
455
  def call_signature(self) -> PureFnCallSignature:
456
+ """The PureFnCallSignature for this address' call."""
342
457
  if (not hasattr(self, "_call_signature_cache")
343
458
  or self._call_signature_cache is None):
344
459
  self._call_signature_cache = self.get_ValueAddr().get()
@@ -384,6 +499,15 @@ class PureFnExecutionResultAddr(HashAddr):
384
499
 
385
500
  @property
386
501
  def _ready_in_nonactive_portals(self) -> bool:
502
+ """Try importing a ready result from non-active portals.
503
+
504
+ If another known portal already has the execution result, copy the
505
+ corresponding key/value into the active portal's stores.
506
+
507
+ Returns:
508
+ bool: True if the value was found in a non-active portal and
509
+ imported; False otherwise.
510
+ """
387
511
  for another_portal in get_nonactive_portals():
388
512
  with another_portal:
389
513
  if self in another_portal._execution_results:
@@ -399,6 +523,12 @@ class PureFnExecutionResultAddr(HashAddr):
399
523
 
400
524
  @property
401
525
  def ready(self) -> bool:
526
+ """Whether the execution result is already available.
527
+
528
+ Returns:
529
+ bool: True if the result is present in the active portal (or can
530
+ be imported from a known non-active portal); False otherwise.
531
+ """
402
532
  if hasattr(self, "_ready_cache"):
403
533
  assert self._ready_cache
404
534
  return True
@@ -413,7 +543,11 @@ class PureFnExecutionResultAddr(HashAddr):
413
543
 
414
544
 
415
545
  def execute(self):
416
- """Execute the function and store the result in the portal."""
546
+ """Execute the function and store the result in the portal.
547
+
548
+ Returns:
549
+ Any: The computed result of the underlying pure function call.
550
+ """
417
551
  if hasattr(self, "_result_cache"):
418
552
  return self._result_cache
419
553
  with self.fn.portal:
@@ -439,7 +573,13 @@ class PureFnExecutionResultAddr(HashAddr):
439
573
 
440
574
  @property
441
575
  def execution_requested(self):
442
- """Indicates if the function execution has been requested."""
576
+ """Whether execution for this call has been requested.
577
+
578
+ Returns:
579
+ bool: True if there's a pending execution request in the active
580
+ portal or any known non-active portal (also synchronizes the
581
+ request into the active portal); False otherwise.
582
+ """
443
583
  with self.fn.portal as active_portal:
444
584
  if self in active_portal._execution_requests:
445
585
  return True
@@ -451,15 +591,22 @@ class PureFnExecutionResultAddr(HashAddr):
451
591
 
452
592
 
453
593
  def get(self, timeout: int = None):
454
- """Retrieve value, referenced by the address.
594
+ """Retrieve the value referenced by this address, waiting if needed.
595
+
596
+ The method does not execute the function itself. If the value is not
597
+ immediately available, it requests execution and waits with
598
+ exponential backoff until the result arrives or the timeout elapses.
455
599
 
456
- If the value is not immediately available, backoff exponentially
457
- till timeout is exceeded. If timeout is None, keep trying forever.
600
+ Args:
601
+ timeout: Optional maximum number of seconds to wait. If None,
602
+ tries/waits indefinitely.
458
603
 
459
- This method does not actually execute the function, but simply
460
- retrieves the result of the function execution, if it is available.
461
- If it is not available, the method waits for the result to become
462
- available, or until the timeout is exceeded.
604
+ Returns:
605
+ Any: The value produced by the pure function call.
606
+
607
+ Raises:
608
+ TimeoutError: If the timeout elapses before the value becomes
609
+ available.
463
610
  """
464
611
  assert timeout is None or timeout >= 0
465
612
  if hasattr(self, "_result_cache"):
@@ -502,10 +649,16 @@ class PureFnExecutionResultAddr(HashAddr):
502
649
 
503
650
  @property
504
651
  def can_be_executed(self) -> PureFnCallSignature | ValidationSuccessFlag | None:
505
- """Indicates if the function can be executed in the current session.
652
+ """Whether execution is currently feasible.
653
+
654
+ This checks pre-validators/guards for the underlying pure function and
655
+ returns a directive for the protected execution pipeline.
506
656
 
507
- TODO: The function should be refactored once we start fully supporting
508
- guards
657
+ Returns:
658
+ PureFnCallSignature | ValidationSuccessFlag | None: If a dependent
659
+ call must be executed first, returns its call signature; if checks
660
+ pass immediately, returns VALIDATION_SUCCESSFUL; otherwise None to
661
+ indicate that the execution is not possible.
509
662
  """
510
663
  with self.fn.portal:
511
664
  return self.fn.can_be_executed(self.kwargs)
@@ -29,6 +29,18 @@ from .._080_pure_code_portals.pure_core_classes import (
29
29
  from persidict import KEEP_CURRENT, Joker
30
30
 
31
31
  class pure(protected):
32
+ """Decorator that marks a function as pure and enables result caching.
33
+
34
+ Pure functions are assumed to be deterministic and free of side effects:
35
+ given the same arguments they always produce the same result. When you
36
+ decorate a function with @pure(), Pythagoras caches execution results in
37
+ a persistent store, avoids re-executing the function for identical calls,
38
+ as well as tracks source-code changes to recompute the function
39
+ when necessary.
40
+
41
+ This decorator is a thin, typed wrapper over the generic `protected`
42
+ decorator configured for pure-function semantics.
43
+ """
32
44
 
33
45
  def __init__(self
34
46
  , pre_validators: list[ValidatorFn] | None = None
@@ -37,6 +49,22 @@ class pure(protected):
37
49
  , excessive_logging: bool | Joker = KEEP_CURRENT
38
50
  , portal: PureCodePortal | None = None
39
51
  ):
52
+ """Initialize the pure decorator.
53
+
54
+ Args:
55
+ pre_validators: Optional list of validator functions that run
56
+ before execution.
57
+ post_validators: Optional list of validator functions that run
58
+ after execution to validate results or side conditions.
59
+ fixed_kwargs: Mapping of argument names to values that will be
60
+ always injected into the decorated function calls.
61
+ excessive_logging: Controls verbosity inside the portal; keep
62
+ KEEP_CURRENT to inherit from the active portal, True/False to
63
+ override.
64
+ portal: Optional PureCodePortal to bind the decorated function to.
65
+ If omitted, the portal(s) to use will be inferred
66
+ during the function call(s).
67
+ """
40
68
  protected.__init__(self=self
41
69
  , portal=portal
42
70
  , excessive_logging=excessive_logging
@@ -46,6 +74,15 @@ class pure(protected):
46
74
 
47
75
 
48
76
  def __call__(self, fn:Callable|str) -> PureFn:
77
+ """Wrap a function as a PureFn within a PureCodePortal.
78
+
79
+ Args:
80
+ fn: The target callable or its import path string.
81
+
82
+ Returns:
83
+ PureFn: A wrapped function instance that supports memoized
84
+ execution via run/swarm/execute and address-based retrieval.
85
+ """
49
86
  wrapper = PureFn(fn
50
87
  , portal=self._portal
51
88
  , pre_validators=self._pre_validators
@@ -1,3 +1,10 @@
1
+ """Pre-validators that help with recursive pure functions.
2
+
3
+ This module provides utilities to short-circuit or schedule execution for
4
+ recursive calls, e.g., Fibonacci-like functions. The pre-validator returns
5
+ either a success flag (skip execution), a call signature to be executed first,
6
+ or None to proceed as usual.
7
+ """
1
8
  from unittest import result
2
9
 
3
10
  import pythagoras as pth
@@ -14,6 +21,26 @@ def _recursion_pre_validator(
14
21
  , fn_addr:ValueAddr
15
22
  , param_name:str
16
23
  )-> PureFnCallSignature | ValidationSuccessFlag | None:
24
+ """Pre-validator to aid recursion by ensuring prerequisites are ready.
25
+
26
+ For a non-negative integer parameter specified by param_name, this
27
+ validator checks whether smaller sub-problems have already been computed.
28
+ It returns:
29
+ - VALIDATION_SUCCESSFUL if a base case is detected or all smaller results
30
+ are already available; or
31
+ - a PureFnCallSignature for the smallest missing sub-problem to compute
32
+ first; or
33
+ - None to proceed normally.
34
+
35
+ Args:
36
+ packed_kwargs: Packed arguments (KwArgs) of the current call.
37
+ fn_addr: ValueAddr pointing to the underlying PureFn.
38
+ param_name: Name of the recursive integer argument to inspect.
39
+
40
+ Returns:
41
+ PureFnCallSignature | ValidationSuccessFlag | None: Directive for the
42
+ protected execution pipeline.
43
+ """
17
44
  unpacked_kwargs = packed_kwargs.unpack()
18
45
  assert param_name in unpacked_kwargs
19
46
  fn = fn_addr.get()
@@ -49,6 +76,18 @@ def _recursion_pre_validator(
49
76
  def recursive_parameters(
50
77
  *args
51
78
  ) -> list[ComplexPreValidatorFn]:
79
+ """Build pre-validators for one or more recursive parameters.
80
+
81
+ Each provided name produces a pre-validator function that uses
82
+ _recursion_pre_validator to ensure prerequisite sub-problems are ready.
83
+
84
+ Args:
85
+ *args: One or more names of integer parameters that govern recursion.
86
+
87
+ Returns:
88
+ list[ComplexPreValidatorFn]: A list of pre-validators to plug into the
89
+ pure/protected decorator configuration.
90
+ """
52
91
  result = []
53
92
  for name in args:
54
93
  assert isinstance(name, str)
@@ -1,13 +1,32 @@
1
+ """Utilities for silencing worker process output.
2
+
3
+ This module provides a small context manager that redirects stdout and stderr
4
+ to the system null device (os.devnull). It is used by swarming workers to keep
5
+ background processing quiet and avoid polluting logs or test output.
6
+ """
7
+
1
8
  import os
2
9
  from contextlib import ExitStack, redirect_stderr, redirect_stdout
3
10
 
4
11
 
5
-
6
12
  class OutputSuppressor:
7
- """A context manager that suppresses stdout and stderr output."""
13
+ """Context manager to suppress stdout and stderr.
14
+
15
+ Example:
16
+ with OutputSuppressor():
17
+ noisy_function()
18
+
19
+ Notes:
20
+ Internally uses contextlib.ExitStack to manage all redirections and to
21
+ ensure restoration even when exceptions are raised.
22
+ """
8
23
 
9
24
  def __enter__(self):
10
- """Redirect stdout and stderr to os.devnull."""
25
+ """Enter the suppression context.
26
+
27
+ Returns:
28
+ OutputSuppressor: The context manager instance.
29
+ """
11
30
  self._stack = ExitStack()
12
31
  devnull = self._stack.enter_context(open(os.devnull, "w"))
13
32
  self._stack.enter_context(redirect_stdout(devnull))
@@ -15,4 +34,14 @@ class OutputSuppressor:
15
34
  return self
16
35
 
17
36
  def __exit__(self, exc_type, exc_val, exc_tb):
37
+ """Exit the suppression context, restoring stdio streams.
38
+
39
+ Args:
40
+ exc_type: Exception class if an exception occurred, else None.
41
+ exc_val: Exception instance if an exception occurred, else None.
42
+ exc_tb: Traceback if an exception occurred, else None.
43
+
44
+ Returns:
45
+ bool: Whatever is returned by ExitStack.__exit__().
46
+ """
18
47
  return self._stack.__exit__(exc_type, exc_val, exc_tb)