pythagoras 0.24.3__py3-none-any.whl → 0.24.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythagoras/_020_ordinary_code_portals/code_normalizer.py +37 -9
- pythagoras/_020_ordinary_code_portals/function_processing.py +58 -15
- pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +14 -0
- pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +196 -24
- pythagoras/_030_data_portals/data_portal_core_classes.py +74 -22
- pythagoras/_030_data_portals/ready_and_get.py +45 -4
- pythagoras/_030_data_portals/storable_decorator.py +18 -1
- pythagoras/_040_logging_code_portals/exception_processing_tracking.py +30 -2
- pythagoras/_040_logging_code_portals/execution_environment_summary.py +60 -24
- pythagoras/_040_logging_code_portals/kw_args.py +74 -12
- pythagoras/_040_logging_code_portals/logging_decorator.py +23 -1
- pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +365 -12
- pythagoras/_040_logging_code_portals/notebook_checker.py +9 -1
- pythagoras/_040_logging_code_portals/uncaught_exceptions.py +40 -0
- pythagoras/_050_safe_code_portals/safe_decorator.py +27 -1
- pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +87 -11
- pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +31 -4
- pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +94 -14
- pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +133 -4
- pythagoras/_070_protected_code_portals/list_flattener.py +45 -7
- pythagoras/_070_protected_code_portals/package_manager.py +99 -24
- pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +70 -0
- pythagoras/_070_protected_code_portals/system_utils.py +85 -12
- pythagoras/_070_protected_code_portals/validation_succesful_const.py +12 -7
- pythagoras/_090_swarming_portals/swarming_portals.py +4 -6
- pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +55 -20
- pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +20 -5
- pythagoras/_800_signatures_and_converters/hash_signatures.py +46 -10
- pythagoras/_800_signatures_and_converters/node_signature.py +27 -12
- pythagoras/_800_signatures_and_converters/random_signatures.py +14 -3
- {pythagoras-0.24.3.dist-info → pythagoras-0.24.6.dist-info}/METADATA +1 -1
- {pythagoras-0.24.3.dist-info → pythagoras-0.24.6.dist-info}/RECORD +33 -33
- {pythagoras-0.24.3.dist-info → pythagoras-0.24.6.dist-info}/WHEEL +0 -0
|
@@ -3,13 +3,41 @@ import psutil
|
|
|
3
3
|
import pynvml
|
|
4
4
|
|
|
5
5
|
def get_unused_ram_mb() -> int:
|
|
6
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
For example, 2.0 means
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
2
|
-
"""Singleton class to represent a successful validation."""
|
|
1
|
+
from persidict.singletons import Singleton
|
|
3
2
|
|
|
4
|
-
_instance = None
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
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()
|
|
@@ -23,7 +23,7 @@ from parameterizable import (
|
|
|
23
23
|
access_jsparams)
|
|
24
24
|
from persidict import PersiDict, Joker, KEEP_CURRENT
|
|
25
25
|
|
|
26
|
-
from parameterizable
|
|
26
|
+
from parameterizable import *
|
|
27
27
|
|
|
28
28
|
from .. import VALIDATION_SUCCESSFUL
|
|
29
29
|
from .._010_basic_portals import get_all_known_portals
|
|
@@ -212,10 +212,8 @@ class SwarmingPortal(PureCodePortal):
|
|
|
212
212
|
del self._max_n_workers_cache
|
|
213
213
|
super()._invalidate_cache()
|
|
214
214
|
|
|
215
|
-
# parameterizable.register_parameterizable_class(SwarmingPortal)
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
def _launch_many_background_workers(portal_init_jsparams:JsonSerializedParams) -> None:
|
|
216
|
+
def _launch_many_background_workers(portal_init_jsparams:JsonSerializedObject) -> None:
|
|
219
217
|
"""Launch many background worker processes."""
|
|
220
218
|
|
|
221
219
|
|
|
@@ -265,7 +263,7 @@ def _launch_many_background_workers(portal_init_jsparams:JsonSerializedParams) -
|
|
|
265
263
|
list_of_all_workers = new_list_of_all_workers
|
|
266
264
|
|
|
267
265
|
|
|
268
|
-
def _background_worker(portal_init_jsparams:
|
|
266
|
+
def _background_worker(portal_init_jsparams:JsonSerializedObject) -> None:
|
|
269
267
|
"""Background worker that keeps processing random execution requests."""
|
|
270
268
|
portal = parameterizable.loadjs(portal_init_jsparams)
|
|
271
269
|
assert isinstance(portal, SwarmingPortal)
|
|
@@ -283,7 +281,7 @@ def _background_worker(portal_init_jsparams:JsonSerializedParams) -> None:
|
|
|
283
281
|
portal._randomly_delay_execution()
|
|
284
282
|
|
|
285
283
|
|
|
286
|
-
def _process_random_execution_request(portal_init_jsparams:
|
|
284
|
+
def _process_random_execution_request(portal_init_jsparams:JsonSerializedObject):
|
|
287
285
|
"""Process one random execution request."""
|
|
288
286
|
# portal = parameterizable.get_object_from_portable_params(
|
|
289
287
|
# portal_init_params)
|
|
@@ -1,48 +1,83 @@
|
|
|
1
|
+
from typing import Final
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
_BASE32_ALPHABET: Final[str] = '0123456789abcdefghijklmnopqrstuv'
|
|
4
|
+
_BASE32_ALPHABET_MAP: Final[dict[str, int]] = {
|
|
5
|
+
char:index for index,char in enumerate(_BASE32_ALPHABET)}
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
def convert_base16_to_base32(hexdigest: str) -> str:
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
+
"""Convert a hexadecimal (base16) string to this project's base32.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
hexdigest (str): A hexadecimal string (case-insensitive). May be an
|
|
13
|
+
empty string or "0" to represent zero.
|
|
9
14
|
|
|
10
|
-
:
|
|
11
|
-
|
|
15
|
+
Returns:
|
|
16
|
+
str: The corresponding value encoded with the custom base32 alphabet
|
|
17
|
+
(digits 0-9 then letters a-v).
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
>>> convert_base16_to_base32("ff")
|
|
21
|
+
'7v'
|
|
12
22
|
"""
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
try:
|
|
25
|
+
num = int(hexdigest, 16)
|
|
26
|
+
except ValueError as e:
|
|
27
|
+
raise ValueError(f"Invalid hexadecimal string: {hexdigest}") from e
|
|
28
|
+
|
|
17
29
|
base32_str = convert_int_to_base32(num)
|
|
18
30
|
|
|
19
31
|
return base32_str
|
|
20
32
|
|
|
21
33
|
|
|
22
34
|
def convert_int_to_base32(n: int) -> str:
|
|
23
|
-
"""
|
|
24
|
-
|
|
35
|
+
"""Convert a non-negative integer to Pythagoras' base32 string.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
n (int): Non-negative integer to encode.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
str: The base32 representation.
|
|
25
42
|
|
|
26
|
-
:
|
|
27
|
-
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If n is negative.
|
|
28
45
|
"""
|
|
46
|
+
if n < 0:
|
|
47
|
+
raise ValueError("n must be non-negative")
|
|
48
|
+
|
|
49
|
+
if n == 0:
|
|
50
|
+
return "0"
|
|
51
|
+
|
|
29
52
|
base32_str = ''
|
|
30
53
|
while n > 0:
|
|
31
|
-
base32_str =
|
|
54
|
+
base32_str = _BASE32_ALPHABET[n & 31] + base32_str
|
|
32
55
|
n >>= 5
|
|
33
56
|
|
|
34
57
|
return base32_str
|
|
35
58
|
|
|
36
59
|
def convert_base_32_to_int(digest: str) -> int:
|
|
37
|
-
"""
|
|
38
|
-
Convert a base 32 string to an integer.
|
|
60
|
+
"""Convert a base32 string (custom alphabet) to an integer.
|
|
39
61
|
|
|
40
|
-
:
|
|
41
|
-
|
|
62
|
+
Args:
|
|
63
|
+
digest (str): String encoded with Pythagoras' base32 alphabet.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
int: The decoded non-negative integer value.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
KeyError: If digest contains a character outside the supported
|
|
70
|
+
base32 alphabet (0-9, a-v).
|
|
42
71
|
"""
|
|
72
|
+
if not digest:
|
|
73
|
+
raise ValueError("Digest cannot be empty")
|
|
74
|
+
|
|
43
75
|
digest = digest.lower()
|
|
44
76
|
num = 0
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
try:
|
|
78
|
+
for char in digest:
|
|
79
|
+
num = num * 32 + _BASE32_ALPHABET_MAP[char]
|
|
80
|
+
except KeyError as e:
|
|
81
|
+
raise ValueError(f"Invalid character '{e.args[0]}' in base32 digest: {digest}") from e
|
|
47
82
|
return num
|
|
48
83
|
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
from datetime import datetime, timezone
|
|
2
|
+
from typing import Final
|
|
2
3
|
|
|
4
|
+
_MONTH_ABBREVIATIONS: Final[tuple[str, ...]] = (
|
|
5
|
+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
6
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
|
|
9
|
+
def current_date_gmt_string() -> str:
|
|
10
|
+
"""Get the current UTC date as a compact string.
|
|
11
|
+
|
|
12
|
+
Produces an underscore-delimited UTC date string suitable for
|
|
13
|
+
stable file names and log records.
|
|
14
|
+
|
|
15
|
+
The format is: "YYYY_MMMonAbbrev_dd_utc" (e.g., "2024_12Dec_11_utc").
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: The formatted UTC date string, for the current moment.
|
|
6
19
|
"""
|
|
7
20
|
|
|
8
21
|
utc_now = datetime.now(timezone.utc)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
22
|
+
month_abbrev = _MONTH_ABBREVIATIONS[utc_now.month - 1]
|
|
23
|
+
# locale-dependent month abbreviation
|
|
24
|
+
result = (f"{utc_now.year}_{utc_now.month:02d}{month_abbrev}" +
|
|
25
|
+
f"_{utc_now.day:02d}_utc")
|
|
26
|
+
return result
|
|
@@ -1,33 +1,69 @@
|
|
|
1
1
|
import sys
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any, Final
|
|
3
3
|
|
|
4
4
|
import joblib.hashing
|
|
5
5
|
|
|
6
6
|
from .base_16_32_convertors import convert_base16_to_base32
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
_HASH_TYPE: Final[str] = "sha256"
|
|
10
|
+
_MAX_SIGNATURE_LENGTH: Final[int] = 22
|
|
11
11
|
|
|
12
12
|
def get_base16_hash_signature(x:Any) -> str:
|
|
13
|
-
"""
|
|
13
|
+
"""Compute a hexadecimal (base16) hash for an arbitrary Python object.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
This function delegates to joblib's hashing utilities. If NumPy is
|
|
16
|
+
imported in the current process, it uses NumpyHasher for efficient and
|
|
17
|
+
stable hashing of NumPy arrays; otherwise it uses the generic Hasher.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
x (Any): The object to hash. Must be picklable by joblib unless a
|
|
21
|
+
specialized routine (e.g., for NumPy arrays) is available.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: A hexadecimal string digest computed with the configured
|
|
25
|
+
algorithm (sha256 by default).
|
|
26
|
+
|
|
27
|
+
Notes:
|
|
28
|
+
- joblib relies on pickle for most Python objects; ensure that custom
|
|
29
|
+
objects are picklable for stable results.
|
|
30
|
+
- The digest is deterministic for the same object content.
|
|
17
31
|
"""
|
|
18
32
|
if 'numpy' in sys.modules:
|
|
19
|
-
hasher = joblib.hashing.NumpyHasher(hash_name=
|
|
33
|
+
hasher = joblib.hashing.NumpyHasher(hash_name=_HASH_TYPE)
|
|
20
34
|
else:
|
|
21
|
-
hasher = joblib.hashing.Hasher(hash_name=
|
|
35
|
+
hasher = joblib.hashing.Hasher(hash_name=_HASH_TYPE)
|
|
22
36
|
hash_signature = hasher.hash(x)
|
|
23
37
|
return str(hash_signature)
|
|
24
38
|
|
|
25
39
|
def get_base32_hash_signature(x:Any) -> str:
|
|
26
|
-
"""
|
|
40
|
+
"""Compute a base32-encoded hash for an arbitrary Python object.
|
|
41
|
+
|
|
42
|
+
Internally computes a hexadecimal digest first, then converts it to the
|
|
43
|
+
custom base32 alphabet used by Pythagoras.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
x (Any): The object to hash.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: The full-length base32 digest string (not truncated).
|
|
50
|
+
"""
|
|
27
51
|
base_16_hash = get_base16_hash_signature(x)
|
|
28
52
|
base_32_hash = convert_base16_to_base32(base_16_hash)
|
|
29
53
|
return base_32_hash
|
|
30
54
|
|
|
31
55
|
def get_hash_signature(x:Any) -> str:
|
|
32
|
-
|
|
56
|
+
"""Compute a short, URL-safe hash signature for an object.
|
|
57
|
+
|
|
58
|
+
This is a convenience wrapper that returns the first max_signature_length
|
|
59
|
+
characters of the base32 digest, which is typically sufficient for
|
|
60
|
+
collision-resistant identifiers in logs and filenames.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
x (Any): The object to hash.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
str: The truncated base32 digest string.
|
|
67
|
+
"""
|
|
68
|
+
return get_base32_hash_signature(x)[:_MAX_SIGNATURE_LENGTH]
|
|
33
69
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getpass
|
|
2
|
+
import platform
|
|
3
|
+
import uuid
|
|
2
4
|
from functools import cache
|
|
3
5
|
|
|
4
6
|
from .hash_signatures import get_hash_signature
|
|
@@ -6,15 +8,28 @@ from .hash_signatures import get_hash_signature
|
|
|
6
8
|
|
|
7
9
|
@cache
|
|
8
10
|
def get_node_signature() -> str:
|
|
9
|
-
"""
|
|
11
|
+
"""Return a stable signature for the current computing node and user.
|
|
12
|
+
|
|
13
|
+
The signature is derived from a concatenation of multiple system- and
|
|
14
|
+
user-specific attributes (MAC address, OS info, CPU, username) and then
|
|
15
|
+
hashed using Pythagoras' short base32 digest. The result is intended to
|
|
16
|
+
uniquely identify the node within logs and distributed systems.
|
|
17
|
+
|
|
18
|
+
Caching:
|
|
19
|
+
The result is cached for the lifetime of the process using
|
|
20
|
+
functools.cache, as the underlying attributes are not expected to
|
|
21
|
+
change while the process is running.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: A short base32 signature string representing this node.
|
|
10
25
|
"""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return get_hash_signature(
|
|
26
|
+
id_parts = [
|
|
27
|
+
str(uuid.getnode()),
|
|
28
|
+
platform.system(),
|
|
29
|
+
platform.release(),
|
|
30
|
+
platform.version(),
|
|
31
|
+
platform.machine(),
|
|
32
|
+
platform.processor(),
|
|
33
|
+
getpass.getuser(),
|
|
34
|
+
]
|
|
35
|
+
return get_hash_signature("".join(id_parts))
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import uuid
|
|
2
|
+
from typing import Final
|
|
2
3
|
|
|
3
4
|
from .base_16_32_convertors import convert_int_to_base32
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
_MAX_SIGNATURE_LENGTH: Final[int] = 22
|
|
6
7
|
|
|
7
8
|
def get_random_signature() -> str:
|
|
8
|
-
|
|
9
|
+
"""Generate a short, random base32 signature string.
|
|
10
|
+
|
|
11
|
+
The randomness is sourced from uuid.uuid4(), which uses a cryptographically
|
|
12
|
+
strong RNG provided by the OS. The resulting large integer is encoded with
|
|
13
|
+
Pythagoras' base32 alphabet and truncated to max_signature_length
|
|
14
|
+
characters.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
str: A random, URL-safe base32 string of length up to
|
|
18
|
+
max_signature_length.
|
|
19
|
+
"""
|
|
9
20
|
random_int = uuid.uuid4().int
|
|
10
21
|
random_str = convert_int_to_base32(random_int)
|
|
11
|
-
return random_str[:
|
|
22
|
+
return random_str[:_MAX_SIGNATURE_LENGTH]
|
|
@@ -6,59 +6,59 @@ pythagoras/_010_basic_portals/long_infoname.py,sha256=KXOmHfQ_5hdZNqfB3Cif2CQiZ3
|
|
|
6
6
|
pythagoras/_010_basic_portals/portal_tester.py,sha256=x6HiJ3GW9XWplnsT6Ob7QCy2J_JPgGpdaJ8QRyFH-e8,3353
|
|
7
7
|
pythagoras/_010_basic_portals/post_init_metaclass.py,sha256=94FEVMCJBUReRb-fo2-LW8YWXUXw5lLLYlXMnlxHJuU,1495
|
|
8
8
|
pythagoras/_020_ordinary_code_portals/__init__.py,sha256=p3kSqaQYj0xlhk9BwptFgA1USdTbfHkAB5Q9MH-ANI0,1295
|
|
9
|
-
pythagoras/_020_ordinary_code_portals/code_normalizer.py,sha256=
|
|
10
|
-
pythagoras/_020_ordinary_code_portals/function_processing.py,sha256=
|
|
11
|
-
pythagoras/_020_ordinary_code_portals/ordinary_decorator.py,sha256=
|
|
12
|
-
pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py,sha256=
|
|
9
|
+
pythagoras/_020_ordinary_code_portals/code_normalizer.py,sha256=GJXvBX85W230OcwNAg8XXHAE4m39Q70piJ9g2jcIr4Q,6616
|
|
10
|
+
pythagoras/_020_ordinary_code_portals/function_processing.py,sha256=b9Y4vNf1KSRkpUfS9UmWj3N_CNuPDgUMXdnR3IeyAoM,5021
|
|
11
|
+
pythagoras/_020_ordinary_code_portals/ordinary_decorator.py,sha256=ETmy-HKRzCJfzTPOI-gelQT2tlJpVLRu4I85yv02vuo,1437
|
|
12
|
+
pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py,sha256=C5nobGkYZBi2IHc1Pjfxjx_8h_ajF3Sf9Jq3yxFIR68,15963
|
|
13
13
|
pythagoras/_030_data_portals/__init__.py,sha256=f_F9DCmuVgPMgzwRjuNj6FI63S3oXu7lj3zU66Nw7Hc,1427
|
|
14
|
-
pythagoras/_030_data_portals/data_portal_core_classes.py,sha256=
|
|
15
|
-
pythagoras/_030_data_portals/ready_and_get.py,sha256=
|
|
16
|
-
pythagoras/_030_data_portals/storable_decorator.py,sha256=
|
|
14
|
+
pythagoras/_030_data_portals/data_portal_core_classes.py,sha256=D_Zg-q15II7IwweEGdBAMhGt09DYN_v_G0XFb5K3WB4,22459
|
|
15
|
+
pythagoras/_030_data_portals/ready_and_get.py,sha256=lgkDygF4lFnZXcCvP5dmvzQX64wrZ8AnYJTI7v2ppng,4172
|
|
16
|
+
pythagoras/_030_data_portals/storable_decorator.py,sha256=u70K8J44NTlofc4HmKHtxxbwKdUkBz6BDsIWds4dALQ,1160
|
|
17
17
|
pythagoras/_040_logging_code_portals/__init__.py,sha256=q2hVyOVgE-9Ru3ycilK98YS9Rat8tSc6erd7AtGxpaA,996
|
|
18
|
-
pythagoras/_040_logging_code_portals/exception_processing_tracking.py,sha256=
|
|
19
|
-
pythagoras/_040_logging_code_portals/execution_environment_summary.py,sha256
|
|
20
|
-
pythagoras/_040_logging_code_portals/kw_args.py,sha256=
|
|
21
|
-
pythagoras/_040_logging_code_portals/logging_decorator.py,sha256
|
|
22
|
-
pythagoras/_040_logging_code_portals/logging_portal_core_classes.py,sha256=
|
|
23
|
-
pythagoras/_040_logging_code_portals/notebook_checker.py,sha256=
|
|
18
|
+
pythagoras/_040_logging_code_portals/exception_processing_tracking.py,sha256=DH1eeJAeVL6Fn-6sXBqx3Ocu2RXxVhLqdH1mvhM24VY,1850
|
|
19
|
+
pythagoras/_040_logging_code_portals/execution_environment_summary.py,sha256=-0ynNibGfRh3J1Sq-N9j7eN1FlGvetEBiW0L4K_qJ30,3813
|
|
20
|
+
pythagoras/_040_logging_code_portals/kw_args.py,sha256=j3Iao80gTBjPx6dk1Azd9D8pcdQ3QdfpkQtQq-4ATV0,4954
|
|
21
|
+
pythagoras/_040_logging_code_portals/logging_decorator.py,sha256=-BGduV2U5SJ40qW8afRCUMrzYv5Pf_CgB4HlSEGDmlA,1766
|
|
22
|
+
pythagoras/_040_logging_code_portals/logging_portal_core_classes.py,sha256=R--DuvPGKzEB4ojWrmmuVU2SezUBVrSXrmdFjnrf72g,34977
|
|
23
|
+
pythagoras/_040_logging_code_portals/notebook_checker.py,sha256=qO7zfMC20hM4tSxlqB7gy6WI4imWX4Xl7ojSwgeVu0A,871
|
|
24
24
|
pythagoras/_040_logging_code_portals/output_capturer.py,sha256=ohCp6qqxL7IuJGfnFuCIgj5Oc4HmC8c7uZGE_uzWkZk,4216
|
|
25
|
-
pythagoras/_040_logging_code_portals/uncaught_exceptions.py,sha256=
|
|
25
|
+
pythagoras/_040_logging_code_portals/uncaught_exceptions.py,sha256=vQrY1mOYdAeKaCmCCY1MUy4xoXurQkfwQuDA43giPl0,4564
|
|
26
26
|
pythagoras/_050_safe_code_portals/__init__.py,sha256=YR-V6W2WZ17SjqmTyY2xdY16xTVEEuLs2MddJj_WCZU,557
|
|
27
|
-
pythagoras/_050_safe_code_portals/safe_decorator.py,sha256=
|
|
28
|
-
pythagoras/_050_safe_code_portals/safe_portal_core_classes.py,sha256=
|
|
27
|
+
pythagoras/_050_safe_code_portals/safe_decorator.py,sha256=AYvX7-km2reRMZ55ndO_2IS2SfHbnpyFv79AVwGg7Po,1681
|
|
28
|
+
pythagoras/_050_safe_code_portals/safe_portal_core_classes.py,sha256=naY4R91N5bcCq8C_-YeBqhrr6UG8zkEQ5t8C3O8ytOw,5673
|
|
29
29
|
pythagoras/_060_autonomous_code_portals/__init__.py,sha256=hnv_dxxRx8c7IDf1QgVYHfYoeVAz8oD9K0oWI_o9N20,1704
|
|
30
|
-
pythagoras/_060_autonomous_code_portals/autonomous_decorators.py,sha256=
|
|
31
|
-
pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py,sha256=
|
|
32
|
-
pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py,sha256=
|
|
30
|
+
pythagoras/_060_autonomous_code_portals/autonomous_decorators.py,sha256=Y5zKrCK7ROY3_0RAIz6yf47Udsr0m3Co1f1isdybBcQ,4042
|
|
31
|
+
pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py,sha256=Nj4TPML94eS0WThoOfaqZhlPJu6RttvDg69VT86WPlg,9881
|
|
32
|
+
pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py,sha256=arfAuFBY4Dx9Zmf0b3x-axrd35QY2Kg_2jHJC8ek3p8,11977
|
|
33
33
|
pythagoras/_070_protected_code_portals/__init__.py,sha256=TvGcJaz20Qqsmv8m2pA4duBtFn_CdCKfkSbOSFoJS8k,989
|
|
34
34
|
pythagoras/_070_protected_code_portals/basic_pre_validators.py,sha256=6wrWKumBr2eyEhqpzZv8UlcX0WwUnAUzQ9D4cFyx1OE,2067
|
|
35
35
|
pythagoras/_070_protected_code_portals/fn_arg_names_checker.py,sha256=6FjOUJmGgDCjkFcXf5Ook-E9eiEFguarY2qqzOyJj7A,1230
|
|
36
|
-
pythagoras/_070_protected_code_portals/list_flattener.py,sha256=
|
|
37
|
-
pythagoras/_070_protected_code_portals/package_manager.py,sha256=
|
|
36
|
+
pythagoras/_070_protected_code_portals/list_flattener.py,sha256=9V1Xj_y5nOCXS2V9mcBFX6UsyDdOR108SBqxbC-Ziyk,1604
|
|
37
|
+
pythagoras/_070_protected_code_portals/package_manager.py,sha256=KbvEGfeKQsWIz0UogVUHfW6enbBmnqo1OjJz1xMTL4o,5437
|
|
38
38
|
pythagoras/_070_protected_code_portals/protected_decorators.py,sha256=5Y62rswuD7CD1duXd54_rhMb6-lLh7YvgdDgojP577g,1598
|
|
39
|
-
pythagoras/_070_protected_code_portals/protected_portal_core_classes.py,sha256=
|
|
40
|
-
pythagoras/_070_protected_code_portals/system_utils.py,sha256=
|
|
41
|
-
pythagoras/_070_protected_code_portals/validation_succesful_const.py,sha256=
|
|
39
|
+
pythagoras/_070_protected_code_portals/protected_portal_core_classes.py,sha256=pYuAxDEZKmxAN6CnqjHzFKq_5XoLZ6sEBpnJcwBPJQo,17034
|
|
40
|
+
pythagoras/_070_protected_code_portals/system_utils.py,sha256=Uv111FaO33xAA9wZ2iwtW8Gf-FXJBP2ld1sMBmvsHdo,5124
|
|
41
|
+
pythagoras/_070_protected_code_portals/validation_succesful_const.py,sha256=DrM-Mf6dDLFJ7AmfzntD39Z23YMFfF6am78XU54AlnM,577
|
|
42
42
|
pythagoras/_080_pure_code_portals/__init__.py,sha256=OI7836lLHT51SYdFfmWp4GdGRgcAkpLiAj-Zj_g2Gxo,1052
|
|
43
43
|
pythagoras/_080_pure_code_portals/pure_core_classes.py,sha256=6AjtE9QdiG84e9WuJtsrvkuHTRC4MovC31xItGn2PD8,20455
|
|
44
44
|
pythagoras/_080_pure_code_portals/pure_decorator.py,sha256=WHZQzmyxgCpALHrqfeiOMrM6TDkZcv0Y2b756ez4Q2k,2279
|
|
45
45
|
pythagoras/_080_pure_code_portals/recursion_pre_validator.py,sha256=n03ooGISJvuwNWteBN9t7CFFSLYAu86AHHFJVcywPqg,1865
|
|
46
46
|
pythagoras/_090_swarming_portals/__init__.py,sha256=TuA17PftTBudptAblNtBlD46BqUiitksOtf3y01QKm0,514
|
|
47
47
|
pythagoras/_090_swarming_portals/output_suppressor.py,sha256=ENRtQtK_-7A94lAqtUQsIWrvtcgKniEpaWcZZZrpfQM,611
|
|
48
|
-
pythagoras/_090_swarming_portals/swarming_portals.py,sha256=
|
|
48
|
+
pythagoras/_090_swarming_portals/swarming_portals.py,sha256=3d8sRniGdW_rKp2zKxdqoCvLA4Em1XW5xofhFtzDLf0,12696
|
|
49
49
|
pythagoras/_100_top_level_API/__init__.py,sha256=s5LtwskY2nwkRPFKzP0PrCzQ1c9oScZO0kM9_bWLi3U,64
|
|
50
50
|
pythagoras/_100_top_level_API/default_local_portal.py,sha256=SnykTpTXg1KuT1qwDnrAZ63lYshMy-0nNiUgoOVMxCs,339
|
|
51
51
|
pythagoras/_100_top_level_API/top_level_API.py,sha256=S2NXW4bfL98o6Txn6NM0EeBb1nzwFtPSl-yWNevAQIE,906
|
|
52
52
|
pythagoras/_800_signatures_and_converters/__init__.py,sha256=WAzpPe8fsh_w_7HhVxJZLBid7gxnW3pmPZW86fYnJjk,166
|
|
53
|
-
pythagoras/_800_signatures_and_converters/base_16_32_convertors.py,sha256
|
|
54
|
-
pythagoras/_800_signatures_and_converters/current_date_gmt_str.py,sha256=
|
|
55
|
-
pythagoras/_800_signatures_and_converters/hash_signatures.py,sha256
|
|
56
|
-
pythagoras/_800_signatures_and_converters/node_signature.py,sha256=
|
|
57
|
-
pythagoras/_800_signatures_and_converters/random_signatures.py,sha256=
|
|
53
|
+
pythagoras/_800_signatures_and_converters/base_16_32_convertors.py,sha256=-E67xY0zUUMyn-xeRpwz7-sZfFXaaiVFLKQf2OHgNw0,2146
|
|
54
|
+
pythagoras/_800_signatures_and_converters/current_date_gmt_str.py,sha256=jMBQaUj_33Yto415IxuiRrNoiuxbORZS-En7bPP1JZU,841
|
|
55
|
+
pythagoras/_800_signatures_and_converters/hash_signatures.py,sha256=-RDExpYwXCWXtjgIfS3xiCYAJxGuiFSZzJ4p2pAvdmc,2278
|
|
56
|
+
pythagoras/_800_signatures_and_converters/node_signature.py,sha256=W5Rg6q0jBRRJzHnnemt8mVapoy2jIswOEg9xIm4poXo,1090
|
|
57
|
+
pythagoras/_800_signatures_and_converters/random_signatures.py,sha256=IpZ7uwReCd-K9Yq1IZoubxn7kidnRfpB9-GBKXdqYdA,719
|
|
58
58
|
pythagoras/_900_project_stats_collector/__init__.py,sha256=Eagt-BhPPtBGgpMywx2lkLDK1603Y9t_QBdtHKUHHFY,71
|
|
59
59
|
pythagoras/_900_project_stats_collector/project_analyzer.py,sha256=uhycFKjUIXEpYcZYnak3yn4JFhchl-oZ7wt6spFxhoY,3574
|
|
60
60
|
pythagoras/__init__.py,sha256=TMPtJdSi_WShCpJnsVVdO48Wcvs78GMbUi5gHc1eMLw,1233
|
|
61
61
|
pythagoras/core/__init__.py,sha256=cXtQ-Vbm8TqzazvkFws5cV3AEEYbEKzNXYeuHeLGFK0,328
|
|
62
|
-
pythagoras-0.24.
|
|
63
|
-
pythagoras-0.24.
|
|
64
|
-
pythagoras-0.24.
|
|
62
|
+
pythagoras-0.24.6.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
|
|
63
|
+
pythagoras-0.24.6.dist-info/METADATA,sha256=uBQsa9SitAVOruwJdT0dxnjU6rO4fiB7BEOIh4g9D3U,7467
|
|
64
|
+
pythagoras-0.24.6.dist-info/RECORD,,
|
|
File without changes
|