haiway 0.10.0__py3-none-any.whl → 0.10.10__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.
haiway/context/access.py CHANGED
@@ -195,6 +195,14 @@ class ScopeContext:
195
195
 
196
196
  @final
197
197
  class ctx:
198
+ @staticmethod
199
+ def trace_id() -> str:
200
+ """
201
+ Get the current context trace identifier.
202
+ """
203
+
204
+ return ScopeIdentifier.current_trace_id()
205
+
198
206
  @staticmethod
199
207
  def scope(
200
208
  label: str,
@@ -1,6 +1,6 @@
1
1
  from contextvars import ContextVar, Token
2
2
  from types import TracebackType
3
- from typing import Self, final
3
+ from typing import Any, Self, final
4
4
  from uuid import uuid4
5
5
 
6
6
  __all__ = [
@@ -12,6 +12,14 @@ __all__ = [
12
12
  class ScopeIdentifier:
13
13
  _context = ContextVar[Self]("ScopeIdentifier")
14
14
 
15
+ @classmethod
16
+ def current_trace_id(cls) -> str:
17
+ try:
18
+ return ScopeIdentifier._context.get().trace_id
19
+
20
+ except LookupError as exc:
21
+ raise RuntimeError("Attempting to access scope identifier outside of scope") from exc
22
+
15
23
  @classmethod
16
24
  def scope(
17
25
  cls,
@@ -60,6 +68,15 @@ class ScopeIdentifier:
60
68
  def __str__(self) -> str:
61
69
  return self.unique_name
62
70
 
71
+ def __eq__(self, other: Any) -> bool:
72
+ if not isinstance(other, self.__class__):
73
+ return False
74
+
75
+ return self.scope_id == other.scope_id and self.trace_id == other.trace_id
76
+
77
+ def __hash__(self) -> int:
78
+ return hash(self.scope_id)
79
+
63
80
  def __enter__(self) -> None:
64
81
  assert not hasattr(self, "_token"), "Context reentrance is not allowed" # nosec: B101
65
82
  self._token: Token[ScopeIdentifier] = ScopeIdentifier._context.set(self)
haiway/helpers/metrics.py CHANGED
@@ -101,6 +101,7 @@ class MetricsHolder:
101
101
  )
102
102
 
103
103
  def __init__(self) -> None:
104
+ self.root_scope: ScopeIdentifier | None = None
104
105
  self.scopes: dict[ScopeIdentifier, MetricsScopeStore] = {}
105
106
 
106
107
  def record(
@@ -109,7 +110,9 @@ class MetricsHolder:
109
110
  /,
110
111
  metric: State,
111
112
  ) -> None:
113
+ assert self.root_scope is not None # nosec: B101
112
114
  assert scope in self.scopes # nosec: B101
115
+
113
116
  metric_type: type[State] = type(metric)
114
117
  metrics: dict[type[State], State] = self.scopes[scope].metrics
115
118
  if (current := metrics.get(metric_type)) and hasattr(current, "__add__"):
@@ -125,6 +128,9 @@ class MetricsHolder:
125
128
  metric: type[Metric],
126
129
  merged: bool,
127
130
  ) -> Metric | None:
131
+ assert self.root_scope is not None # nosec: B101
132
+ assert scope in self.scopes # nosec: B101
133
+
128
134
  if merged:
129
135
  return self.scopes[scope].merged(metric)
130
136
 
@@ -139,7 +145,11 @@ class MetricsHolder:
139
145
  assert scope not in self.scopes # nosec: B101
140
146
  scope_metrics = MetricsScopeStore(scope)
141
147
  self.scopes[scope] = scope_metrics
142
- if not scope.is_root: # root scopes have no actual parent
148
+
149
+ if self.root_scope is None:
150
+ self.root_scope = scope
151
+
152
+ else:
143
153
  for key in self.scopes.keys():
144
154
  if key.scope_id == scope.parent_id:
145
155
  self.scopes[key].nested.append(scope_metrics)
@@ -182,9 +192,10 @@ class MetricsLogger:
182
192
  items_limit: int | None,
183
193
  redact_content: bool,
184
194
  ) -> None:
195
+ self.root_scope: ScopeIdentifier | None = None
196
+ self.scopes: dict[ScopeIdentifier, MetricsScopeStore] = {}
185
197
  self.items_limit: int | None = items_limit
186
198
  self.redact_content: bool = redact_content
187
- self.scopes: dict[ScopeIdentifier, MetricsScopeStore] = {}
188
199
 
189
200
  def record(
190
201
  self,
@@ -192,7 +203,9 @@ class MetricsLogger:
192
203
  /,
193
204
  metric: State,
194
205
  ) -> None:
206
+ assert self.root_scope is not None # nosec: B101
195
207
  assert scope in self.scopes # nosec: B101
208
+
196
209
  metric_type: type[State] = type(metric)
197
210
  metrics: dict[type[State], State] = self.scopes[scope].metrics
198
211
  if (current := metrics.get(metric_type)) and hasattr(current, "__add__"):
@@ -214,6 +227,9 @@ class MetricsLogger:
214
227
  metric: type[Metric],
215
228
  merged: bool,
216
229
  ) -> Metric | None:
230
+ assert self.root_scope is not None # nosec: B101
231
+ assert scope in self.scopes # nosec: B101
232
+
217
233
  if merged:
218
234
  return self.scopes[scope].merged(metric)
219
235
 
@@ -228,7 +244,11 @@ class MetricsLogger:
228
244
  assert scope not in self.scopes # nosec: B101
229
245
  scope_metrics = MetricsScopeStore(scope)
230
246
  self.scopes[scope] = scope_metrics
231
- if not scope.is_root: # root scopes have no actual parent
247
+
248
+ if self.root_scope is None:
249
+ self.root_scope = scope
250
+
251
+ else:
232
252
  for key in self.scopes.keys():
233
253
  if key.scope_id == scope.parent_id:
234
254
  self.scopes[key].nested.append(scope_metrics)
@@ -246,7 +266,7 @@ class MetricsLogger:
246
266
  assert scope in self.scopes # nosec: B101
247
267
  self.scopes[scope].exited = monotonic()
248
268
 
249
- if scope.is_root and self.scopes[scope].finished:
269
+ if scope == self.root_scope and self.scopes[scope].finished:
250
270
  if log := _tree_log(
251
271
  self.scopes[scope],
252
272
  list_items_limit=self.items_limit,
@@ -509,7 +509,10 @@ def _resolve_type_typeddict(
509
509
  self_annotation=resolved_attribute,
510
510
  recursion_guard=recursion_guard,
511
511
  ).update_required(key in annotation.__required_keys__)
512
- resolved_attribute.extra = attributes
512
+ resolved_attribute.extra = {
513
+ "attributes": attributes,
514
+ "required": annotation.__required_keys__,
515
+ }
513
516
  return resolved_attribute
514
517
 
515
518
 
@@ -86,6 +86,12 @@ class AttributeValidator[Type]:
86
86
  assert self.validation is not MISSING # nosec: B101
87
87
  return self.validation(value) # pyright: ignore[reportCallIssue, reportUnknownVariableType]
88
88
 
89
+ def __str__(self) -> str:
90
+ return f"Validator[{self.annotation}]"
91
+
92
+ def __repr__(self) -> str:
93
+ return f"Validator[{self.annotation}]"
94
+
89
95
 
90
96
  def _prepare_validator_of_any(
91
97
  annotation: AttributeAnnotation,
@@ -391,11 +397,12 @@ def _prepare_validator_of_typed_dict(
391
397
  case _:
392
398
  raise TypeError(f"'{value}' is not matching expected type of 'str'")
393
399
 
400
+ formatted_type: str = str(annotation)
394
401
  values_validators: dict[str, AttributeValidation[Any]] = {
395
402
  key: AttributeValidator.of(element, recursion_guard=recursion_guard)
396
- for key, element in annotation.extra.items()
403
+ for key, element in annotation.extra["attributes"].items()
397
404
  }
398
- formatted_type: str = str(annotation)
405
+ required_values: Set[str] = annotation.extra["required"]
399
406
 
400
407
  def validator(
401
408
  value: Any,
@@ -406,8 +413,10 @@ def _prepare_validator_of_typed_dict(
406
413
  validated: MutableMapping[str, Any] = {}
407
414
  for key, validator in values_validators.items():
408
415
  element: Any = elements.get(key, MISSING)
409
- if element is not MISSING:
410
- validated[key_validator(key)] = validator(element)
416
+ if element is MISSING and key not in required_values:
417
+ continue # skip missing and not required
418
+
419
+ validated[key_validator(key)] = validator(element)
411
420
 
412
421
  # TODO: make sure dict is not mutable with MappingProxyType?
413
422
  return validated
@@ -1,7 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: haiway
3
- Version: 0.10.0
3
+ Version: 0.10.10
4
4
  Summary: Framework for dependency injection and state management within structured concurrency model.
5
+ Project-URL: Homepage, https://miquido.com
6
+ Project-URL: Repository, https://github.com/miquido/haiway.git
5
7
  Maintainer-email: Kacper Kaliński <kacper.kalinski@miquido.com>
6
8
  License: MIT License
7
9
 
@@ -24,25 +26,22 @@ License: MIT License
24
26
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
27
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
28
  SOFTWARE.
27
- Project-URL: Homepage, https://miquido.com
28
- Project-URL: Repository, https://github.com/miquido/haiway.git
29
- Classifier: License :: OSI Approved :: MIT License
29
+ License-File: LICENSE
30
30
  Classifier: Intended Audience :: Developers
31
+ Classifier: License :: OSI Approved :: MIT License
31
32
  Classifier: Programming Language :: Python
32
- Classifier: Typing :: Typed
33
33
  Classifier: Topic :: Software Development
34
34
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
35
+ Classifier: Typing :: Typed
35
36
  Requires-Python: >=3.12
36
- Description-Content-Type: text/markdown
37
- License-File: LICENSE
38
37
  Provides-Extra: dev
39
- Requires-Dist: haiway; extra == "dev"
40
- Requires-Dist: ruff~=0.9; extra == "dev"
41
- Requires-Dist: pyright~=1.1; extra == "dev"
42
- Requires-Dist: bandit~=1.7; extra == "dev"
43
- Requires-Dist: pytest~=7.4; extra == "dev"
44
- Requires-Dist: pytest-cov~=4.1; extra == "dev"
45
- Requires-Dist: pytest-asyncio~=0.23; extra == "dev"
38
+ Requires-Dist: bandit~=1.7; extra == 'dev'
39
+ Requires-Dist: pyright~=1.1; extra == 'dev'
40
+ Requires-Dist: pytest-asyncio~=0.23; extra == 'dev'
41
+ Requires-Dist: pytest-cov~=4.1; extra == 'dev'
42
+ Requires-Dist: pytest~=7.4; extra == 'dev'
43
+ Requires-Dist: ruff~=0.9; extra == 'dev'
44
+ Description-Content-Type: text/markdown
46
45
 
47
46
  # 🚗 haiway 🚕 🚚 🚙
48
47
 
@@ -1,9 +1,9 @@
1
1
  haiway/__init__.py,sha256=IEUCyFYKT5IPHnkiUvDVZHdJeHqCaBnG8FhPD20Zgo8,1929
2
2
  haiway/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  haiway/context/__init__.py,sha256=eRvuhifx7xCd-_6desgk55idzNpD5S5sprmCfGb3_9M,662
4
- haiway/context/access.py,sha256=RpTvs7107NoAxCgxxbp7BUThOM9gLXo_Vxv-JgDqW58,16954
4
+ haiway/context/access.py,sha256=Fc2NfoIV2zWE5qPnNgSitK1dy-PME-rJJfUPhoNwJzY,17125
5
5
  haiway/context/disposables.py,sha256=DZjnMp-wMfF-em2Wjhbm1MvXubNpuzFBT70BQNIxC7M,2019
6
- haiway/context/identifier.py,sha256=f5-vZOnhKZmYj__emS51WcgsJqfD_yz6ilr3GUo9z1k,2034
6
+ haiway/context/identifier.py,sha256=Fyb6OHx5FPaSLLRK249HUEr_KlBSG5F-eW01Oxg_Ke8,2570
7
7
  haiway/context/logging.py,sha256=ptwgENuyw-WFgokVsYx9OXZGhJENuO_wgfVjcBryUKM,4251
8
8
  haiway/context/metrics.py,sha256=Ve628X39u0rdLm0vYmVZt7aGeoEeRquR6f67vJIXClY,4213
9
9
  haiway/context/state.py,sha256=LCcFxXqDBu6prvPyPicN-ecONSNHyR56PfQ5u5jNFCU,3000
@@ -12,17 +12,17 @@ haiway/context/types.py,sha256=VvJA7wAPZ3ISpgyThVguioYUXqhHf0XkPfRd0M1ERiQ,142
12
12
  haiway/helpers/__init__.py,sha256=8XRJWNhidWuBKqRZ1Hyc2xqt7DeWLcoOs2V-oexl8VY,579
13
13
  haiway/helpers/asynchrony.py,sha256=9lo9wT3G0TyPb4vfmTnWGBvB_eN6p6nIlj46_9Ag8fQ,6022
14
14
  haiway/helpers/caching.py,sha256=Ok_WE5Whe7XqnIuLZo4rNNBFeWap-aUWX799s4b1JAQ,9536
15
- haiway/helpers/metrics.py,sha256=0CDDEM3QZL35Wc3GteNoAeMuLDehJX06ukxLpNediq0,12813
15
+ haiway/helpers/metrics.py,sha256=0oFBiO-hAzihyC5jvXevNrYOoTcUGc2yGhE1A_866Mc,13314
16
16
  haiway/helpers/retries.py,sha256=gIkyUlqJLDYaxIZd3qzeqGFY9y5Gp8dgZLlZ6hs8hoc,7538
17
17
  haiway/helpers/throttling.py,sha256=zo0OwFq64si5KUwhd58cFHLmGAmYwRbFRJMbv9suhPs,3844
18
18
  haiway/helpers/timeouted.py,sha256=1xU09hQnFdj6p48BwZl5xUvtIr3zC0ZUXehkdrduCjs,3074
19
19
  haiway/helpers/tracing.py,sha256=eQpkIoGSB51jRF8RcLaihvHX3VzJIRdyRxTx3I14Pzg,3346
20
20
  haiway/state/__init__.py,sha256=emTuwGFn7HyjyTJ_ass69J5jQIA7_WHO4teZz_dR05Y,355
21
- haiway/state/attributes.py,sha256=iQ7TJHnT3hlcYwKcxchXE56zU8WbOTJZhsVn_HocXBc,22903
21
+ haiway/state/attributes.py,sha256=0gJbgOKiH79RNM9IW66NNWSWM29037yGTTpoln6vPvU,22984
22
22
  haiway/state/path.py,sha256=4vh-fYQv8_xRWjS0ErMQslKDWRI6-KVECAr8JhYk0UY,17503
23
23
  haiway/state/requirement.py,sha256=3iQqzp5Q7w6y5uClamJGH7S5Hib9pciuTAV27PP5lS8,6161
24
24
  haiway/state/structure.py,sha256=bSIj0S_HG-F1Z5GxSlY6VpGtrtiwG82-AIL_PL1lRLo,12465
25
- haiway/state/validation.py,sha256=hbvzPVBD36AD_B5u9ER4A11vjaXR9LJLD8G2k8IE-AI,13492
25
+ haiway/state/validation.py,sha256=r0EMIs-nvoXsmSA74oGu6Lrbw8lkzmseaY82_-E8ous,13814
26
26
  haiway/types/__init__.py,sha256=-j4uDN6ix3GBXLBqXC-k_QOJSDlO6zvNCxDej8vVzek,342
27
27
  haiway/types/default.py,sha256=IVQsNzDnfukL3-XlScYv2PgTcJ1x_BNP9i5UlS5oEbg,2179
28
28
  haiway/types/frozen.py,sha256=CZhFCXnWAKEhuWSfILxA8smfdpMd5Ku694ycfLh98R8,76
@@ -36,8 +36,7 @@ haiway/utils/logs.py,sha256=oDsc1ZdqKDjlTlctLbDcp9iX98Acr-1tdw-Pyg3DElo,1577
36
36
  haiway/utils/mimic.py,sha256=BkVjTVP2TxxC8GChPGyDV6UXVwJmiRiSWeOYZNZFHxs,1828
37
37
  haiway/utils/noop.py,sha256=qgbZlOKWY6_23Zs43OLukK2HagIQKRyR04zrFVm5rWI,344
38
38
  haiway/utils/queue.py,sha256=oQ3GXCJ-PGNtMEr6EPdgqAvYZoj8lAa7Z2drBKBEoBM,2345
39
- haiway-0.10.0.dist-info/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
40
- haiway-0.10.0.dist-info/METADATA,sha256=6fSYOr9NesJPftFBAIfoOGH_OsX5cIqCy8DY4N4qswM,3895
41
- haiway-0.10.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
42
- haiway-0.10.0.dist-info/top_level.txt,sha256=_LdXVLzUzgkvAGQnQJj5kQfoFhpPW6EF4Kj9NapniLg,7
43
- haiway-0.10.0.dist-info/RECORD,,
39
+ haiway-0.10.10.dist-info/METADATA,sha256=PhcebR048J2VPSVrFIQOnQSEIvQpwolFKMmRuOGTWAg,3858
40
+ haiway-0.10.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
+ haiway-0.10.10.dist-info/licenses/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
42
+ haiway-0.10.10.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1 +0,0 @@
1
- haiway