pythagoras 0.24.4__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.
Files changed (27) hide show
  1. pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +31 -4
  2. pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +94 -14
  3. pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +133 -4
  4. pythagoras/_070_protected_code_portals/basic_pre_validators.py +130 -15
  5. pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +20 -18
  6. pythagoras/_070_protected_code_portals/list_flattener.py +45 -7
  7. pythagoras/_070_protected_code_portals/package_manager.py +99 -24
  8. pythagoras/_070_protected_code_portals/protected_decorators.py +59 -1
  9. pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +239 -4
  10. pythagoras/_070_protected_code_portals/system_utils.py +85 -12
  11. pythagoras/_070_protected_code_portals/validation_succesful_const.py +12 -7
  12. pythagoras/_080_pure_code_portals/pure_core_classes.py +178 -25
  13. pythagoras/_080_pure_code_portals/pure_decorator.py +37 -0
  14. pythagoras/_080_pure_code_portals/recursion_pre_validator.py +39 -0
  15. pythagoras/_090_swarming_portals/output_suppressor.py +32 -3
  16. pythagoras/_090_swarming_portals/swarming_portals.py +165 -19
  17. pythagoras/_100_top_level_API/__init__.py +11 -0
  18. pythagoras/_800_signatures_and_converters/__init__.py +17 -0
  19. pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +55 -20
  20. pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +20 -5
  21. pythagoras/_800_signatures_and_converters/hash_signatures.py +46 -10
  22. pythagoras/_800_signatures_and_converters/node_signature.py +27 -12
  23. pythagoras/_800_signatures_and_converters/random_signatures.py +14 -3
  24. pythagoras/core/__init__.py +54 -0
  25. {pythagoras-0.24.4.dist-info → pythagoras-0.24.7.dist-info}/METADATA +1 -1
  26. {pythagoras-0.24.4.dist-info → pythagoras-0.24.7.dist-info}/RECORD +27 -27
  27. {pythagoras-0.24.4.dist-info → pythagoras-0.24.7.dist-info}/WHEEL +0 -0
@@ -30,18 +30,54 @@ from .._060_autonomous_code_portals import *
30
30
 
31
31
 
32
32
  class ProtectedCodePortal(AutonomousCodePortal):
33
+ """Portal for protected code execution.
34
+
35
+ This portal specializes the AutonomousCodePortal to coordinate execution of
36
+ ProtectedFn instances. It carries configuration and storage
37
+ required by validators (e.g., retry throttling) and by protected function
38
+ orchestration.
39
+
40
+ Args:
41
+ root_dict (PersiDict | str | None): Optional persistent dictionary or a
42
+ path/identifier to initialize the portal's storage. If None, a
43
+ default in-memory storage may be used.
44
+ p_consistency_checks (float | Joker): Probability or flag controlling
45
+ internal consistency checks performed by the portal. Use
46
+ KEEP_CURRENT to inherit the current setting.
47
+ excessive_logging (bool | Joker): Enables verbose logging of portal and
48
+ function operations. Use KEEP_CURRENT to inherit the current
49
+ setting.
50
+ """
33
51
 
34
52
  def __init__(self
35
53
  , root_dict: PersiDict|str|None = None
36
54
  , p_consistency_checks: float|Joker = KEEP_CURRENT
37
55
  , excessive_logging: bool|Joker = KEEP_CURRENT
38
56
  ):
57
+ """Initialize the portal.
58
+
59
+ Args:
60
+ root_dict (PersiDict | str | None): Backing storage or its path.
61
+ If None, use default.
62
+ p_consistency_checks (float | Joker): Probability for internal
63
+ consistency checks (or KEEP_CURRENT to inherit).
64
+ excessive_logging (bool | Joker): Verbose logging flag
65
+ (KEEP_CURRENT to inherit).
66
+ """
39
67
  super().__init__(root_dict=root_dict
40
68
  , p_consistency_checks=p_consistency_checks
41
69
  , excessive_logging=excessive_logging)
42
70
 
43
71
 
44
72
  class ProtectedFn(AutonomousFn):
73
+ """Function wrapper that enforces pre/post validation around execution.
74
+
75
+ A ProtectedFn evaluates a sequence of pre-validators before executing the
76
+ underlying function and a sequence of post-validators after execution. If a
77
+ pre-validator returns a ProtectedFnCallSignature, that signature will be
78
+ executed first (allowing validators to perform prerequisite actions) before
79
+ re-attempting the validation/execution loop.
80
+ """
45
81
 
46
82
  _pre_validators_cache: list[ValidatorFn] | None
47
83
  _post_validators_cache: list[ValidatorFn] | None
@@ -57,6 +93,25 @@ class ProtectedFn(AutonomousFn):
57
93
  , excessive_logging: bool | Joker = KEEP_CURRENT
58
94
  , fixed_kwargs: dict[str,Any] | None = None
59
95
  , portal: ProtectedCodePortal | None = None):
96
+ """Construct a ProtectedFn.
97
+
98
+ Args:
99
+ fn (Callable | str): The underlying Python function or its source
100
+ code string.
101
+ pre_validators (list[ValidatorFn] | list[Callable] | ValidatorFn | Callable | None):
102
+ Pre-execution validators. Callables are wrapped into
103
+ PreValidatorFn. Lists can be nested and will
104
+ be flattened.
105
+ post_validators (list[ValidatorFn] | list[Callable] | ValidatorFn | Callable | None):
106
+ Post-execution validators. Callables are wrapped into
107
+ PostValidatorFn. Lists can be nested and will be flattened.
108
+ excessive_logging (bool | Joker): Enable verbose logging or inherit
109
+ current setting with KEEP_CURRENT.
110
+ fixed_kwargs (dict[str, Any] | None): Keyword arguments to be fixed
111
+ (bound) for every execution of the function.
112
+ portal (ProtectedCodePortal | None): Portal instance to bind the
113
+ function to.
114
+ """
60
115
  super().__init__(fn=fn
61
116
  , portal = portal
62
117
  , fixed_kwargs=fixed_kwargs
@@ -114,6 +169,11 @@ class ProtectedFn(AutonomousFn):
114
169
 
115
170
  @property
116
171
  def pre_validators(self) -> list[AutonomousFn]:
172
+ """List of pre-validator functions for this protected function.
173
+
174
+ Returns:
175
+ list[AutonomousFn]: A cached list of PreValidatorFn instances.
176
+ """
117
177
  if not hasattr(self, "_pre_validators_cache"):
118
178
  self._pre_validators_cache = [
119
179
  addr.get() for addr in self._pre_validators_addrs]
@@ -122,6 +182,11 @@ class ProtectedFn(AutonomousFn):
122
182
 
123
183
  @property
124
184
  def post_validators(self) -> list[AutonomousFn]:
185
+ """List of post-validator functions for this protected function.
186
+
187
+ Returns:
188
+ list[AutonomousFn]: A cached list of PostValidatorFn instances.
189
+ """
125
190
  if not hasattr(self, "_post_validators_cache"):
126
191
  self._post_validators_cache = [
127
192
  addr.get() for addr in self._post_validators_addrs]
@@ -131,6 +196,21 @@ class ProtectedFn(AutonomousFn):
131
196
  def can_be_executed(self
132
197
  , kw_args: KwArgs
133
198
  ) -> ProtectedFnCallSignature|ValidationSuccessFlag|None:
199
+ """Run pre-validators to determine if execution can proceed.
200
+
201
+ The portal will shuffle the order of pre-validators. If any validator
202
+ returns a ProtectedFnCallSignature, that signature should be executed by
203
+ the caller prior to executing the protected function (this method simply
204
+ returns it). If any validator fails, None is returned. If all succeed,
205
+ VALIDATION_SUCCESSFUL is returned.
206
+
207
+ Args:
208
+ kw_args (KwArgs): Arguments intended for the wrapped function.
209
+
210
+ Returns:
211
+ ProtectedFnCallSignature | ValidationSuccessFlag | None: Either a
212
+ signature to execute first, the success flag, or None on failure.
213
+ """
134
214
  with self.portal as portal:
135
215
  kw_args = kw_args.pack()
136
216
  pre_validators = copy(self.pre_validators)
@@ -150,6 +230,16 @@ class ProtectedFn(AutonomousFn):
150
230
  def validate_execution_result(self
151
231
  , kw_args: KwArgs
152
232
  , result: Any) -> ValidationSuccessFlag|None:
233
+ """Run post-validators to confirm the execution result is acceptable.
234
+
235
+ Args:
236
+ kw_args (KwArgs): Arguments that were passed to the protected function.
237
+ result (Any): The value returned by the protected function.
238
+
239
+ Returns:
240
+ ValidationSuccessFlag | None: VALIDATION_SUCCESSFUL if all
241
+ post-validators pass, otherwise None.
242
+ """
153
243
  with self.portal as portal:
154
244
  kw_args = kw_args.pack()
155
245
  post_validators = copy(self.post_validators)
@@ -162,6 +252,24 @@ class ProtectedFn(AutonomousFn):
162
252
 
163
253
 
164
254
  def execute(self, **kwargs) -> Any:
255
+ """Execute the protected function with validation.
256
+
257
+ This method performs the following loop:
258
+ - Runs pre-validators. If a pre-validator returns a
259
+ ProtectedFnCallSignature, that signature is executed and validation is
260
+ reattempted. If any pre-validator fails, an AssertionError is raised.
261
+ - Executes the wrapped function.
262
+ - Runs post-validators and asserts they all succeed.
263
+
264
+ Args:
265
+ **kwargs: Keyword arguments to pass to the wrapped function.
266
+
267
+ Returns:
268
+ Any: The result returned by the wrapped function.
269
+
270
+ Raises:
271
+ AssertionError: If pre- or post-validation fails.
272
+ """
165
273
  with (self.portal):
166
274
  kw_args = KwArgs(**kwargs)
167
275
  while True:
@@ -182,8 +290,21 @@ class ProtectedFn(AutonomousFn):
182
290
  ) -> list[ValidatorFn]:
183
291
  """Return list of validators in a normalized form.
184
292
 
185
- All the functions-validators are converted to AutonomousFn objects,
186
- and returned as a list, sorted by functions' hash signatures.
293
+ - Wraps plain callables/strings into appropriate ValidatorFn subclasses.
294
+ - Flattens nested lists.
295
+ - Removes duplicates while inforcing deterministic
296
+ order via sort_dict_by_keys.
297
+
298
+ Args:
299
+ validators (list[ValidatorFn] | ValidatorFn | None): Validators in
300
+ any supported representation (single, list, nested lists, etc.).
301
+ validator_type (type): Either PreValidatorFn or PostValidatorFn.
302
+
303
+ Returns:
304
+ list[ValidatorFn]: A sorted list of validator instances.
305
+
306
+ Raises:
307
+ TypeError: If an unexpected validator_type is provided.
187
308
  """
188
309
  assert validator_type in {PreValidatorFn, PostValidatorFn}
189
310
  if validators is None:
@@ -216,6 +337,12 @@ class ProtectedFn(AutonomousFn):
216
337
 
217
338
  @property
218
339
  def portal(self) -> ProtectedCodePortal:
340
+ """Return the bound ProtectedCodePortal.
341
+
342
+ Returns:
343
+ ProtectedCodePortal: The portal controlling execution context and
344
+ storage for this protected function.
345
+ """
219
346
  return super().portal
220
347
 
221
348
 
@@ -240,14 +367,33 @@ class ProtectedFn(AutonomousFn):
240
367
 
241
368
 
242
369
  def get_signature(self, arguments:dict) -> ProtectedFnCallSignature:
370
+ """Create a call signature for this protected function.
371
+
372
+ Args:
373
+ arguments (dict): Arguments to bind into the call signature.
374
+
375
+ Returns:
376
+ ProtectedFnCallSignature: Signature object representing a
377
+ particular call to this function.
378
+ """
243
379
  return ProtectedFnCallSignature(self, arguments)
244
380
 
245
381
 
246
382
  class ProtectedFnCallSignature(AutonomousFnCallSignature):
247
- """A signature of a call to a pure function"""
383
+ """Invocation signature for a protected function.
384
+
385
+ Encapsulates a function reference and bound arguments that can be executed
386
+ later via execute().
387
+ """
248
388
  _fn_cache: ProtectedFn | None
249
389
 
250
390
  def __init__(self, fn: ProtectedFn, arguments: dict):
391
+ """Initialize the signature.
392
+
393
+ Args:
394
+ fn (ProtectedFn): The protected function to call.
395
+ arguments (dict): Keyword arguments to be passed at execution time.
396
+ """
251
397
  assert isinstance(fn, ProtectedFn)
252
398
  assert isinstance(arguments, dict)
253
399
  super().__init__(fn, arguments)
@@ -259,10 +405,26 @@ class ProtectedFnCallSignature(AutonomousFnCallSignature):
259
405
 
260
406
 
261
407
  class ValidatorFn(AutonomousFn):
408
+ """Base class for validator wrappers.
409
+
410
+ A ValidatorFn ensures the wrapped callable accepts exactly the keyword
411
+ arguments declared by get_allowed_kwargs_names(). Subclasses define the
412
+ specific interface for pre/post validation phases.
413
+ """
262
414
  def __init__(self, fn: Callable | str | AutonomousFn
263
415
  , fixed_kwargs: dict | None = None
264
416
  , excessive_logging: bool | Joker = KEEP_CURRENT
265
417
  , portal: AutonomousCodePortal | None = None):
418
+ """Initialize a validator function wrapper.
419
+
420
+ Args:
421
+ fn (Callable | str | AutonomousFn): The validator implementation or
422
+ its source code.
423
+ fixed_kwargs (dict | None): Keyword arguments fixed for every
424
+ validation call.
425
+ excessive_logging (bool | Joker): Controls verbose logging.
426
+ portal (AutonomousCodePortal | None): Optional portal binding.
427
+ """
266
428
  super().__init__(
267
429
  fn=fn
268
430
  , fixed_kwargs=fixed_kwargs
@@ -274,20 +436,52 @@ class ValidatorFn(AutonomousFn):
274
436
 
275
437
  @classmethod
276
438
  def get_allowed_kwargs_names(cls)->set[str]:
439
+ """Return the exact set of allowed keyword argument names.
440
+
441
+ Subclasses must override to declare their interface.
442
+
443
+ Returns:
444
+ set[str]: Names of keyword arguments accepted by execute().
445
+ """
277
446
  raise NotImplementedError("This method must be overridden")
278
447
 
279
448
 
280
449
  def execute(self,**kwargs) \
281
450
  -> ProtectedFnCallSignature | ValidationSuccessFlag | None:
451
+ """Execute the validator after verifying keyword arguments.
452
+
453
+ Args:
454
+ **kwargs: Must exactly match get_allowed_kwargs_names().
455
+
456
+ Returns:
457
+ ProtectedFnCallSignature | ValidationSuccessFlag | None: Depending
458
+ on the validator type and outcome.
459
+ """
282
460
  assert set(kwargs) == self.get_allowed_kwargs_names()
283
461
  return super().execute(**kwargs)
284
462
 
285
463
 
286
464
  class PreValidatorFn(ValidatorFn):
465
+ """Base class for pre-execution validators.
466
+
467
+ Pre-validators are executed before the protected function. They may return:
468
+ - VALIDATION_SUCCESSFUL to indicate execution can proceed;
469
+ - ProtectedFnCallSignature to request execution of an auxiliary action
470
+ prior to re-validating;
471
+ - None to indicate failure.
472
+ """
287
473
  def __init__(self, fn: Callable | str | AutonomousFn
288
474
  , fixed_kwargs: dict | None = None
289
475
  , excessive_logging: bool | Joker = KEEP_CURRENT
290
476
  , portal: AutonomousCodePortal | None = None):
477
+ """Initialize a pre-execution validator wrapper.
478
+
479
+ Args:
480
+ fn (Callable | str | AutonomousFn): The pre-validator implementation.
481
+ fixed_kwargs (dict | None): Keyword arguments fixed for every call.
482
+ excessive_logging (bool | Joker): Controls verbose logging.
483
+ portal (AutonomousCodePortal | None): Optional portal binding.
484
+ """
291
485
  super().__init__(
292
486
  fn=fn
293
487
  , fixed_kwargs=fixed_kwargs
@@ -296,10 +490,22 @@ class PreValidatorFn(ValidatorFn):
296
490
 
297
491
 
298
492
  class SimplePreValidatorFn(PreValidatorFn):
493
+ """A pre-validator that takes no runtime inputs.
494
+
495
+ The wrapped callable must accept no parameters; use fixed_kwargs only.
496
+ """
299
497
  def __init__(self, fn: Callable | str | AutonomousFn
300
498
  , fixed_kwargs: dict | None = None
301
499
  , excessive_logging: bool | Joker = KEEP_CURRENT
302
500
  , portal: AutonomousCodePortal | None = None):
501
+ """Initialize a simple pre-validator.
502
+
503
+ Args:
504
+ fn (Callable | str | AutonomousFn): The implementation.
505
+ fixed_kwargs (dict | None): Fixed keyword arguments, if any.
506
+ excessive_logging (bool | Joker): Controls verbose logging.
507
+ portal (AutonomousCodePortal | None): Optional portal binding.
508
+ """
303
509
  super().__init__(
304
510
  fn=fn
305
511
  , fixed_kwargs=fixed_kwargs
@@ -314,10 +520,23 @@ class SimplePreValidatorFn(PreValidatorFn):
314
520
 
315
521
 
316
522
  class ComplexPreValidatorFn(PreValidatorFn):
523
+ """A pre-validator that can inspect inputs and the function address.
524
+
525
+ The callable must accept the keyword arguments named
526
+ packed_kwargs and fn_addr.
527
+ """
317
528
  def __init__(self, fn: Callable | str | AutonomousFn
318
529
  , fixed_kwargs: dict | None = None
319
530
  , excessive_logging: bool | Joker = KEEP_CURRENT
320
531
  , portal: AutonomousCodePortal | None = None):
532
+ """Initialize a complex pre-validator.
533
+
534
+ Args:
535
+ fn (Callable | str | AutonomousFn): The implementation.
536
+ fixed_kwargs (dict | None): Fixed keyword arguments, if any.
537
+ excessive_logging (bool | Joker): Controls verbose logging.
538
+ portal (AutonomousCodePortal | None): Optional portal binding.
539
+ """
321
540
  super().__init__(
322
541
  fn=fn
323
542
  , fixed_kwargs=fixed_kwargs
@@ -332,10 +551,22 @@ class ComplexPreValidatorFn(PreValidatorFn):
332
551
 
333
552
 
334
553
  class PostValidatorFn(ValidatorFn):
554
+ """Post-execution validator wrapper.
555
+
556
+ The callable must accept packed_kwargs, fn_addr, and result.
557
+ """
335
558
  def __init__(self, fn: Callable | str | AutonomousFn
336
559
  , fixed_kwargs: dict | None = None
337
560
  , excessive_logging: bool | Joker = KEEP_CURRENT
338
561
  , portal: AutonomousCodePortal | None = None):
562
+ """Initialize a post-execution validator.
563
+
564
+ Args:
565
+ fn (Callable | str | AutonomousFn): The implementation.
566
+ fixed_kwargs (dict | None): Fixed keyword arguments, if any.
567
+ excessive_logging (bool | Joker): Controls verbose logging.
568
+ portal (AutonomousCodePortal | None): Optional portal binding.
569
+ """
339
570
  super().__init__(
340
571
  fn=fn
341
572
  , fixed_kwargs=fixed_kwargs
@@ -344,5 +575,9 @@ class PostValidatorFn(ValidatorFn):
344
575
 
345
576
  @classmethod
346
577
  def get_allowed_kwargs_names(cls) -> set[str]:
347
- """Post-validators use info about the function, its input arguments and returned value."""
578
+ """Post-validators use function metadata, inputs, and the result.
579
+
580
+ Returns:
581
+ set[str]: {"packed_kwargs", "fn_addr", "result"}
582
+ """
348
583
  return {"packed_kwargs", "fn_addr", "result" }
@@ -3,13 +3,41 @@ import psutil
3
3
  import pynvml
4
4
 
5
5
  def get_unused_ram_mb() -> int:
6
- """Returns the amount of available RAM in MB. """
6
+ """Get the currently available RAM on the system in megabytes (MB).
7
+
8
+ Returns:
9
+ int: Integer number of megabytes of RAM that are currently available
10
+ to user processes as reported by psutil.virtual_memory().available.
11
+
12
+ Notes:
13
+ - The value is rounded down to the nearest integer.
14
+ - Uses powers-of-two conversion (1 MB = 1024^2 bytes).
15
+ - On systems with memory compression or overcommit, this value is an
16
+ approximation provided by the OS.
17
+ """
7
18
  free_ram = psutil.virtual_memory().available / (1024 * 1024)
8
19
  return int(free_ram)
9
20
 
10
21
 
11
22
  def get_unused_cpu_cores() -> float:
12
- """Returns the free (logical) CPU capacity"""
23
+ """Estimate currently unused logical CPU capacity in units of CPU cores.
24
+
25
+ On POSIX systems with load average support, this uses the 1-minute load
26
+ average to estimate remaining capacity: max(logical_cores - load1, 0).
27
+ On other systems, it falls back to instantaneous CPU percent usage as
28
+ reported by psutil and computes: logical_cores * (1 - usage/100).
29
+
30
+ Returns:
31
+ float: A non-negative float representing approximate available logical
32
+ CPU cores. For example, 2.5 means roughly two and a half cores free.
33
+
34
+ Notes:
35
+ - The number of logical cores (with SMT/Hyper-Threading) is used.
36
+ - If psutil reports near-zero usage, a small default (0.5%) is assumed
37
+ to avoid transient 0.0 readings.
38
+ - This is a heuristic; short spikes and scheduling nuances may cause
39
+ deviations from actual availability.
40
+ """
13
41
 
14
42
  cnt = psutil.cpu_count(logical=True) or 1
15
43
 
@@ -24,7 +52,20 @@ def get_unused_cpu_cores() -> float:
24
52
 
25
53
 
26
54
  def process_is_active(pid: int) -> bool:
27
- """Checks if a process with the given PID is active."""
55
+ """Check whether a process with the given PID is currently active.
56
+
57
+ Args:
58
+ pid (int): Operating system process identifier (PID).
59
+
60
+ Returns:
61
+ bool: True if the process exists and is running; False if it does not
62
+ exist, has exited, or cannot be inspected due to permissions or other
63
+ errors.
64
+
65
+ Notes:
66
+ - Any exception from psutil (e.g., NoSuchProcess, AccessDenied) results
67
+ in a False return value for safety.
68
+ """
28
69
  try:
29
70
  process = psutil.Process(pid)
30
71
  return process.is_running()
@@ -33,7 +74,19 @@ def process_is_active(pid: int) -> bool:
33
74
 
34
75
 
35
76
  def get_process_start_time(pid: int) -> int:
36
- """Returns the start time of the process with the given PID."""
77
+ """Get the UNIX timestamp of when a process started.
78
+
79
+ Args:
80
+ pid (int): Operating system process identifier (PID).
81
+
82
+ Returns:
83
+ int: Start time as a UNIX timestamp (seconds since epoch). Returns 0 if
84
+ the process does not exist or cannot be accessed.
85
+
86
+ Notes:
87
+ - Any exception from psutil (e.g., NoSuchProcess, AccessDenied) results
88
+ in a 0 return value for safety.
89
+ """
37
90
  try:
38
91
  process = psutil.Process(pid)
39
92
  return int(process.create_time())
@@ -42,21 +95,41 @@ def get_process_start_time(pid: int) -> int:
42
95
 
43
96
 
44
97
  def get_current_process_id() -> int:
45
- """Returns the current process ID."""
98
+ """Get the current process ID (PID).
99
+
100
+ Returns:
101
+ int: The PID of the running Python process.
102
+ """
46
103
  return psutil.Process().pid
47
104
 
48
105
 
49
106
  def get_current_process_start_time() -> int:
50
- """Returns the start time of the current process."""
107
+ """Get the UNIX timestamp for when the current Python process started.
108
+
109
+ Returns:
110
+ int: Start time as a UNIX timestamp (seconds since epoch). Returns 0 on
111
+ unexpected error.
112
+ """
51
113
  return get_process_start_time(get_current_process_id())
52
114
 
53
115
 
54
116
  def get_unused_nvidia_gpus() -> float:
55
- """Returns the total unused GPU capacity as a float value.
56
-
57
- The function calculates the sum of unused capacity across all available NVIDIA GPUs.
58
- For example, 2.0 means two completely unused GPUs
59
- 0 is returned if no GPUs or on error.
117
+ """Estimate the total unused NVIDIA GPU capacity across all devices.
118
+
119
+ This aggregates the per-GPU unused utilization percentage (100 - gpu%) and
120
+ returns the sum in "GPU units". For example, 2.0 means capacity equivalent
121
+ to two fully idle GPUs. If no NVIDIA GPUs are present or NVML is unavailable,
122
+ the function returns 0.0.
123
+
124
+ Returns:
125
+ float: Sum of unused GPU capacity across all NVIDIA GPUs in GPU units.
126
+
127
+ Notes:
128
+ - Requires NVIDIA Management Library (pynvml) to be installed and the
129
+ NVIDIA driver to be available.
130
+ - Utilization is based on instantaneous NVML readings and may fluctuate.
131
+ - Any NVML error (e.g., no devices, driver issues) results in 0.0 for
132
+ safety.
60
133
  """
61
134
  try:
62
135
  pynvml.nvmlInit()
@@ -70,7 +143,7 @@ def get_unused_nvidia_gpus() -> float:
70
143
 
71
144
  return unused_capacity / 100.0
72
145
 
73
- except pynvml.NVMLError as e:
146
+ except pynvml.NVMLError:
74
147
  # Return 0.0 on any NVML error (no GPUs, driver issues, etc.)
75
148
  return 0.0
76
149
  finally:
@@ -1,11 +1,16 @@
1
- class ValidationSuccessFlag:
2
- """Singleton class to represent a successful validation."""
1
+ from persidict.singletons import Singleton
3
2
 
4
- _instance = None
5
3
 
6
- def __new__(cls):
7
- if cls._instance is None:
8
- cls._instance = super().__new__(cls)
9
- return cls._instance
4
+ class ValidationSuccessFlag(Singleton):
5
+ """Marker singleton indicating that validation has succeeded.
10
6
 
7
+ This lightweight class is used as a unique sentinel object that signals a
8
+ successful validation outcome in protected code portals. Using a singleton
9
+ avoids ambiguity with other truthy values.
10
+ """
11
+ pass
12
+
13
+
14
+ # A canonical, importable singleton value representing a successful validation.
15
+ # Use identity checks (``is VALIDATION_SUCCESSFUL``) rather than equality.
11
16
  VALIDATION_SUCCESSFUL = ValidationSuccessFlag()