pythagoras 0.24.3__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.
@@ -2,7 +2,15 @@
2
2
  _is_in_notebook: bool|None = None
3
3
 
4
4
  def is_executed_in_notebook() -> bool:
5
- """ Return True if running inside a Jupyter notebook. """
5
+ """Return whether code is running inside a Jupyter/IPython notebook.
6
+
7
+ Uses a lightweight heuristic: checks if IPython is present and whether the
8
+ current shell exposes the set_custom_exc attribute, which is specific to
9
+ IPython interactive environments (including Jupyter).
10
+
11
+ Returns:
12
+ bool: True if running inside a Jupyter/IPython notebook, False otherwise.
13
+ """
6
14
  global _is_in_notebook
7
15
  if _is_in_notebook is not None:
8
16
  return _is_in_notebook
@@ -16,6 +16,17 @@ from .._800_signatures_and_converters.random_signatures import (
16
16
 
17
17
 
18
18
  def pth_excepthook(exc_type, exc_value, trace_back) -> None:
19
+ """sys.excepthook replacement that logs uncaught exceptions.
20
+
21
+ Args:
22
+ exc_type: The exception class.
23
+ exc_value: The exception instance.
24
+ trace_back: Traceback object for the exception.
25
+
26
+ Side Effects:
27
+ - Records the exception in the active LoggingCodePortal's crash history.
28
+ - Calls the original sys.__excepthook__ after logging.
29
+ """
19
30
  if _exception_needs_to_be_processed(exc_type, exc_value, trace_back):
20
31
  exception_id = "app_"+ get_random_signature() + "_crash"
21
32
  event_body = add_execution_environment_summary(
@@ -30,6 +41,17 @@ def pth_excepthook(exc_type, exc_value, trace_back) -> None:
30
41
 
31
42
  def pth_excepthandler(_, exc_type, exc_value
32
43
  , trace_back, tb_offset=None) -> None:
44
+ """IPython custom exception handler that logs uncaught exceptions.
45
+
46
+ This signature matches IPython's set_custom_exc handler protocol.
47
+
48
+ Args:
49
+ _: Unused first parameter required by IPython.
50
+ exc_type: The exception class.
51
+ exc_value: The exception instance.
52
+ trace_back: Traceback object for the exception.
53
+ tb_offset: Optional traceback offset used by IPython. Unused.
54
+ """
33
55
  if _exception_needs_to_be_processed(exc_type, exc_value, trace_back):
34
56
  exception_id = "app_" + get_random_signature() + "_crash"
35
57
  event_body = add_execution_environment_summary(
@@ -45,6 +67,15 @@ _previous_excepthook = None
45
67
  _number_of_handlers_registrations = 0
46
68
 
47
69
  def register_systemwide_uncaught_exception_handlers() -> None:
70
+ """Install Pythagoras handlers for uncaught exceptions system-wide.
71
+
72
+ In standard Python, replaces sys.excepthook; in Jupyter/IPython,
73
+ registers a custom exception handler via IPython.set_custom_exc when
74
+ available. Multiple registrations are reference-counted and idempotent.
75
+
76
+ Returns:
77
+ None
78
+ """
48
79
  global _number_of_handlers_registrations, _previous_excepthook
49
80
  _number_of_handlers_registrations += 1
50
81
  if _number_of_handlers_registrations > 1:
@@ -64,6 +95,15 @@ def register_systemwide_uncaught_exception_handlers() -> None:
64
95
 
65
96
 
66
97
  def unregister_systemwide_uncaught_exception_handlers() -> None:
98
+ """Uninstall previously registered Pythagoras exception handlers.
99
+
100
+ Decrements the registration reference counter. When it reaches zero,
101
+ restores the previous sys.excepthook (if any) and removes the custom
102
+ IPython exception handler in notebook environments.
103
+
104
+ Returns:
105
+ None
106
+ """
67
107
  global _number_of_handlers_registrations, _previous_excepthook
68
108
  _number_of_handlers_registrations -= 1
69
109
  if _number_of_handlers_registrations > 0:
@@ -7,12 +7,29 @@ from .safe_portal_core_classes import SafeFn, SafeCodePortal
7
7
 
8
8
 
9
9
  class safe(logging):
10
- """A decorator that converts a Python function into a SafeFn object.
10
+ """Decorator that wraps a callable into a SafeFn for portal execution.
11
+
12
+ Usage:
13
+ @safe()
14
+ def my_fn(x: int) -> int:
15
+ return x + 1
16
+
17
+ Notes:
18
+ This decorator configures only logging-related behavior for now. Actual
19
+ safety/sandboxing is not yet implemented.
11
20
  """
12
21
 
13
22
  def __init__(self
14
23
  , excessive_logging: bool|None|Joker = KEEP_CURRENT
15
24
  , portal: SafeCodePortal | None = None):
25
+ """Create a safe decorator bound to an optional portal.
26
+
27
+ Args:
28
+ excessive_logging: Whether to enable verbose logging for wrapped
29
+ calls. KEEP_CURRENT inherits from current context.
30
+ portal: The SafeCodePortal to attach the resulting SafeFn to. If
31
+ None, the active portal (if any) may be used by lower layers.
32
+ """
16
33
  assert isinstance(portal, SafeCodePortal) or portal is None
17
34
  logging.__init__(self=self
18
35
  , portal=portal
@@ -20,6 +37,15 @@ class safe(logging):
20
37
 
21
38
 
22
39
  def __call__(self,fn:Callable)->SafeFn:
40
+ """Wrap a Python callable into a SafeFn.
41
+
42
+ Args:
43
+ fn: The function to wrap.
44
+
45
+ Returns:
46
+ SafeFn: The wrapped function that can be executed via a portal and
47
+ will record logging information.
48
+ """
23
49
  wrapper = SafeFn(fn
24
50
  , portal=self._portal
25
51
  , excessive_logging=self._excessive_logging)
@@ -13,20 +13,43 @@ It will be done soon by integrating https://pypi.org/project/RestrictedPython/
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
- from typing import Callable
17
-
18
- # from parameterizable import register_parameterizable_class
19
- from persidict import PersiDict, Joker, KEEP_CURRENT
20
-
21
16
  from .._040_logging_code_portals.logging_portal_core_classes import *
22
17
 
23
18
 
24
19
  class SafeCodePortal(LoggingCodePortal):
20
+ """A portal that executes functions with logging under a "safe" contract.
21
+
22
+ Currently, SafeCodePortal inherits all behavior from LoggingCodePortal.
23
+ True sandboxing/isolation has not yet been implemented and will be
24
+ introduced in the future (see Notes).
25
+
26
+ Notes:
27
+ The actual safety guarantees (prohibiting access to external
28
+ filesystem, network, process state, etc.) are not yet enforced.
29
+ The plan is to integrate RestrictedPython to implement a proper
30
+ sandbox. Until then, SafeCodePortal behaves like LoggingCodePortal
31
+ but keeps the API intended for safe execution.
32
+ """
33
+
25
34
  def __init__(self
26
35
  , root_dict: PersiDict|str|None = None
27
36
  , p_consistency_checks: float|Joker = KEEP_CURRENT
28
37
  , excessive_logging: bool|Joker = KEEP_CURRENT
29
38
  ):
39
+ """Initialize a SafeCodePortal.
40
+
41
+ Args:
42
+ root_dict: Root persistent dictionary or its path used by the
43
+ underlying data portal for storing execution artifacts. If a
44
+ string is provided, it is treated as a path on disk. If None,
45
+ an in-memory structure may be used (depending on configuration).
46
+ p_consistency_checks: Probability in [0, 1] of running additional
47
+ consistency checks on persistent state mutations. Use
48
+ KEEP_CURRENT to inherit the active setting from parent context.
49
+ excessive_logging: Whether to enable verbose logging of execution
50
+ attempts, results, outputs and events. Use KEEP_CURRENT to
51
+ inherit the active setting from parent context.
52
+ """
30
53
  LoggingCodePortal.__init__(self
31
54
  , root_dict=root_dict
32
55
  , p_consistency_checks=p_consistency_checks
@@ -34,26 +57,60 @@ class SafeCodePortal(LoggingCodePortal):
34
57
 
35
58
 
36
59
  class SafeFnCallSignature(LoggingFnCallSignature):
37
- """A signature of a call to a safe function"""
60
+ """A signature object describing a call to a SafeFn.
61
+
62
+ This specialization only narrows the types returned by some accessors
63
+ (e.g., fn) to Safe* counterparts. All logging behavior and storage layout
64
+ are inherited from LoggingFnCallSignature.
65
+ """
38
66
  _fn_cache: SafeFn | None
39
67
 
40
68
  def __init__(self, fn: SafeFn, arguments: dict):
69
+ """Construct a signature for a specific SafeFn call.
70
+
71
+ Args:
72
+ fn: The safe function object to be called.
73
+ arguments: The keyword arguments to use for the call.
74
+ """
41
75
  assert isinstance(fn, SafeFn)
42
76
  assert isinstance(arguments, dict)
43
77
  super().__init__(fn, arguments)
44
78
 
45
79
  @property
46
80
  def fn(self) -> SafeFn:
47
- """Return the function object referenced by the signature."""
81
+ """Return the SafeFn referenced by this signature.
82
+
83
+ Returns:
84
+ SafeFn: The underlying safe function instance.
85
+ """
48
86
  return super().fn
49
87
 
50
88
 
51
89
  class SafeFn(LoggingFn):
90
+ """A function wrapper intended for safe execution within a portal.
91
+
92
+ SafeFn currently behaves like LoggingFn, adding only type-narrowed
93
+ accessors for convenience. Future versions will enforce sandboxed
94
+ execution (see Notes).
95
+
96
+ Notes:
97
+ No actual sandboxing is performed yet. Behavior equals LoggingFn.
98
+ """
99
+
52
100
  def __init__(self
53
101
  , fn: Callable|str
54
102
  , portal: LoggingCodePortal|None = None
55
103
  , excessive_logging: bool|Joker = KEEP_CURRENT
56
104
  ):
105
+ """Create a SafeFn wrapper.
106
+
107
+ Args:
108
+ fn: The Python callable to wrap or its import string.
109
+ portal: The portal to associate with this function. If None, the
110
+ active portal (if any) may be used by the underlying layers.
111
+ excessive_logging: Whether to enable verbose logging for this fn.
112
+ Use KEEP_CURRENT to inherit from the surrounding context.
113
+ """
57
114
  LoggingFn.__init__(self
58
115
  , fn = fn
59
116
  , portal=portal
@@ -61,23 +118,42 @@ class SafeFn(LoggingFn):
61
118
 
62
119
 
63
120
  def __getstate__(self):
64
- """This method is called when the object is pickled."""
121
+ """Return picklable state.
122
+
123
+ Returns:
124
+ dict: The state returned by the parent LoggingFn for pickling.
125
+ """
65
126
  state = super().__getstate__()
66
127
  return state
67
128
 
68
129
 
69
130
  def __setstate__(self, state):
70
- """This method is called when the object is unpickled."""
131
+ """Restore object state after unpickling.
132
+
133
+ Args:
134
+ state: The state previously produced by __getstate__.
135
+ """
71
136
  super().__setstate__(state)
72
137
 
73
138
 
74
139
  @property
75
140
  def portal(self) -> SafeCodePortal:
141
+ """Return the associated SafeCodePortal.
142
+
143
+ Returns:
144
+ SafeCodePortal: The portal that owns this function.
145
+ """
76
146
  return super().portal
77
147
 
78
148
 
79
149
  def get_signature(self, arguments:dict) -> SafeFnCallSignature:
80
- return SafeFnCallSignature(fn=self, arguments=arguments)
150
+ """Create a SafeFnCallSignature for a given call.
81
151
 
152
+ Args:
153
+ arguments: The keyword arguments for the call.
82
154
 
83
- # register_parameterizable_class(SafeCodePortal)
155
+ Returns:
156
+ SafeFnCallSignature: A typed call signature suitable for execution
157
+ and logging through the portal.
158
+ """
159
+ return SafeFnCallSignature(fn=self, arguments=arguments)
@@ -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.json_processor import _Markers
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:JsonSerializedParams) -> None:
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:JsonSerializedParams):
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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythagoras
3
- Version: 0.24.3
3
+ Version: 0.24.4
4
4
  Summary: Planet-scale distributed computing in Python.
5
5
  Keywords: cloud,ML,AI,serverless,distributed,parallel,machine-learning,deep-learning,pythagoras
6
6
  Author: Volodymyr (Vlad) Pavlov
@@ -6,26 +6,26 @@ 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=7L3P5AaZKS9IPqrIEv5UJc3HHDP48fw6EGQWXZcxQtM,5004
10
- pythagoras/_020_ordinary_code_portals/function_processing.py,sha256=rlhB0dpaUztv6SM2wlUdaUdETcV2pVUICYufVrQCRcc,3623
11
- pythagoras/_020_ordinary_code_portals/ordinary_decorator.py,sha256=J4qx03NEEgpYWvg4D8UkAL0PdtAt2sQyMN1op6LMFsA,1028
12
- pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py,sha256=bY2Hzy19lTezv96dSc1l5SsaGxNkMTOq8IsfnkdV1wY,9743
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=CJmJD2uzZwDuk8lQEJbpWAtN25UF57tkKYTot_yZmfo,20418
15
- pythagoras/_030_data_portals/ready_and_get.py,sha256=UKiQHkLhdAdvEwP5BTdoAnp4XEs7HDGx6026M2eMuc0,2842
16
- pythagoras/_030_data_portals/storable_decorator.py,sha256=l8W3GhVmIscgjoCTGq3tmdehKGDLIVnFbTM-GW-1G4g,578
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=sU-FCayvppF6gafBRAKsJcMC0JMBcnCCiLzu3yFmxiA,778
19
- pythagoras/_040_logging_code_portals/execution_environment_summary.py,sha256=hTvTbadYAtisZ4H8rq-n_hsKPCS38jsSz8lw_A1DIqY,2268
20
- pythagoras/_040_logging_code_portals/kw_args.py,sha256=4EYpPrr2xCgFAarmdFRKlEvGhIHC9kk9kc-VPW4S-XA,2717
21
- pythagoras/_040_logging_code_portals/logging_decorator.py,sha256=079w2_Z5HhXFLrgyXQekjuOby9CdUgFUGRbRT5lpujU,891
22
- pythagoras/_040_logging_code_portals/logging_portal_core_classes.py,sha256=FZ_tFg6tVO-kCCXR3X9FLLXxe_5M7tHtkrSmyYhM9f4,21953
23
- pythagoras/_040_logging_code_portals/notebook_checker.py,sha256=5lQJDIDzhRIRSmX1T88nAREMEMoDDFf0OIKcvTpnhzk,541
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=B3bPvX5nnJJx4JjLSGdl1xXOBU2pqo3CP-MomJAfXaE,3121
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=ZTsIMmtb3eGAWSpwdMYXRmIyaJiqV6mdFbB_Mqng784,804
28
- pythagoras/_050_safe_code_portals/safe_portal_core_classes.py,sha256=G1jYgKaOzkzYxIBMvlLYKMPk2OOpK_SQzMQVsHfBJqY,2562
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
30
  pythagoras/_060_autonomous_code_portals/autonomous_decorators.py,sha256=diWX03jZaInc55jAg391ZRBh-St5exJRLDZiS7dqYvg,2895
31
31
  pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py,sha256=21JNwBgEoIHXwCrBPb_eN5vMaHTZ98LdFkHuaMHtkI0,6665
@@ -45,7 +45,7 @@ pythagoras/_080_pure_code_portals/pure_decorator.py,sha256=WHZQzmyxgCpALHrqfeiOM
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=ZZuzkAf4Ls9oZyg_yUbj3kEEmXGTPjTCevVh-w7AAkE,12784
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
@@ -59,6 +59,6 @@ pythagoras/_900_project_stats_collector/__init__.py,sha256=Eagt-BhPPtBGgpMywx2lk
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.3.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
63
- pythagoras-0.24.3.dist-info/METADATA,sha256=tOfVHjizrk-0w3mhvbdhqpSNY-z3U_BhhMuYsa-CCVE,7467
64
- pythagoras-0.24.3.dist-info/RECORD,,
62
+ pythagoras-0.24.4.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
63
+ pythagoras-0.24.4.dist-info/METADATA,sha256=o7l1zf9TCe9tz4nhG5KtKVU_z4qI9QQV-h0IHSitI_g,7467
64
+ pythagoras-0.24.4.dist-info/RECORD,,