haiway 0.10.1__py3-none-any.whl → 0.10.11__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,
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Mapping, Sequence, Set
2
+ from typing import overload
2
3
 
3
4
  __all__ = [
4
5
  "as_dict",
@@ -8,24 +9,43 @@ __all__ = [
8
9
  ]
9
10
 
10
11
 
12
+ @overload
11
13
  def as_list[T](
12
14
  collection: Sequence[T],
13
15
  /,
14
- ) -> list[T]:
16
+ ) -> list[T]: ...
17
+
18
+
19
+ @overload
20
+ def as_list[T](
21
+ collection: Sequence[T] | None,
22
+ /,
23
+ ) -> list[T] | None: ...
24
+
25
+
26
+ def as_list[T](
27
+ collection: Sequence[T] | None,
28
+ /,
29
+ ) -> list[T] | None:
15
30
  """
16
31
  Converts any given Sequence into a list.
17
32
 
18
33
  Parameters
19
34
  ----------
20
- collection : Sequence[T]
35
+ collection : Sequence[T] | None
21
36
  The input collection to be converted.
22
37
 
23
38
  Returns
24
39
  -------
25
- list[T]
26
- A new list containing all elements of the input collection,
27
- or the original list if it was already one.
40
+ list[T] | None
41
+ A new list containing all elements of the input collection,\
42
+ or the original list if it was already one.
43
+ None if no value was provided.
28
44
  """
45
+
46
+ if collection is None:
47
+ return None
48
+
29
49
  if isinstance(collection, list):
30
50
  return collection
31
51
 
@@ -33,24 +53,43 @@ def as_list[T](
33
53
  return list(collection)
34
54
 
35
55
 
56
+ @overload
36
57
  def as_tuple[T](
37
58
  collection: Sequence[T],
38
59
  /,
39
- ) -> tuple[T, ...]:
60
+ ) -> tuple[T, ...]: ...
61
+
62
+
63
+ @overload
64
+ def as_tuple[T](
65
+ collection: Sequence[T] | None,
66
+ /,
67
+ ) -> tuple[T, ...] | None: ...
68
+
69
+
70
+ def as_tuple[T](
71
+ collection: Sequence[T] | None,
72
+ /,
73
+ ) -> tuple[T, ...] | None:
40
74
  """
41
75
  Converts any given Sequence into a tuple.
42
76
 
43
77
  Parameters
44
78
  ----------
45
- collection : Sequence[T]
79
+ collection : Sequence[T] | None
46
80
  The input collection to be converted.
47
81
 
48
82
  Returns
49
83
  -------
50
- tuple[T]
51
- A new tuple containing all elements of the input collection,
52
- or the original tuple if it was already one.
84
+ tuple[T] | None
85
+ A new tuple containing all elements of the input collection,\
86
+ or the original tuple if it was already one.
87
+ None if no value was provided.
53
88
  """
89
+
90
+ if collection is None:
91
+ return None
92
+
54
93
  if isinstance(collection, tuple):
55
94
  return collection
56
95
 
@@ -58,10 +97,24 @@ def as_tuple[T](
58
97
  return tuple(collection)
59
98
 
60
99
 
100
+ @overload
61
101
  def as_set[T](
62
102
  collection: Set[T],
63
103
  /,
64
- ) -> set[T]:
104
+ ) -> set[T]: ...
105
+
106
+
107
+ @overload
108
+ def as_set[T](
109
+ collection: Set[T] | None,
110
+ /,
111
+ ) -> set[T] | None: ...
112
+
113
+
114
+ def as_set[T](
115
+ collection: Set[T] | None,
116
+ /,
117
+ ) -> set[T] | None:
65
118
  """
66
119
  Converts any given Set into a set.
67
120
 
@@ -73,9 +126,14 @@ def as_set[T](
73
126
  Returns
74
127
  -------
75
128
  set[T]
76
- A new set containing all elements of the input collection,
77
- or the original set if it was already one.
129
+ A new set containing all elements of the input collection,\
130
+ or the original set if it was already one.
131
+ None if no value was provided.
78
132
  """
133
+
134
+ if collection is None:
135
+ return None
136
+
79
137
  if isinstance(collection, set):
80
138
  return collection
81
139
 
@@ -83,10 +141,24 @@ def as_set[T](
83
141
  return set(collection)
84
142
 
85
143
 
144
+ @overload
86
145
  def as_dict[K, V](
87
146
  collection: Mapping[K, V],
88
147
  /,
89
- ) -> dict[K, V]:
148
+ ) -> dict[K, V]: ...
149
+
150
+
151
+ @overload
152
+ def as_dict[K, V](
153
+ collection: Mapping[K, V] | None,
154
+ /,
155
+ ) -> dict[K, V] | None: ...
156
+
157
+
158
+ def as_dict[K, V](
159
+ collection: Mapping[K, V] | None,
160
+ /,
161
+ ) -> dict[K, V] | None:
90
162
  """
91
163
  Converts any given Mapping into a dict.
92
164
 
@@ -98,9 +170,14 @@ def as_dict[K, V](
98
170
  Returns
99
171
  -------
100
172
  dict[K, V]
101
- A new dict containing all elements of the input collection,
102
- or the original dict if it was already one.
173
+ A new dict containing all elements of the input collection,\
174
+ or the original dict if it was already one.
175
+ None if no value was provided.
103
176
  """
177
+
178
+ if collection is None:
179
+ return None
180
+
104
181
  if isinstance(collection, dict):
105
182
  return collection
106
183
 
@@ -1,7 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: haiway
3
- Version: 0.10.1
3
+ Version: 0.10.11
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,7 +12,7 @@ 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
@@ -29,15 +29,14 @@ haiway/types/frozen.py,sha256=CZhFCXnWAKEhuWSfILxA8smfdpMd5Ku694ycfLh98R8,76
29
29
  haiway/types/missing.py,sha256=rDnyA2wxPkTbJl0L-zbo0owp7IJ04xkCIp6xD6wh8NI,1712
30
30
  haiway/utils/__init__.py,sha256=O7qmAmUktX-X_5D1L5FJMeCFEiOVrrnyYSyiycm4nyg,739
31
31
  haiway/utils/always.py,sha256=2abp8Lm9rQkrfS3rm1Iqhb-IcWyVfH1BULab3KMxgOw,1234
32
- haiway/utils/collections.py,sha256=_MwB4o8sCZeAjrwry8Pu9JvGlt8m7O7MqWSFFmPjJ6k,2120
32
+ haiway/utils/collections.py,sha256=pKHZhXqTMcOth7gV6mXcC5WcSyBl70MmVIELbDSmMoA,3320
33
33
  haiway/utils/env.py,sha256=vlW21LEp8uOVNnUXpBfPtj3zKi9Kkjoemb_H5hQpYPQ,4433
34
34
  haiway/utils/freezing.py,sha256=K34ZIMzbkpgkHKH-KF73plEbXExsajNRkRTYp9nJEf4,620
35
35
  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.1.dist-info/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
40
- haiway-0.10.1.dist-info/METADATA,sha256=0m2_QdvBq_XiPN3GiIYFIE0cXf8cVw5esT8kA6_K_5Y,3895
41
- haiway-0.10.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
42
- haiway-0.10.1.dist-info/top_level.txt,sha256=_LdXVLzUzgkvAGQnQJj5kQfoFhpPW6EF4Kj9NapniLg,7
43
- haiway-0.10.1.dist-info/RECORD,,
39
+ haiway-0.10.11.dist-info/METADATA,sha256=Xz9EVwzVcqiYBNaDv0QZQ4gXv3482M9eV9n82ba6kJo,3858
40
+ haiway-0.10.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
+ haiway-0.10.11.dist-info/licenses/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
42
+ haiway-0.10.11.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