numbox 0.3.2__py3-none-any.whl → 0.3.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.

Potentially problematic release.


This version of numbox might be problematic. Click here for more details.

numbox/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.3.2'
1
+ __version__ = '0.3.4'
@@ -3,7 +3,7 @@ import warnings
3
3
  from collections import defaultdict
4
4
  from dataclasses import dataclass, field
5
5
  from typing import (
6
- Any, Callable, Dict, List, Mapping, Set, Tuple, TypeAlias, Union
6
+ Any, Callable, Dict, Iterator, List, Mapping, Set, Tuple, TypeAlias, Union
7
7
  )
8
8
 
9
9
 
@@ -23,7 +23,7 @@ _null = _Null()
23
23
  QUAL_SEP = "."
24
24
 
25
25
 
26
- def _make_qual_name(namespace_name: str, var_name: str) -> str:
26
+ def make_qual_name(namespace_name: str, var_name: str) -> str:
27
27
  """ Each `Variable` instance is best contained in a
28
28
  mapping namespace, with the given `namespace_name`.
29
29
  This function thereby returns qualified name of the
@@ -55,6 +55,9 @@ class External:
55
55
  self._vars[name] = variable
56
56
  return variable
57
57
 
58
+ def __iter__(self) -> Iterator['Variable']:
59
+ return iter(self._vars.values())
60
+
58
61
 
59
62
  @dataclass(frozen=True)
60
63
  class Variable:
@@ -92,6 +95,8 @@ class Variable:
92
95
  these variables.
93
96
  :param formula: (optional) function that calculates value
94
97
  of this `Variable` from its sources.
98
+ :param metadata: any possible metadata associated with
99
+ this variable.
95
100
  :param cacheable: (default `False`) when `True`, the
96
101
  corresponding `Value` (see below) will be cached during
97
102
  calculation by the `id` of the corresponding Python object
@@ -102,6 +107,7 @@ class Variable:
102
107
  source: str = field(default="")
103
108
  inputs: Mapping[str, str] = field(default_factory=lambda: {})
104
109
  formula: Callable = field(default=None)
110
+ metadata: str | None = field(default=None)
105
111
  cacheable: bool = field(default=False)
106
112
 
107
113
  def __hash__(self):
@@ -111,7 +117,7 @@ class Variable:
111
117
  return isinstance(other, Variable) and self.name == other.name and self.source == other.source
112
118
 
113
119
  def qual_name(self) -> str:
114
- return _make_qual_name(self.source, self.name)
120
+ return make_qual_name(self.source, self.name)
115
121
 
116
122
 
117
123
  class Variables:
@@ -140,6 +146,9 @@ class Variables:
140
146
  """
141
147
  return self.variables[variable_name]
142
148
 
149
+ def __iter__(self) -> Iterator[Variable]:
150
+ return iter(self.variables.values())
151
+
143
152
 
144
153
  @dataclass
145
154
  class Value:
@@ -169,8 +178,8 @@ class CompiledNode:
169
178
  inputs: List[Variable]
170
179
 
171
180
  def __post_init__(self):
172
- if self.variable.formula is None and self.inputs:
173
- raise RuntimeError(f"{self.variable} contains inputs but no formula, how come?")
181
+ if self.variable.formula and not self.inputs:
182
+ raise RuntimeError(f"{self.variable} contains formula but no inputs, how come?")
174
183
 
175
184
  def __hash__(self):
176
185
  return hash((self.variable.source, self.variable.name))
@@ -209,7 +218,6 @@ class CompiledGraph:
209
218
  source and then from the name of the variable within that
210
219
  source to the variable's actual value.
211
220
  :param values: runtime storage of all values, instance of `Values`.
212
- :return:
213
221
  """
214
222
  self._assign_external_values(external_values, values)
215
223
  self._calculate(self.ordered_nodes, values)
@@ -238,7 +246,7 @@ class CompiledGraph:
238
246
  for var_name, variable in variables.items():
239
247
  if var_name not in provided:
240
248
  raise KeyError(
241
- f"Missing value for external variable '{_make_qual_name(source_name, var_name)}'"
249
+ f"Missing value for external variable '{make_qual_name(source_name, var_name)}'"
242
250
  )
243
251
  values.get(variable).value = provided[var_name]
244
252
 
@@ -295,7 +303,7 @@ class CompiledGraph:
295
303
  for src, vals in changed.items():
296
304
  for name, val in vals.items():
297
305
  variable = self.required_external_variables.get(src, {}).get(name)
298
- qual = _make_qual_name(src, name)
306
+ qual = make_qual_name(src, name)
299
307
  if variable is None:
300
308
  try:
301
309
  variable = next(n.variable for n in self.ordered_nodes if n.variable.qual_name() == qual)
@@ -409,7 +417,7 @@ class Graph:
409
417
  if isinstance(source, External):
410
418
  used_external_vars.add(variable)
411
419
  for input_name, input_source in variable.inputs.items():
412
- visit(_make_qual_name(input_source, input_name))
420
+ visit(make_qual_name(input_source, input_name))
413
421
  visiting.remove(qual_name)
414
422
  visited.add(qual_name)
415
423
  ordered_variables.append(variable)
@@ -430,3 +438,71 @@ class Graph:
430
438
  variable_source = variable.source
431
439
  required_external_variables[variable_source][variable_name] = variable
432
440
  return required_external_variables
441
+
442
+ def _build_reverse_dependencies(self) -> Dict[str, set[str]]:
443
+ """
444
+ Utility to calculate set of qualified names of variables
445
+ impacted by each of the encountered inputs.
446
+ """
447
+ reverse = defaultdict(set)
448
+ for source_name, source in self.registry.items():
449
+ for variable in source:
450
+ var_qual = make_qual_name(source_name, variable.name)
451
+ for input_name, input_source in variable.inputs.items():
452
+ input_qual = make_qual_name(input_source, input_name)
453
+ reverse[input_qual].add(var_qual)
454
+ return reverse
455
+
456
+ def dependents_of(self, qual_names: List[str] | Set[str] | str) -> Set[str]:
457
+ """
458
+ Return qualified names of `Variable`s that directly or indirectly
459
+ depend on any of `qual_names`.
460
+ """
461
+ if isinstance(qual_names, str):
462
+ qual_names = {qual_names}
463
+ else:
464
+ qual_names = set(qual_names)
465
+ reverse = self._build_reverse_dependencies()
466
+ result = set(qual_names)
467
+ stack = list(qual_names)
468
+ while stack:
469
+ current = stack.pop()
470
+ for dep in reverse.get(current, ()):
471
+ if dep not in result:
472
+ result.add(dep)
473
+ stack.append(dep)
474
+ return result
475
+
476
+ def explain(self, qual_name: str, direct: bool = True) -> str:
477
+ """
478
+ Follow the dependencies chain to explain how the given
479
+ variable is derived.
480
+ :param qual_name: qualified name of the `Variable`.
481
+ :param direct: when `True` (default), begin explanation
482
+ with `qual_name`.
483
+ """
484
+ derived = set()
485
+ derivation = []
486
+
487
+ def collect(qual_name_: str):
488
+ if qual_name_ in derived:
489
+ return
490
+ derived.add(qual_name_)
491
+ source_name, variable_name = qual_name_.split(QUAL_SEP)
492
+ variable_source = self.registry[source_name]
493
+ variable = variable_source[variable_name]
494
+ inputs_qual_names = []
495
+ for input_name, input_source in variable.inputs.items():
496
+ inputs_qual_names.append(make_qual_name(input_source, input_name))
497
+ collect(make_qual_name(input_source, input_name))
498
+ if isinstance(variable_source, External):
499
+ derivation.append(f"'{variable_name}' comes from external source '{source_name}'\n")
500
+ else:
501
+ derivation.append(
502
+ f"""'{qual_name_}' depends on {tuple(sorted(inputs_qual_names))} via \n\n{variable.metadata}"""
503
+ )
504
+
505
+ collect(qual_name)
506
+ derivation = reversed(derivation) if direct else derivation
507
+ derivation_txt = "\n" + "\n".join(derivation)
508
+ return derivation_txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numbox
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Author: Mikhail Goykhman
5
5
  License: MIT License (with Citation Clause)
6
6
 
@@ -1,4 +1,4 @@
1
- numbox/__init__.py,sha256=bqu4G7Is-8Were-hMi-SthEalL7QW-PAbKWGZ9pVB6U,22
1
+ numbox/__init__.py,sha256=Yyz9rKJSsP7kA7oh3l-XTxOFzEuuSR9Ly7oYXA1hfFM,22
2
2
  numbox/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  numbox/core/configurations.py,sha256=0bCmxXL-QMwtvyIDhpXLeT-1KJMf_QpH0wLuEvYLGxQ,68
4
4
  numbox/core/any/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -15,7 +15,7 @@ numbox/core/bindings/utils.py,sha256=OATfF4k8e5oPa9_wlHHkLQhY_DhrPNKYdeeGu9Nj5yg
15
15
  numbox/core/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  numbox/core/proxy/proxy.py,sha256=Wt7yzswDmeQXt0yjcTcnLi2coneowSHWXy_IFpZZJMU,3612
17
17
  numbox/core/variable/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- numbox/core/variable/variable.py,sha256=Y1nB9wqt91BkWCKRyQNe1jFR0aic57HNnylTGp9h1nI,16518
18
+ numbox/core/variable/variable.py,sha256=vmkecU_1_OUJov1cg0bT3fU7zxu1L7cGMBWD3_0cvQM,19621
19
19
  numbox/core/work/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  numbox/core/work/builder.py,sha256=d0DRwJoyskp-6tYQyV1VE-v9eX99qJbQJ_FdAFrjuiE,6273
21
21
  numbox/core/work/builder_utils.py,sha256=z8au1x10AwnzQ0_MAbQ6DnKTp3u9HeYZ1jyfkUMYjVg,1213
@@ -35,8 +35,8 @@ numbox/utils/meminfo.py,sha256=ykFi8Vt0WcHI3ztgMwvpn6NqaflDSQGL8tjI01jrzm0,1759
35
35
  numbox/utils/standard.py,sha256=SPsQcyLZw21RaNCdfkIGE_QBaVnMtZjJY4F40_GGuak,347
36
36
  numbox/utils/timer.py,sha256=5_d690Fb3L2axJBRxtoB0qe23exBosNR4qu6cno4QfY,641
37
37
  numbox/utils/void_type.py,sha256=IkZsjNeAIShYJtvWbvERdHnl_mbF1rCRWiM3gp6II8U,404
38
- numbox-0.3.2.dist-info/LICENSE,sha256=ZlmjEDrZFNHpGxy_7HThWRlyBvuOMchK__qgTdiO_Uk,1446
39
- numbox-0.3.2.dist-info/METADATA,sha256=j7WYDDW5WT6aBnyXtsqpwKPXWKGYQq9Vu8_NNyC44q4,2996
40
- numbox-0.3.2.dist-info/WHEEL,sha256=WnJ8fYhv8N4SYVK2lLYNI6N0kVATA7b0piVUNvqIIJE,91
41
- numbox-0.3.2.dist-info/top_level.txt,sha256=A67jOkfqidCSYYm6ifjN_WZyIiR1B27fjxv6nNbPvjc,7
42
- numbox-0.3.2.dist-info/RECORD,,
38
+ numbox-0.3.4.dist-info/LICENSE,sha256=ZlmjEDrZFNHpGxy_7HThWRlyBvuOMchK__qgTdiO_Uk,1446
39
+ numbox-0.3.4.dist-info/METADATA,sha256=E3a0zMbjM0vjbO2DVN9zaZOuW1D09A20spTbIZK2imM,2996
40
+ numbox-0.3.4.dist-info/WHEEL,sha256=WnJ8fYhv8N4SYVK2lLYNI6N0kVATA7b0piVUNvqIIJE,91
41
+ numbox-0.3.4.dist-info/top_level.txt,sha256=A67jOkfqidCSYYm6ifjN_WZyIiR1B27fjxv6nNbPvjc,7
42
+ numbox-0.3.4.dist-info/RECORD,,
File without changes