haiway 0.18.2__py3-none-any.whl → 0.19.0__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/__init__.py CHANGED
@@ -20,7 +20,6 @@ from haiway.context import (
20
20
  )
21
21
  from haiway.helpers import (
22
22
  LoggerObservability,
23
- ResultTrace,
24
23
  asynchronous,
25
24
  cache,
26
25
  retry,
@@ -87,7 +86,6 @@ __all__ = (
87
86
  "ObservabilityMetricRecording",
88
87
  "ObservabilityScopeEntering",
89
88
  "ObservabilityScopeExiting",
90
- "ResultTrace",
91
89
  "ScopeContext",
92
90
  "ScopeIdentifier",
93
91
  "State",
haiway/context/access.py CHANGED
@@ -11,6 +11,7 @@ from collections.abc import (
11
11
  Callable,
12
12
  Coroutine,
13
13
  Iterable,
14
+ Mapping,
14
15
  )
15
16
  from logging import Logger
16
17
  from types import TracebackType
@@ -597,81 +598,69 @@ class ctx:
597
598
  ObservabilityLevel.DEBUG, message, *args, exception=exception, **extra
598
599
  )
599
600
 
601
+ @overload
600
602
  @staticmethod
601
- def event(
602
- event: State,
603
+ def record(
604
+ level: ObservabilityLevel = ObservabilityLevel.DEBUG,
603
605
  /,
604
606
  *,
605
- level: ObservabilityLevel = ObservabilityLevel.INFO,
606
- **extra: Any,
607
- ) -> None:
608
- """
609
- Record event within current scope context.
610
-
611
- Parameters
612
- ----------
613
- event: State
614
- contents of event to be recorded.
607
+ attributes: Mapping[str, ObservabilityAttribute],
608
+ ) -> None: ...
615
609
 
616
- Returns
617
- -------
618
- None
619
- """
620
-
621
- ObservabilityContext.record_event(
622
- event,
623
- level=level,
624
- **extra,
625
- )
610
+ @overload
611
+ @staticmethod
612
+ def record(
613
+ level: ObservabilityLevel = ObservabilityLevel.DEBUG,
614
+ /,
615
+ *,
616
+ event: str,
617
+ attributes: Mapping[str, ObservabilityAttribute] | None = None,
618
+ ) -> None: ...
626
619
 
620
+ @overload
627
621
  @staticmethod
628
- def metric(
629
- metric: str,
622
+ def record(
623
+ level: ObservabilityLevel = ObservabilityLevel.DEBUG,
630
624
  /,
631
625
  *,
626
+ metric: str,
632
627
  value: float | int,
633
628
  unit: str | None = None,
634
- **extra: Any,
635
- ) -> None:
636
- """
637
- Record metric within current scope context.
638
-
639
- Parameters
640
- ----------
641
- metric: State
642
- name of metric to be recorded.
643
- value: float | int
644
- value of metric to be recorded.
645
- unit: str | None = None
646
- unit of metric to be recorded.
647
-
648
- Returns
649
- -------
650
- None
651
- """
652
-
653
- ObservabilityContext.record_metric(
654
- metric,
655
- value=value,
656
- unit=unit,
657
- **extra,
658
- )
629
+ attributes: Mapping[str, ObservabilityAttribute] | None = None,
630
+ ) -> None: ...
659
631
 
660
632
  @staticmethod
661
- def attributes(**attributes: ObservabilityAttribute) -> None:
662
- """
663
- Record attributes within current scope context.
664
-
665
- Parameters
666
- ----------
667
- **attributes: ObservabilityAttribute,
668
- attributes to be recorded within current context.
633
+ def record(
634
+ level: ObservabilityLevel = ObservabilityLevel.DEBUG,
635
+ /,
636
+ *,
637
+ event: str | None = None,
638
+ metric: str | None = None,
639
+ value: float | int | None = None,
640
+ unit: str | None = None,
641
+ attributes: Mapping[str, ObservabilityAttribute] | None = None,
642
+ ) -> None:
643
+ if event is not None:
644
+ assert metric is None # nosec: B101
645
+ ObservabilityContext.record_event(
646
+ level,
647
+ event,
648
+ attributes=attributes or {},
649
+ )
669
650
 
670
- Returns
671
- -------
672
- None
673
- """
651
+ elif metric is not None:
652
+ assert event is None # nosec: B101
653
+ assert value is not None # nosec: B101
654
+ ObservabilityContext.record_metric(
655
+ level,
656
+ metric,
657
+ value=value,
658
+ unit=unit,
659
+ attributes=attributes or {},
660
+ )
674
661
 
675
- ObservabilityContext.record_attributes(
676
- **attributes,
677
- )
662
+ else:
663
+ ObservabilityContext.record_attributes(
664
+ level,
665
+ attributes=attributes or {},
666
+ )
@@ -1,4 +1,4 @@
1
- from collections.abc import Sequence
1
+ from collections.abc import Mapping, Sequence
2
2
  from contextvars import ContextVar, Token
3
3
  from enum import IntEnum
4
4
  from logging import DEBUG as DEBUG_LOGGING
@@ -11,7 +11,8 @@ from typing import Any, Final, Protocol, Self, final, runtime_checkable
11
11
 
12
12
  from haiway.context.identifier import ScopeIdentifier
13
13
  from haiway.state import State
14
- from haiway.types import MISSING, Missing
14
+ from haiway.types import Missing
15
+ from haiway.utils.formatting import format_str
15
16
 
16
17
  __all__ = (
17
18
  "DEBUG",
@@ -68,7 +69,6 @@ class ObservabilityLogRecording(Protocol):
68
69
  message: str,
69
70
  *args: Any,
70
71
  exception: BaseException | None,
71
- **extra: Any,
72
72
  ) -> None: ...
73
73
 
74
74
 
@@ -78,10 +78,10 @@ class ObservabilityEventRecording(Protocol):
78
78
  self,
79
79
  scope: ScopeIdentifier,
80
80
  /,
81
- *,
82
81
  level: ObservabilityLevel,
83
- event: State,
84
- **extra: Any,
82
+ *,
83
+ event: str,
84
+ attributes: Mapping[str, ObservabilityAttribute],
85
85
  ) -> None: ...
86
86
 
87
87
 
@@ -91,11 +91,12 @@ class ObservabilityMetricRecording(Protocol):
91
91
  self,
92
92
  scope: ScopeIdentifier,
93
93
  /,
94
+ level: ObservabilityLevel,
94
95
  *,
95
96
  metric: str,
96
97
  value: float | int,
97
98
  unit: str | None,
98
- **extra: Any,
99
+ attributes: Mapping[str, ObservabilityAttribute],
99
100
  ) -> None: ...
100
101
 
101
102
 
@@ -105,7 +106,8 @@ class ObservabilityAttributesRecording(Protocol):
105
106
  self,
106
107
  scope: ScopeIdentifier,
107
108
  /,
108
- **attributes: ObservabilityAttribute,
109
+ level: ObservabilityLevel,
110
+ attributes: Mapping[str, ObservabilityAttribute],
109
111
  ) -> None: ...
110
112
 
111
113
 
@@ -217,7 +219,6 @@ def _logger_observability(
217
219
  message: str,
218
220
  *args: Any,
219
221
  exception: BaseException | None,
220
- **extra: Any,
221
222
  ) -> None:
222
223
  logger.log(
223
224
  level,
@@ -229,42 +230,51 @@ def _logger_observability(
229
230
  def event_recording(
230
231
  scope: ScopeIdentifier,
231
232
  /,
232
- *,
233
233
  level: ObservabilityLevel,
234
- event: State,
235
- **extra: Any,
234
+ *,
235
+ event: str,
236
+ attributes: Mapping[str, ObservabilityAttribute],
236
237
  ) -> None:
237
238
  logger.log(
238
239
  level,
239
- f"{scope.unique_name} Recorded event:\n{event.to_str(pretty=True)}",
240
+ f"{scope.unique_name} Recorded event: {event} {format_str(attributes)}",
240
241
  )
241
242
 
242
243
  def metric_recording(
243
244
  scope: ScopeIdentifier,
244
245
  /,
246
+ level: ObservabilityLevel,
245
247
  *,
246
248
  metric: str,
247
249
  value: float | int,
248
250
  unit: str | None,
249
- **extra: Any,
251
+ attributes: Mapping[str, ObservabilityAttribute],
250
252
  ) -> None:
251
- logger.log(
252
- INFO,
253
- f"{scope.unique_name} Recorded metric: {metric}={value}{unit or ''}",
254
- )
253
+ if attributes:
254
+ logger.log(
255
+ level,
256
+ f"{scope.unique_name} Recorded metric: {metric}={value}{unit or ''}"
257
+ f"\n{format_str(attributes)}",
258
+ )
259
+
260
+ else:
261
+ logger.log(
262
+ level,
263
+ f"{scope.unique_name} Recorded metric: {metric}={value}{unit or ''}",
264
+ )
255
265
 
256
266
  def attributes_recording(
257
267
  scope: ScopeIdentifier,
258
268
  /,
259
- **attributes: ObservabilityAttribute,
269
+ level: ObservabilityLevel,
270
+ attributes: Mapping[str, ObservabilityAttribute],
260
271
  ) -> None:
261
272
  if not attributes:
262
273
  return
263
274
 
264
275
  logger.log(
265
- INFO,
266
- f"{scope.unique_name} Recorded attributes:"
267
- f"\n{'\n'.join(f'{k}: {v}' for k, v in attributes.items() if v is not None and v is not MISSING)}", # noqa: E501
276
+ level,
277
+ f"{scope.unique_name} Recorded attributes: {format_str(attributes)}",
268
278
  )
269
279
 
270
280
  def scope_entering[Metric: State](
@@ -357,7 +367,6 @@ class ObservabilityContext:
357
367
  /,
358
368
  *args: Any,
359
369
  exception: BaseException | None,
360
- **extra: Any,
361
370
  ) -> None:
362
371
  try: # catch exceptions - we don't wan't to blow up on observability
363
372
  context: Self = cls._context.get()
@@ -369,7 +378,6 @@ class ObservabilityContext:
369
378
  message,
370
379
  *args,
371
380
  exception=exception,
372
- **extra,
373
381
  )
374
382
 
375
383
  except LookupError:
@@ -383,11 +391,11 @@ class ObservabilityContext:
383
391
  @classmethod
384
392
  def record_event(
385
393
  cls,
386
- event: State,
394
+ level: ObservabilityLevel,
395
+ event: str,
387
396
  /,
388
397
  *,
389
- level: ObservabilityLevel,
390
- **extra: Any,
398
+ attributes: Mapping[str, ObservabilityAttribute],
391
399
  ) -> None:
392
400
  try: # catch exceptions - we don't wan't to blow up on observability
393
401
  context: Self = cls._context.get()
@@ -397,7 +405,7 @@ class ObservabilityContext:
397
405
  context._scope,
398
406
  level=level,
399
407
  event=event,
400
- **extra,
408
+ attributes=attributes,
401
409
  )
402
410
 
403
411
  except Exception as exc:
@@ -410,12 +418,13 @@ class ObservabilityContext:
410
418
  @classmethod
411
419
  def record_metric(
412
420
  cls,
421
+ level: ObservabilityLevel,
413
422
  metric: str,
414
423
  /,
415
424
  *,
416
425
  value: float | int,
417
426
  unit: str | None,
418
- **extra: Any,
427
+ attributes: Mapping[str, ObservabilityAttribute],
419
428
  ) -> None:
420
429
  try: # catch exceptions - we don't wan't to blow up on observability
421
430
  context: Self = cls._context.get()
@@ -423,10 +432,11 @@ class ObservabilityContext:
423
432
  if context.observability is not None:
424
433
  context.observability.metric_recording(
425
434
  context._scope,
435
+ level=level,
426
436
  metric=metric,
427
437
  value=value,
428
438
  unit=unit,
429
- **extra,
439
+ attributes=attributes,
430
440
  )
431
441
 
432
442
  except Exception as exc:
@@ -439,7 +449,10 @@ class ObservabilityContext:
439
449
  @classmethod
440
450
  def record_attributes(
441
451
  cls,
442
- **attributes: ObservabilityAttribute,
452
+ level: ObservabilityLevel,
453
+ /,
454
+ *,
455
+ attributes: Mapping[str, ObservabilityAttribute],
443
456
  ) -> None:
444
457
  try: # catch exceptions - we don't wan't to blow up on observability
445
458
  context: Self = cls._context.get()
@@ -447,13 +460,14 @@ class ObservabilityContext:
447
460
  if context.observability is not None:
448
461
  context.observability.attributes_recording(
449
462
  context._scope,
450
- **attributes,
463
+ level=level,
464
+ attributes=attributes,
451
465
  )
452
466
 
453
467
  except Exception as exc:
454
468
  cls.record_log(
455
469
  ERROR,
456
- f"Failed to record attributes: {attributes}",
470
+ "Failed to record attributes",
457
471
  exception=exc,
458
472
  )
459
473
 
@@ -4,14 +4,13 @@ from haiway.helpers.observability import LoggerObservability
4
4
  from haiway.helpers.retries import retry
5
5
  from haiway.helpers.throttling import throttle
6
6
  from haiway.helpers.timeouted import timeout
7
- from haiway.helpers.tracing import ResultTrace, traced
7
+ from haiway.helpers.tracing import traced
8
8
 
9
9
  __all__ = (
10
10
  "CacheMakeKey",
11
11
  "CacheRead",
12
12
  "CacheWrite",
13
13
  "LoggerObservability",
14
- "ResultTrace",
15
14
  "asynchronous",
16
15
  "cache",
17
16
  "retry",
@@ -1,11 +1,12 @@
1
- from logging import Logger
1
+ from collections.abc import Mapping
2
+ from logging import Logger, getLogger
2
3
  from time import monotonic
3
4
  from typing import Any
4
5
 
5
6
  from haiway.context import Observability, ObservabilityLevel, ScopeIdentifier
6
7
  from haiway.context.observability import ObservabilityAttribute
7
8
  from haiway.state import State
8
- from haiway.types import MISSING
9
+ from haiway.utils.formatting import format_str
9
10
 
10
11
  __all__ = ("LoggerObservability",)
11
12
 
@@ -63,12 +64,13 @@ class ScopeStore:
63
64
 
64
65
 
65
66
  def LoggerObservability( # noqa: C901, PLR0915
66
- logger: Logger,
67
+ logger: Logger | None = None,
67
68
  /,
68
69
  *,
69
- summarize_context: bool = __debug__,
70
+ debug_context: bool = __debug__,
70
71
  ) -> Observability:
71
72
  root_scope: ScopeIdentifier | None = None
73
+ root_logger: Logger | None = logger
72
74
  scopes: dict[str, ScopeStore] = {}
73
75
 
74
76
  def log_recording(
@@ -78,12 +80,12 @@ def LoggerObservability( # noqa: C901, PLR0915
78
80
  message: str,
79
81
  *args: Any,
80
82
  exception: BaseException | None,
81
- **extra: Any,
82
83
  ) -> None:
83
84
  assert root_scope is not None # nosec: B101
85
+ assert root_logger is not None # nosec: B101
84
86
  assert scope.scope_id in scopes # nosec: B101
85
87
 
86
- logger.log(
88
+ root_logger.log(
87
89
  level,
88
90
  f"{scope.unique_name} {message}",
89
91
  *args,
@@ -93,19 +95,20 @@ def LoggerObservability( # noqa: C901, PLR0915
93
95
  def event_recording(
94
96
  scope: ScopeIdentifier,
95
97
  /,
96
- *,
97
98
  level: ObservabilityLevel,
98
- event: State,
99
- **extra: Any,
99
+ *,
100
+ event: str,
101
+ attributes: Mapping[str, ObservabilityAttribute],
100
102
  ) -> None:
101
103
  assert root_scope is not None # nosec: B101
104
+ assert root_logger is not None # nosec: B101
102
105
  assert scope.scope_id in scopes # nosec: B101
103
106
 
104
- event_str: str = f"Event:\n{event.to_str(pretty=True)}"
105
- if summarize_context: # store only for summary
107
+ event_str: str = f"Event: {event} {format_str(attributes)}"
108
+ if debug_context: # store only for summary
106
109
  scopes[scope.scope_id].store.append(event_str)
107
110
 
108
- logger.log(
111
+ root_logger.log(
109
112
  level,
110
113
  f"{scope.unique_name} {event_str}",
111
114
  )
@@ -113,41 +116,50 @@ def LoggerObservability( # noqa: C901, PLR0915
113
116
  def metric_recording(
114
117
  scope: ScopeIdentifier,
115
118
  /,
119
+ level: ObservabilityLevel,
116
120
  *,
117
121
  metric: str,
118
122
  value: float | int,
119
123
  unit: str | None,
120
- **extra: Any,
124
+ attributes: Mapping[str, ObservabilityAttribute],
121
125
  ) -> None:
122
126
  assert root_scope is not None # nosec: B101
127
+ assert root_logger is not None # nosec: B101
123
128
  assert scope.scope_id in scopes # nosec: B101
124
129
 
125
- metric_str: str = f"Metric: {metric}={value}{unit or ''}"
126
- if summarize_context: # store only for summary
130
+ metric_str: str
131
+ if attributes:
132
+ metric_str = f"Metric: {metric}={value}{unit or ''}\n{format_str(attributes)}"
133
+
134
+ else:
135
+ metric_str = f"Metric: {metric}={value}{unit or ''}"
136
+
137
+ if debug_context: # store only for summary
127
138
  scopes[scope.scope_id].store.append(metric_str)
128
139
 
129
- logger.log(
130
- ObservabilityLevel.INFO,
140
+ root_logger.log(
141
+ level,
131
142
  f"{scope.unique_name} {metric_str}",
132
143
  )
133
144
 
134
145
  def attributes_recording(
135
146
  scope: ScopeIdentifier,
136
147
  /,
137
- **attributes: ObservabilityAttribute,
148
+ level: ObservabilityLevel,
149
+ attributes: Mapping[str, ObservabilityAttribute],
138
150
  ) -> None:
151
+ assert root_scope is not None # nosec: B101
152
+ assert root_logger is not None # nosec: B101
153
+
139
154
  if not attributes:
140
155
  return
141
156
 
142
- attributes_str: str = (
143
- f"{scope.unique_name} Attributes:"
144
- f"\n{'\n'.join(f'{k}: {v}' for k, v in attributes.items() if v is not None and v is not MISSING)}" # noqa: E501
145
- )
146
- if summarize_context: # store only for summary
157
+ attributes_str: str = f"Attributes: {format_str(attributes)}"
158
+ if debug_context: # store only for summary
147
159
  scopes[scope.scope_id].store.append(attributes_str)
148
160
 
149
- logger.log(
150
- ObservabilityLevel.INFO,
161
+ root_logger.log(
162
+ level,
151
163
  attributes_str,
152
164
  )
153
165
 
@@ -159,18 +171,21 @@ def LoggerObservability( # noqa: C901, PLR0915
159
171
  scope_store: ScopeStore = ScopeStore(scope)
160
172
  scopes[scope.scope_id] = scope_store
161
173
 
162
- logger.log(
163
- ObservabilityLevel.INFO,
164
- f"{scope.unique_name} Entering scope: {scope.label}",
165
- )
166
-
167
174
  nonlocal root_scope
175
+ nonlocal root_logger
168
176
  if root_scope is None:
169
177
  root_scope = scope
178
+ root_logger = logger or getLogger(scope.label)
170
179
 
171
180
  else:
172
181
  scopes[scope.parent_id].nested.append(scope_store)
173
182
 
183
+ assert root_logger is not None # nosec: B101
184
+ root_logger.log(
185
+ ObservabilityLevel.INFO,
186
+ f"{scope.unique_name} Entering scope: {scope.label}",
187
+ )
188
+
174
189
  def scope_exiting[Metric: State](
175
190
  scope: ScopeIdentifier,
176
191
  /,
@@ -178,8 +193,10 @@ def LoggerObservability( # noqa: C901, PLR0915
178
193
  exception: BaseException | None,
179
194
  ) -> None:
180
195
  nonlocal root_scope
196
+ nonlocal root_logger
181
197
  nonlocal scopes
182
198
  assert root_scope is not None # nosec: B101
199
+ assert root_logger is not None # nosec: B101
183
200
  assert scope.scope_id in scopes # nosec: B101
184
201
 
185
202
  scopes[scope.scope_id].exit()
@@ -187,15 +204,15 @@ def LoggerObservability( # noqa: C901, PLR0915
187
204
  if not scopes[scope.scope_id].try_complete():
188
205
  return # not completed yet or already completed
189
206
 
190
- logger.log(
207
+ root_logger.log(
191
208
  ObservabilityLevel.INFO,
192
209
  f"{scope.unique_name} Exiting scope: {scope.label}",
193
210
  )
194
211
  metric_str: str = f"Metric - scope_time:{scopes[scope.scope_id].time:.3f}s"
195
- if summarize_context: # store only for summary
212
+ if debug_context: # store only for summary
196
213
  scopes[scope.scope_id].store.append(metric_str)
197
214
 
198
- logger.log(
215
+ root_logger.log(
199
216
  ObservabilityLevel.INFO,
200
217
  f"{scope.unique_name} {metric_str}",
201
218
  )
@@ -207,14 +224,15 @@ def LoggerObservability( # noqa: C901, PLR0915
207
224
 
208
225
  # check for root completion
209
226
  if scopes[root_scope.scope_id].completed:
210
- if summarize_context:
211
- logger.log(
227
+ if debug_context:
228
+ root_logger.log(
212
229
  ObservabilityLevel.DEBUG,
213
230
  f"Observability summary:\n{_tree_summary(scopes[root_scope.scope_id])}",
214
231
  )
215
232
 
216
233
  # finished root - cleanup state
217
234
  root_scope = None
235
+ root_logger = None
218
236
  scopes = {}
219
237
 
220
238
  return Observability(
haiway/helpers/tracing.py CHANGED
@@ -1,40 +1,13 @@
1
1
  from asyncio import iscoroutinefunction
2
2
  from collections.abc import Callable, Coroutine
3
- from typing import Any, Self, cast, overload
3
+ from typing import Any, cast, overload
4
4
 
5
5
  from haiway.context import ctx
6
- from haiway.state import State
7
- from haiway.types import MISSING, Missing
6
+ from haiway.types import MISSING
8
7
  from haiway.utils import mimic_function
8
+ from haiway.utils.formatting import format_str
9
9
 
10
- __all__ = (
11
- "ResultTrace",
12
- "traced",
13
- )
14
-
15
-
16
- class ResultTrace(State):
17
- if __debug__:
18
-
19
- @classmethod
20
- def of(
21
- cls,
22
- value: Any,
23
- /,
24
- ) -> Self:
25
- return cls(result=f"{value}")
26
-
27
- else: # remove tracing for non debug runs to prevent accidental secret leaks
28
-
29
- @classmethod
30
- def of(
31
- cls,
32
- value: Any,
33
- /,
34
- ) -> Self:
35
- return cls(result=MISSING)
36
-
37
- result: str | Missing
10
+ __all__ = ("traced",)
38
11
 
39
12
 
40
13
  @overload
@@ -96,18 +69,28 @@ def _traced_sync[**Args, Result](
96
69
  **kwargs: Args.kwargs,
97
70
  ) -> Result:
98
71
  with ctx.scope(label):
99
- ctx.attributes(
100
- **{f"[{idx}]": f"{arg}" for idx, arg in enumerate(args) if arg is not MISSING}
72
+ ctx.record(
73
+ attributes={
74
+ f"[{idx}]": f"{arg}" for idx, arg in enumerate(args) if arg is not MISSING
75
+ }
76
+ )
77
+ ctx.record(
78
+ attributes={key: f"{arg}" for key, arg in kwargs.items() if arg is not MISSING}
101
79
  )
102
- ctx.attributes(**{key: f"{arg}" for key, arg in kwargs.items() if arg is not MISSING})
103
80
 
104
81
  try:
105
82
  result: Result = function(*args, **kwargs)
106
- ctx.event(ResultTrace.of(result))
83
+ ctx.record(
84
+ event="result",
85
+ attributes={"value": format_str(result)},
86
+ )
107
87
  return result
108
88
 
109
89
  except BaseException as exc:
110
- ctx.event(ResultTrace.of(f"{type(exc)}: {exc}"))
90
+ ctx.record(
91
+ event="result",
92
+ attributes={"error": f"{type(exc)}: {exc}"},
93
+ )
111
94
  raise exc
112
95
 
113
96
  return mimic_function(
@@ -126,19 +109,28 @@ def _traced_async[**Args, Result](
126
109
  **kwargs: Args.kwargs,
127
110
  ) -> Result:
128
111
  with ctx.scope(label):
129
- for idx, arg in enumerate(args):
130
- ctx.attributes(**{f"[{idx}]": f"{arg}"})
131
-
132
- for key, arg in kwargs.items():
133
- ctx.attributes(**{key: f"{arg}"})
112
+ ctx.record(
113
+ attributes={
114
+ f"[{idx}]": f"{arg}" for idx, arg in enumerate(args) if arg is not MISSING
115
+ }
116
+ )
117
+ ctx.record(
118
+ attributes={key: f"{arg}" for key, arg in kwargs.items() if arg is not MISSING}
119
+ )
134
120
 
135
121
  try:
136
122
  result: Result = await function(*args, **kwargs)
137
- ctx.event(ResultTrace.of(result))
123
+ ctx.record(
124
+ event="result",
125
+ attributes={"value": format_str(result)},
126
+ )
138
127
  return result
139
128
 
140
129
  except BaseException as exc:
141
- ctx.event(ResultTrace.of(f"{type(exc)}: {exc}"))
130
+ ctx.record(
131
+ event="result",
132
+ attributes={"error": f"{type(exc)}: {exc}"},
133
+ )
142
134
  raise exc
143
135
 
144
136
  return mimic_function(
@@ -29,6 +29,7 @@ from opentelemetry.sdk.resources import Resource
29
29
  from opentelemetry.sdk.trace import TracerProvider
30
30
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SpanExporter
31
31
  from opentelemetry.trace import Span, StatusCode, Tracer
32
+ from opentelemetry.trace.span import SpanContext
32
33
 
33
34
  from haiway.context import Observability, ObservabilityLevel, ScopeIdentifier
34
35
  from haiway.context.observability import ObservabilityAttribute
@@ -44,9 +45,9 @@ class ScopeStore:
44
45
  "_counters",
45
46
  "_exited",
46
47
  "_histograms",
48
+ "_span_context",
47
49
  "identifier",
48
50
  "logger",
49
- "logger_2",
50
51
  "meter",
51
52
  "nested",
52
53
  "span",
@@ -67,6 +68,7 @@ class ScopeStore:
67
68
  self._exited: bool = False
68
69
  self._completed: bool = False
69
70
  self.span: Span = span
71
+ self._span_context: SpanContext = span.get_span_context()
70
72
  self.meter: Meter = meter
71
73
  self.logger: Logger = logger
72
74
 
@@ -104,16 +106,16 @@ class ScopeStore:
104
106
  ) -> None:
105
107
  self.logger.emit(
106
108
  LogRecord(
107
- span_id=self.span.get_span_context().span_id,
108
- trace_id=self.span.get_span_context().trace_id,
109
- trace_flags=self.span.get_span_context().trace_flags,
109
+ span_id=self._span_context.span_id,
110
+ trace_id=self._span_context.trace_id,
111
+ trace_flags=self._span_context.trace_flags,
110
112
  body=message,
111
113
  severity_text=level.name,
112
114
  severity_number=SEVERITY_MAPPING[level],
113
115
  attributes={
114
- "trace_id": self.identifier.trace_id,
115
- "scope_id": self.identifier.scope_id,
116
- "parent_id": self.identifier.parent_id,
116
+ "context.trace_id": self.identifier.trace_id,
117
+ "context.scope_id": self.identifier.scope_id,
118
+ "context.parent_id": self.identifier.parent_id,
117
119
  },
118
120
  )
119
121
  )
@@ -127,12 +129,18 @@ class ScopeStore:
127
129
 
128
130
  def record_event(
129
131
  self,
130
- event: State,
132
+ event: str,
131
133
  /,
134
+ *,
135
+ attributes: Mapping[str, ObservabilityAttribute],
132
136
  ) -> None:
133
137
  self.span.add_event(
134
- str(type(event).__name__),
135
- attributes=event.to_mapping(recursive=True),
138
+ event,
139
+ attributes={
140
+ key: cast(Any, value)
141
+ for key, value in attributes.items()
142
+ if value is not None and value is not MISSING
143
+ },
136
144
  )
137
145
 
138
146
  def record_metric(
@@ -142,13 +150,8 @@ class ScopeStore:
142
150
  *,
143
151
  value: float | int,
144
152
  unit: str | None,
153
+ attributes: Mapping[str, ObservabilityAttribute],
145
154
  ) -> None:
146
- attributes: Mapping[str, Any] = {
147
- "trace_id": self.identifier.trace_id,
148
- "scope_id": self.identifier.scope_id,
149
- "parent_id": self.identifier.parent_id,
150
- }
151
-
152
155
  if name not in self._counters:
153
156
  self._counters[name] = self.meter.create_counter(
154
157
  name=name,
@@ -157,7 +160,18 @@ class ScopeStore:
157
160
 
158
161
  self._counters[name].add(
159
162
  value,
160
- attributes=attributes,
163
+ attributes={
164
+ **{
165
+ "context.trace_id": self.identifier.trace_id,
166
+ "context.scope_id": self.identifier.scope_id,
167
+ "context.parent_id": self.identifier.parent_id,
168
+ },
169
+ **{
170
+ key: cast(Any, value)
171
+ for key, value in attributes.items()
172
+ if value is not None and value is not MISSING
173
+ },
174
+ },
161
175
  )
162
176
 
163
177
  def record_attribute(
@@ -193,8 +207,8 @@ class OpenTelemetry:
193
207
  {
194
208
  "service.name": service,
195
209
  "service.version": version,
196
- "deployment.environment": environment,
197
210
  "service.pid": os.getpid(),
211
+ "deployment.environment": environment,
198
212
  **(attributes if attributes is not None else {}),
199
213
  },
200
214
  )
@@ -273,7 +287,6 @@ class OpenTelemetry:
273
287
  message: str,
274
288
  *args: Any,
275
289
  exception: BaseException | None,
276
- **extra: Any,
277
290
  ) -> None:
278
291
  assert root_scope is not None # nosec: B101
279
292
  assert scope.scope_id in scopes # nosec: B101
@@ -291,10 +304,10 @@ class OpenTelemetry:
291
304
  def event_recording(
292
305
  scope: ScopeIdentifier,
293
306
  /,
294
- *,
295
307
  level: ObservabilityLevel,
296
- event: State,
297
- **extra: Any,
308
+ *,
309
+ event: str,
310
+ attributes: Mapping[str, ObservabilityAttribute],
298
311
  ) -> None:
299
312
  assert root_scope is not None # nosec: B101
300
313
  assert scope.scope_id in scopes # nosec: B101
@@ -302,16 +315,20 @@ class OpenTelemetry:
302
315
  if level < observed_level:
303
316
  return
304
317
 
305
- scopes[scope.scope_id].record_event(event)
318
+ scopes[scope.scope_id].record_event(
319
+ event,
320
+ attributes=attributes,
321
+ )
306
322
 
307
323
  def metric_recording(
308
324
  scope: ScopeIdentifier,
309
325
  /,
326
+ level: ObservabilityLevel,
310
327
  *,
311
328
  metric: str,
312
329
  value: float | int,
313
330
  unit: str | None,
314
- **extra: Any,
331
+ attributes: Mapping[str, ObservabilityAttribute],
315
332
  ) -> None:
316
333
  assert root_scope is not None # nosec: B101
317
334
  assert scope.scope_id in scopes # nosec: B101
@@ -323,13 +340,18 @@ class OpenTelemetry:
323
340
  metric,
324
341
  value=value,
325
342
  unit=unit,
343
+ attributes=attributes,
326
344
  )
327
345
 
328
346
  def attributes_recording(
329
347
  scope: ScopeIdentifier,
330
348
  /,
331
- **attributes: ObservabilityAttribute,
349
+ level: ObservabilityLevel,
350
+ attributes: Mapping[str, ObservabilityAttribute],
332
351
  ) -> None:
352
+ if level < observed_level:
353
+ return
354
+
333
355
  if not attributes:
334
356
  return
335
357
 
@@ -363,9 +385,9 @@ class OpenTelemetry:
363
385
  parent_id=scope.parent_id,
364
386
  ),
365
387
  attributes={
366
- "trace_id": scope.trace_id,
367
- "scope_id": scope.scope_id,
368
- "parent_id": scope.parent_id,
388
+ "context.trace_id": scope.trace_id,
389
+ "context.scope_id": scope.scope_id,
390
+ "context.parent_id": scope.parent_id,
369
391
  },
370
392
  ),
371
393
  meter=meter,
@@ -390,9 +412,9 @@ class OpenTelemetry:
390
412
  ),
391
413
  ),
392
414
  attributes={
393
- "trace_id": scope.trace_id,
394
- "scope_id": scope.scope_id,
395
- "parent_id": scope.parent_id,
415
+ "context.trace_id": scope.trace_id,
416
+ "context.scope_id": scope.scope_id,
417
+ "context.parent_id": scope.parent_id,
396
418
  },
397
419
  ),
398
420
  meter=meter,
haiway/state/structure.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import typing
2
- from collections.abc import ItemsView, Mapping, Sequence
2
+ from collections.abc import Mapping
3
3
  from types import EllipsisType, GenericAlias
4
4
  from typing import (
5
5
  Any,
@@ -366,15 +366,8 @@ class State(metaclass=StateMeta):
366
366
  ) -> Self:
367
367
  return self.__replace__(**kwargs)
368
368
 
369
- def to_str(
370
- self,
371
- pretty: bool = False,
372
- ) -> str:
373
- if pretty:
374
- return _state_str(self)
375
-
376
- else:
377
- return self.__str__()
369
+ def to_str(self) -> str:
370
+ return self.__str__()
378
371
 
379
372
  def to_mapping(
380
373
  self,
@@ -448,144 +441,3 @@ class State(metaclass=StateMeta):
448
441
  **kwargs,
449
442
  }
450
443
  )
451
-
452
-
453
- def _attribute_str(
454
- *,
455
- key: str,
456
- value: str,
457
- ) -> str:
458
- return f"┝ {key}: {value}"
459
-
460
-
461
- def _element_str(
462
- *,
463
- key: Any,
464
- value: Any,
465
- ) -> str:
466
- return f"[{key}]: {value}"
467
-
468
-
469
- def _state_str(
470
- state: State,
471
- /,
472
- ) -> str:
473
- variables: ItemsView[str, Any] = vars(state).items()
474
-
475
- parts: list[str] = [f"┍━ {type(state).__name__}:"]
476
- for key, value in variables:
477
- value_string: str | None = _value_str(value)
478
-
479
- if value_string:
480
- parts.append(
481
- _attribute_str(
482
- key=key,
483
- value=value_string,
484
- )
485
- )
486
-
487
- else:
488
- continue # skip empty elements
489
-
490
- if parts:
491
- return "\n".join(parts) + "\n┕━"
492
-
493
- else:
494
- return "╍"
495
-
496
-
497
- def _mapping_str(
498
- dictionary: Mapping[Any, Any],
499
- /,
500
- ) -> str | None:
501
- elements: ItemsView[Any, Any] = dictionary.items()
502
-
503
- parts: list[str] = []
504
- for key, value in elements:
505
- value_string: str | None = _value_str(value)
506
-
507
- if value_string:
508
- parts.append(
509
- _element_str(
510
- key=key,
511
- value=value_string,
512
- )
513
- )
514
-
515
- else:
516
- continue # skip empty elements
517
-
518
- if parts:
519
- return "\n| " + "\n".join(parts).replace("\n", "\n| ")
520
-
521
- else:
522
- return None
523
-
524
-
525
- def _sequence_str(
526
- sequence: Sequence[Any],
527
- /,
528
- ) -> str | None:
529
- parts: list[str] = []
530
- for idx, element in enumerate(sequence):
531
- element_string: str | None = _value_str(element)
532
-
533
- if element_string:
534
- parts.append(
535
- _element_str(
536
- key=idx,
537
- value=element_string,
538
- )
539
- )
540
-
541
- else:
542
- continue # skip empty elements
543
-
544
- if parts:
545
- return "\n| " + "\n".join(parts).replace("\n", "\n| ")
546
-
547
- else:
548
- return None
549
-
550
-
551
- def _raw_value_str(
552
- value: Any,
553
- /,
554
- ) -> str | None:
555
- if value is MISSING:
556
- return None # skip missing
557
-
558
- else:
559
- return str(value).strip().replace("\n", "\n| ")
560
-
561
-
562
- def _value_str( # noqa: PLR0911
563
- value: Any,
564
- /,
565
- ) -> str | None:
566
- # check for string
567
- if isinstance(value, str):
568
- if "\n" in value:
569
- return f'"""\n{value}\n"""'.replace("\n", "\n| ")
570
-
571
- else:
572
- return f'"{value}"'
573
-
574
- # check for bytes
575
- elif isinstance(value, bytes):
576
- return f'b"{value}"'
577
-
578
- # try unpack state
579
- elif isinstance(value, State):
580
- return _state_str(value)
581
-
582
- # try unpack mapping
583
- elif isinstance(value, Mapping):
584
- return _mapping_str(value)
585
-
586
- # try unpack sequence
587
- elif isinstance(value, Sequence):
588
- return _sequence_str(value)
589
-
590
- else: # fallback to other
591
- return _raw_value_str(value)
haiway/utils/__init__.py CHANGED
@@ -8,6 +8,7 @@ from haiway.utils.env import (
8
8
  getenv_str,
9
9
  load_env,
10
10
  )
11
+ from haiway.utils.formatting import format_str
11
12
  from haiway.utils.freezing import freeze
12
13
  from haiway.utils.logs import setup_logging
13
14
  from haiway.utils.mimic import mimic_function
@@ -25,6 +26,7 @@ __all__ = (
25
26
  "as_tuple",
26
27
  "async_always",
27
28
  "async_noop",
29
+ "format_str",
28
30
  "freeze",
29
31
  "getenv_base64",
30
32
  "getenv_bool",
@@ -0,0 +1,148 @@
1
+ from collections.abc import ItemsView, Mapping, Sequence
2
+ from typing import Any
3
+
4
+ from haiway.types.missing import MISSING
5
+
6
+ __all__ = ("format_str",)
7
+
8
+
9
+ def format_str( # noqa: PLR0911
10
+ value: Any,
11
+ /,
12
+ ) -> str:
13
+ # check for string
14
+ if isinstance(value, str):
15
+ if "\n" in value:
16
+ return f'"""\n{value.replace("\n", "\n ")}\n"""'
17
+
18
+ else:
19
+ return f'"{value}"'
20
+
21
+ # check for bytes
22
+ elif isinstance(value, bytes):
23
+ return f"b'{value}'"
24
+
25
+ # try unpack mapping
26
+ elif isinstance(value, Mapping):
27
+ return _mapping_str(value)
28
+
29
+ # try unpack sequence
30
+ elif isinstance(value, Sequence):
31
+ return _sequence_str(value)
32
+
33
+ elif value is MISSING:
34
+ return ""
35
+
36
+ else: # fallback to object
37
+ return _object_str(value)
38
+
39
+
40
+ def _attribute_str(
41
+ *,
42
+ key: str,
43
+ value: str,
44
+ ) -> str:
45
+ if "\n" in value:
46
+ formatted_value: str = value.replace("\n", "\n| ")
47
+ return f"┝ {key}:\n{formatted_value}"
48
+
49
+ else:
50
+ return f"┝ {key}: {value}"
51
+
52
+
53
+ def _element_str(
54
+ *,
55
+ key: Any,
56
+ value: Any,
57
+ ) -> str:
58
+ if "\n" in value:
59
+ formatted_value: str = value.replace("\n", "\n ")
60
+ return f"[{key}]:\n{formatted_value}"
61
+
62
+ else:
63
+ return f"[{key}]: {value}"
64
+
65
+
66
+ def _object_str(
67
+ other: object,
68
+ /,
69
+ ) -> str:
70
+ variables: ItemsView[str, Any] = vars(other).items()
71
+
72
+ parts: list[str] = [f"┍━ {type(other).__name__}:"]
73
+ for key, value in variables:
74
+ if key.startswith("_"):
75
+ continue # skip private and dunder
76
+
77
+ value_string: str = format_str(value)
78
+
79
+ if value_string:
80
+ parts.append(
81
+ _attribute_str(
82
+ key=key,
83
+ value=value_string,
84
+ )
85
+ )
86
+
87
+ else:
88
+ continue # skip empty elements
89
+
90
+ if parts:
91
+ return "\n".join(parts) + "\n┕━"
92
+
93
+ else:
94
+ return ""
95
+
96
+
97
+ def _mapping_str(
98
+ mapping: Mapping[Any, Any],
99
+ /,
100
+ ) -> str:
101
+ items: ItemsView[Any, Any] = mapping.items()
102
+
103
+ parts: list[str] = []
104
+ for key, value in items:
105
+ value_string: str = format_str(value)
106
+
107
+ if value_string:
108
+ parts.append(
109
+ _element_str(
110
+ key=key,
111
+ value=value_string,
112
+ )
113
+ )
114
+
115
+ else:
116
+ continue # skip empty items
117
+
118
+ if parts:
119
+ return "{\n " + "\n".join(parts) + "\n}"
120
+
121
+ else:
122
+ return "{}"
123
+
124
+
125
+ def _sequence_str(
126
+ sequence: Sequence[Any],
127
+ /,
128
+ ) -> str:
129
+ parts: list[str] = []
130
+ for idx, element in enumerate(sequence):
131
+ element_string: str = format_str(element)
132
+
133
+ if element_string:
134
+ parts.append(
135
+ _element_str(
136
+ key=idx,
137
+ value=element_string,
138
+ )
139
+ )
140
+
141
+ else:
142
+ continue # skip empty elements
143
+
144
+ if parts:
145
+ return "[\n " + "\n".join(parts) + "\n]"
146
+
147
+ else:
148
+ return "[]"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiway
3
- Version: 0.18.2
3
+ Version: 0.19.0
4
4
  Summary: Framework for dependency injection and state management within structured concurrency model.
5
5
  Project-URL: Homepage, https://miquido.com
6
6
  Project-URL: Repository, https://github.com/miquido/haiway.git
@@ -1,44 +1,45 @@
1
- haiway/__init__.py,sha256=GAukz1qQxO0mhvxSXdabC0JYiWw4nGKIGnmuImtDWdQ,2389
1
+ haiway/__init__.py,sha256=UjaJeNa5lQGaeOguf2COSakrBDc8sd5zB9ioHiK4uOw,2353
2
2
  haiway/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  haiway/context/__init__.py,sha256=eoxqxUmFtkWLhc_gH6tqax9m90tSDie-jiP1BHruTdk,1102
4
- haiway/context/access.py,sha256=49yzFt9yJU6p0pDE3jV_ZIvmgSk6_czzOOT2gijSJk8,18648
4
+ haiway/context/access.py,sha256=vTh5BlduAGBjqnVyTgTUt08DG6SrnAakweAfIDBqx-0,18838
5
5
  haiway/context/disposables.py,sha256=xE8RZYsYgXiOZY_TjWR7UiPG6dirna6y2LBZvMwTkIs,2588
6
6
  haiway/context/identifier.py,sha256=i6nO-tozps7iDnpS5Se7CRch7hh6z2akjZthutxHte8,3943
7
- haiway/context/observability.py,sha256=jgmRP2iKu3AF2UbAh6o3w7wZK9Qrr8bsKlUF4eyOZh4,13831
7
+ haiway/context/observability.py,sha256=SiBKW7IEZVqxjAi4FCXSuOc-qVENYUa3s2E90M0UJ18,14502
8
8
  haiway/context/state.py,sha256=0oq7ctNO0urJd7rVzwwNtgpguoXuI6Tp1exfCsxrS2M,5981
9
9
  haiway/context/tasks.py,sha256=QOxFdjmMp4IYff0ihHElKLCQrcVksSJmxqTlOKfoH4o,2907
10
10
  haiway/context/types.py,sha256=WulPvpqUbI1vYyny-s2NItldDnk3zh1O-n_hGibFZRY,142
11
- haiway/helpers/__init__.py,sha256=awAEVFv9_talefS6ymMbofDXigB-Klsxkj2uth8la-g,615
11
+ haiway/helpers/__init__.py,sha256=dYqwWSBk8ss9XyXEF6YHZNPrCwU8VDM4nKtcz1tsvZI,583
12
12
  haiway/helpers/asynchrony.py,sha256=k_A0yCWUKSFfzYZ8WvqK4wqTMljv6ykMivmERrDLHIU,6266
13
13
  haiway/helpers/caching.py,sha256=4WX2Md5AOncduYB_RLLENI2s9C2zD5kNJgKZjMzPIGY,13257
14
- haiway/helpers/observability.py,sha256=Su9C4H_w8RuAXM0S24Yv6UDU5A6jnhZgwYhc9UNTL84,7075
14
+ haiway/helpers/observability.py,sha256=VjT9ENMRrRck9QbYiMTtlf-BLwwf2d_wGMXbGFzdK1k,7839
15
15
  haiway/helpers/retries.py,sha256=unssUKBDOENvquh6R4Ud65TuSKl4mTHgZ5N_b7mAYa4,7533
16
16
  haiway/helpers/throttling.py,sha256=U6HJvSzffw47730VeiXxXSW4VVxpDx48k0oIAOpL-O4,4115
17
17
  haiway/helpers/timeouted.py,sha256=_M8diuD_GN49pl5KQA5fMKn4iUHsUuhkDSatAwWXiK8,3331
18
- haiway/helpers/tracing.py,sha256=v4vRpkXKV5AEdrnvQ3MGsaXHKKqXvwQ0V5xWXOg6RrU,3692
18
+ haiway/helpers/tracing.py,sha256=3-gbrAp4X-vNBbPGR6Li8BpMB0svH_PgRNJYopvYYXw,3752
19
19
  haiway/opentelemetry/__init__.py,sha256=TV-1C14mDAtcHhFZ29ActFQdrGH6x5KuGV9w-JlKYJg,91
20
- haiway/opentelemetry/observability.py,sha256=71jDpoxwye-_dEqSxRd_g6Z9_8fz0dYUOwdfOHla1uk,14380
20
+ haiway/opentelemetry/observability.py,sha256=ac0Jl0YFkpb4rbmwg3smPAs-QV_pcMjxSzelGhbwcsc,15332
21
21
  haiway/state/__init__.py,sha256=AaMqlMhO4zKS_XNevy3A7BHh5PxmguA-Sk_FnaNDY1Q,355
22
22
  haiway/state/attributes.py,sha256=p6jUBzg62bOl0zAYTCa7NIllsaNY2Kt68IooQ9tb-y8,23311
23
23
  haiway/state/path.py,sha256=-IpbUpF2QHWg3hEITkWYHJ6ZPoRVixu-SOSuWk-bbBY,21318
24
24
  haiway/state/requirement.py,sha256=oKh9eqgTwxcJF4JNhU-DAbHbHsaACMNSlX-mkVjeJeY,7034
25
- haiway/state/structure.py,sha256=rlA7qTr7rmJ_cU7_lJYq_y9Y-GO1wsaeHtjyDiIB8y8,16195
25
+ haiway/state/structure.py,sha256=_aAB32qH3Nhu3TQzHBZYO9l2D55V1TW1WJW_CJdAMH0,13202
26
26
  haiway/state/validation.py,sha256=LiCkItybUHT3oKG6IyLu2x6IKKvnWnabuEcVkTbEP9Y,14996
27
27
  haiway/types/__init__.py,sha256=73DMgf60Ftf1gLRCSQG66Nyu3_QFjdRJggBtS4-RQkY,342
28
28
  haiway/types/default.py,sha256=IIU6QA73aDUKXLNu78KQ2dLQFbyBrU74w7jlFswHl-8,2208
29
29
  haiway/types/frozen.py,sha256=zLVkj85_lj6LrXjXAdv06Yy0MCj4spC8FQ-AhZMDPKg,70
30
30
  haiway/types/missing.py,sha256=769MX5qpJ3zjNu6xLUH75On8FgheY06f2JYFR21gs9o,1712
31
- haiway/utils/__init__.py,sha256=RqeTPXTVhvYp8rd5YLMSyH0hYF9y4j6aSxdbjG4VZtA,907
31
+ haiway/utils/__init__.py,sha256=HOylRgBEa0uNxEuPBupaJ28l4wEQiy98cGJi2Gtirr4,972
32
32
  haiway/utils/always.py,sha256=dd6jDQ1j4DpJjTKO1J2Tv5xS8X1LnMC4kQ0D7DtKUvw,1230
33
33
  haiway/utils/collections.py,sha256=pSBXhtLdhrLqmYo9YZEx716JI9S_sIztLJ5z5wi2d7Y,4162
34
34
  haiway/utils/env.py,sha256=gdZcQS9l82hKm4Jojy1vnE42s89JqPFbiYODAE8I2sA,5339
35
+ haiway/utils/formatting.py,sha256=9OZUDa9aUSjzYj8pBwadfDwz7SjD9aB_vgn43RtGbrI,3042
35
36
  haiway/utils/freezing.py,sha256=QsThd6FJ8TgErio7pCsHSnUKmVQbHZu6iEDYiqvJteo,614
36
37
  haiway/utils/logs.py,sha256=NuwoqKQnMNi1FMIA91cVFnAPefUFeg3UIT50IOl3sJk,1571
37
38
  haiway/utils/mimic.py,sha256=L5AS4WEL2aPMZAQZlvLvRzHl0cipI7ivky60_eL4iwY,1822
38
39
  haiway/utils/noop.py,sha256=f54PSLHGEwCQNYXQHkPAW5NDE-tk5yjzkNL1pZj0TJQ,344
39
40
  haiway/utils/queue.py,sha256=YTvCn3wgSwLJiLqolMx44sa3304Xkv3tJG77gvfWnZs,4114
40
41
  haiway/utils/stream.py,sha256=Mjhy2S-ZDR1g_NsgS_nuBA8AgVbhrGXKvG3wjJ5mCJQ,2826
41
- haiway-0.18.2.dist-info/METADATA,sha256=Sf5zs_gFdyGBy5MtZQyM68b4zTnvuf9M2otV7_3ricM,4527
42
- haiway-0.18.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
- haiway-0.18.2.dist-info/licenses/LICENSE,sha256=3phcpHVNBP8jsi77gOO0E7rgKeDeu99Pi7DSnK9YHoQ,1069
44
- haiway-0.18.2.dist-info/RECORD,,
42
+ haiway-0.19.0.dist-info/METADATA,sha256=VbWYq_-EAnn3JtAluszAzWZ7kVt3okHNNMrVvBh_sMk,4527
43
+ haiway-0.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
+ haiway-0.19.0.dist-info/licenses/LICENSE,sha256=3phcpHVNBP8jsi77gOO0E7rgKeDeu99Pi7DSnK9YHoQ,1069
45
+ haiway-0.19.0.dist-info/RECORD,,