haiway 0.17.0__py3-none-any.whl → 0.18.1__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.
Files changed (47) hide show
  1. haiway/__init__.py +24 -18
  2. haiway/context/__init__.py +23 -13
  3. haiway/context/access.py +127 -91
  4. haiway/context/disposables.py +2 -2
  5. haiway/context/identifier.py +4 -5
  6. haiway/context/observability.py +526 -0
  7. haiway/context/state.py +2 -2
  8. haiway/context/tasks.py +1 -3
  9. haiway/context/types.py +2 -2
  10. haiway/helpers/__init__.py +5 -7
  11. haiway/helpers/asynchrony.py +2 -2
  12. haiway/helpers/caching.py +2 -2
  13. haiway/helpers/observability.py +244 -0
  14. haiway/helpers/retries.py +1 -3
  15. haiway/helpers/throttling.py +1 -3
  16. haiway/helpers/timeouted.py +1 -3
  17. haiway/helpers/tracing.py +21 -35
  18. haiway/opentelemetry/__init__.py +3 -0
  19. haiway/opentelemetry/observability.py +452 -0
  20. haiway/state/__init__.py +2 -2
  21. haiway/state/attributes.py +2 -2
  22. haiway/state/path.py +1 -3
  23. haiway/state/requirement.py +1 -3
  24. haiway/state/structure.py +161 -30
  25. haiway/state/validation.py +2 -2
  26. haiway/types/__init__.py +2 -2
  27. haiway/types/default.py +2 -2
  28. haiway/types/frozen.py +1 -3
  29. haiway/types/missing.py +2 -2
  30. haiway/utils/__init__.py +2 -2
  31. haiway/utils/always.py +2 -2
  32. haiway/utils/collections.py +2 -2
  33. haiway/utils/env.py +2 -2
  34. haiway/utils/freezing.py +1 -3
  35. haiway/utils/logs.py +1 -3
  36. haiway/utils/mimic.py +1 -3
  37. haiway/utils/noop.py +2 -2
  38. haiway/utils/queue.py +1 -3
  39. haiway/utils/stream.py +1 -3
  40. {haiway-0.17.0.dist-info → haiway-0.18.1.dist-info}/METADATA +9 -5
  41. haiway-0.18.1.dist-info/RECORD +44 -0
  42. haiway/context/logging.py +0 -242
  43. haiway/context/metrics.py +0 -176
  44. haiway/helpers/metrics.py +0 -465
  45. haiway-0.17.0.dist-info/RECORD +0 -43
  46. {haiway-0.17.0.dist-info → haiway-0.18.1.dist-info}/WHEEL +0 -0
  47. {haiway-0.17.0.dist-info → haiway-0.18.1.dist-info}/licenses/LICENSE +0 -0
haiway/__init__.py CHANGED
@@ -1,22 +1,25 @@
1
1
  from haiway.context import (
2
2
  Disposable,
3
3
  Disposables,
4
- MetricsContext,
5
- MetricsHandler,
6
- MetricsRecording,
7
- MetricsScopeEntering,
8
- MetricsScopeExiting,
9
4
  MissingContext,
10
5
  MissingState,
6
+ Observability,
7
+ ObservabilityAttribute,
8
+ ObservabilityAttributesRecording,
9
+ ObservabilityContext,
10
+ ObservabilityEventRecording,
11
+ ObservabilityLevel,
12
+ ObservabilityLogRecording,
13
+ ObservabilityMetricRecording,
14
+ ObservabilityScopeEntering,
15
+ ObservabilityScopeExiting,
11
16
  ScopeContext,
12
17
  ScopeIdentifier,
13
18
  StateContext,
14
19
  ctx,
15
20
  )
16
21
  from haiway.helpers import (
17
- ArgumentsTrace,
18
- MetricsHolder,
19
- MetricsLogger,
22
+ LoggerObservability,
20
23
  ResultTrace,
21
24
  asynchronous,
22
25
  cache,
@@ -60,9 +63,8 @@ from haiway.utils import (
60
63
  without_missing,
61
64
  )
62
65
 
63
- __all__ = [
66
+ __all__ = (
64
67
  "MISSING",
65
- "ArgumentsTrace",
66
68
  "AsyncQueue",
67
69
  "AsyncStream",
68
70
  "AttributePath",
@@ -71,16 +73,20 @@ __all__ = [
71
73
  "DefaultValue",
72
74
  "Disposable",
73
75
  "Disposables",
74
- "MetricsContext",
75
- "MetricsHandler",
76
- "MetricsHolder",
77
- "MetricsLogger",
78
- "MetricsRecording",
79
- "MetricsScopeEntering",
80
- "MetricsScopeExiting",
76
+ "LoggerObservability",
81
77
  "Missing",
82
78
  "MissingContext",
83
79
  "MissingState",
80
+ "Observability",
81
+ "ObservabilityAttribute",
82
+ "ObservabilityAttributesRecording",
83
+ "ObservabilityContext",
84
+ "ObservabilityEventRecording",
85
+ "ObservabilityLevel",
86
+ "ObservabilityLogRecording",
87
+ "ObservabilityMetricRecording",
88
+ "ObservabilityScopeEntering",
89
+ "ObservabilityScopeExiting",
84
90
  "ResultTrace",
85
91
  "ScopeContext",
86
92
  "ScopeIdentifier",
@@ -116,4 +122,4 @@ __all__ = [
116
122
  "when_missing",
117
123
  "without_missing",
118
124
  "wrap_async",
119
- ]
125
+ )
@@ -1,28 +1,38 @@
1
1
  from haiway.context.access import ScopeContext, ctx
2
2
  from haiway.context.disposables import Disposable, Disposables
3
3
  from haiway.context.identifier import ScopeIdentifier
4
- from haiway.context.metrics import (
5
- MetricsContext,
6
- MetricsHandler,
7
- MetricsRecording,
8
- MetricsScopeEntering,
9
- MetricsScopeExiting,
4
+ from haiway.context.observability import (
5
+ Observability,
6
+ ObservabilityAttribute,
7
+ ObservabilityAttributesRecording,
8
+ ObservabilityContext,
9
+ ObservabilityEventRecording,
10
+ ObservabilityLevel,
11
+ ObservabilityLogRecording,
12
+ ObservabilityMetricRecording,
13
+ ObservabilityScopeEntering,
14
+ ObservabilityScopeExiting,
10
15
  )
11
16
  from haiway.context.state import StateContext
12
17
  from haiway.context.types import MissingContext, MissingState
13
18
 
14
- __all__ = [
19
+ __all__ = (
15
20
  "Disposable",
16
21
  "Disposables",
17
- "MetricsContext",
18
- "MetricsHandler",
19
- "MetricsRecording",
20
- "MetricsScopeEntering",
21
- "MetricsScopeExiting",
22
22
  "MissingContext",
23
23
  "MissingState",
24
+ "Observability",
25
+ "ObservabilityAttribute",
26
+ "ObservabilityAttributesRecording",
27
+ "ObservabilityContext",
28
+ "ObservabilityEventRecording",
29
+ "ObservabilityLevel",
30
+ "ObservabilityLogRecording",
31
+ "ObservabilityMetricRecording",
32
+ "ObservabilityScopeEntering",
33
+ "ObservabilityScopeExiting",
24
34
  "ScopeContext",
25
35
  "ScopeIdentifier",
26
36
  "StateContext",
27
37
  "ctx",
28
- ]
38
+ )
haiway/context/access.py CHANGED
@@ -18,17 +18,19 @@ from typing import Any, final, overload
18
18
 
19
19
  from haiway.context.disposables import Disposable, Disposables
20
20
  from haiway.context.identifier import ScopeIdentifier
21
- from haiway.context.logging import LoggerContext
22
- from haiway.context.metrics import MetricsContext, MetricsHandler
21
+ from haiway.context.observability import (
22
+ Observability,
23
+ ObservabilityAttribute,
24
+ ObservabilityContext,
25
+ ObservabilityLevel,
26
+ )
23
27
  from haiway.context.state import ScopeState, StateContext
24
28
  from haiway.context.tasks import TaskGroupContext
25
29
  from haiway.state import State
26
30
  from haiway.utils import mimic_function
27
31
  from haiway.utils.stream import AsyncStream
28
32
 
29
- __all__ = [
30
- "ctx",
31
- ]
33
+ __all__ = ("ctx",)
32
34
 
33
35
 
34
36
  @final
@@ -36,8 +38,7 @@ class ScopeContext:
36
38
  __slots__ = (
37
39
  "_disposables",
38
40
  "_identifier",
39
- "_logger_context",
40
- "_metrics_context",
41
+ "_observability_context",
41
42
  "_state_context",
42
43
  "_task_group_context",
43
44
  )
@@ -45,11 +46,10 @@ class ScopeContext:
45
46
  def __init__(
46
47
  self,
47
48
  label: str,
48
- logger: Logger | None,
49
49
  task_group: TaskGroup | None,
50
50
  state: tuple[State, ...],
51
51
  disposables: Disposables | None,
52
- metrics: MetricsHandler | None,
52
+ observability: Observability | Logger | None,
53
53
  ) -> None:
54
54
  self._identifier: ScopeIdentifier
55
55
  object.__setattr__(
@@ -57,23 +57,6 @@ class ScopeContext:
57
57
  "_identifier",
58
58
  ScopeIdentifier.scope(label),
59
59
  )
60
- self._logger_context: LoggerContext
61
- object.__setattr__(
62
- self,
63
- "_logger_context",
64
- LoggerContext(
65
- self._identifier,
66
- logger=logger,
67
- ),
68
- )
69
- self._task_group_context: TaskGroupContext | None
70
- object.__setattr__(
71
- self,
72
- "_task_group_context",
73
- TaskGroupContext(task_group=task_group)
74
- if task_group is not None or self._identifier.is_root
75
- else None,
76
- )
77
60
  # prepare state context to capture current state
78
61
  self._state_context: StateContext
79
62
  object.__setattr__(
@@ -87,16 +70,24 @@ class ScopeContext:
87
70
  "_disposables",
88
71
  disposables,
89
72
  )
90
- self._metrics_context: MetricsContext
73
+ self._observability_context: ObservabilityContext
91
74
  object.__setattr__(
92
75
  self,
93
- "_metrics_context",
94
- # pre-building metrics context to ensure nested context registering
95
- MetricsContext.scope(
76
+ "_observability_context",
77
+ # pre-building observability context to ensure nested context registering
78
+ ObservabilityContext.scope(
96
79
  self._identifier,
97
- metrics=metrics,
80
+ observability=observability,
98
81
  ),
99
82
  )
83
+ self._task_group_context: TaskGroupContext | None
84
+ object.__setattr__(
85
+ self,
86
+ "_task_group_context",
87
+ TaskGroupContext(task_group=task_group)
88
+ if task_group is not None or self._identifier.is_root
89
+ else None,
90
+ )
100
91
 
101
92
  def __setattr__(
102
93
  self,
@@ -123,9 +114,8 @@ class ScopeContext:
123
114
  ), "Can't enter synchronous context with task group"
124
115
  assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
125
116
  self._identifier.__enter__()
126
- self._logger_context.__enter__()
117
+ self._observability_context.__enter__()
127
118
  self._state_context.__enter__()
128
- self._metrics_context.__enter__()
129
119
 
130
120
  return self._identifier.trace_id
131
121
 
@@ -135,24 +125,16 @@ class ScopeContext:
135
125
  exc_val: BaseException | None,
136
126
  exc_tb: TracebackType | None,
137
127
  ) -> None:
138
- self._metrics_context.__exit__(
139
- exc_type=exc_type,
140
- exc_val=exc_val,
141
- exc_tb=exc_tb,
142
- )
143
-
144
128
  self._state_context.__exit__(
145
129
  exc_type=exc_type,
146
130
  exc_val=exc_val,
147
131
  exc_tb=exc_tb,
148
132
  )
149
-
150
- self._logger_context.__exit__(
133
+ self._observability_context.__exit__(
151
134
  exc_type=exc_type,
152
135
  exc_val=exc_val,
153
136
  exc_tb=exc_tb,
154
137
  )
155
-
156
138
  self._identifier.__exit__(
157
139
  exc_type=exc_type,
158
140
  exc_val=exc_val,
@@ -161,7 +143,7 @@ class ScopeContext:
161
143
 
162
144
  async def __aenter__(self) -> str:
163
145
  self._identifier.__enter__()
164
- self._logger_context.__enter__()
146
+ self._observability_context.__enter__()
165
147
 
166
148
  if task_group := self._task_group_context:
167
149
  await task_group.__aenter__()
@@ -183,7 +165,6 @@ class ScopeContext:
183
165
  )
184
166
 
185
167
  self._state_context.__enter__()
186
- self._metrics_context.__enter__()
187
168
 
188
169
  return self._identifier.trace_id
189
170
 
@@ -207,19 +188,13 @@ class ScopeContext:
207
188
  exc_tb=exc_tb,
208
189
  )
209
190
 
210
- self._metrics_context.__exit__(
211
- exc_type=exc_type,
212
- exc_val=exc_val,
213
- exc_tb=exc_tb,
214
- )
215
-
216
191
  self._state_context.__exit__(
217
192
  exc_type=exc_type,
218
193
  exc_val=exc_val,
219
194
  exc_tb=exc_tb,
220
195
  )
221
196
 
222
- self._logger_context.__exit__(
197
+ self._observability_context.__exit__(
223
198
  exc_type=exc_type,
224
199
  exc_val=exc_val,
225
200
  exc_tb=exc_tb,
@@ -288,9 +263,8 @@ class ctx:
288
263
  /,
289
264
  *state: State,
290
265
  disposables: Disposables | Iterable[Disposable] | None = None,
291
- logger: Logger | None = None,
292
266
  task_group: TaskGroup | None = None,
293
- metrics: MetricsHandler | None = None,
267
+ observability: Observability | Logger | None = None,
294
268
  ) -> ScopeContext:
295
269
  """
296
270
  Prepare scope context with given parameters. When called within an existing context\
@@ -310,18 +284,14 @@ class ctx:
310
284
  be added to the scope state. Using asynchronous context is required if any disposables\
311
285
  were provided.
312
286
 
313
- logger: Logger | None
314
- logger used within the scope context, when not provided current logger will be used\
315
- if any, otherwise the logger with the scope name will be requested.
316
-
317
287
  task_group: TaskGroup | None
318
288
  task group used for spawning and joining tasks within the context. Root scope will
319
289
  always have task group created even when not set.
320
290
 
321
- metrics_store: MetricsStore | None = None
322
- metrics storage solution responsible for recording and storing metrics.\
323
- Metrics recroding will be ignored if storage is not provided.
324
- Assigning metrics_store within existing context will result in an error.
291
+ observability: Observability | Logger | None = None
292
+ observability solution responsible for recording and storing metrics, logs and events.\
293
+ Assigning observability within existing context will result in an error.
294
+ When not provided, logger with the scope name will be requested and used.
325
295
 
326
296
  Returns
327
297
  -------
@@ -343,11 +313,10 @@ class ctx:
343
313
 
344
314
  return ScopeContext(
345
315
  label=label,
346
- logger=logger,
347
316
  task_group=task_group,
348
317
  state=state,
349
318
  disposables=resolved_disposables,
350
- metrics=metrics,
319
+ observability=observability,
351
320
  )
352
321
 
353
322
  @staticmethod
@@ -492,33 +461,13 @@ class ctx:
492
461
  default=default,
493
462
  )
494
463
 
495
- @staticmethod
496
- def record(
497
- metric: State,
498
- /,
499
- ) -> None:
500
- """
501
- Record metric within current scope context.
502
-
503
- Parameters
504
- ----------
505
- metric: State
506
- value of metric to be recorded. When a metric implements __add__ it will be added to\
507
- current value if any, otherwise subsequent calls may replace existing value.
508
-
509
- Returns
510
- -------
511
- None
512
- """
513
-
514
- MetricsContext.record(metric)
515
-
516
464
  @staticmethod
517
465
  def log_error(
518
466
  message: str,
519
467
  /,
520
468
  *args: Any,
521
469
  exception: BaseException | None = None,
470
+ **extra: Any,
522
471
  ) -> None:
523
472
  """
524
473
  Log using ERROR level within current scope context. When there is no current scope\
@@ -540,10 +489,12 @@ class ctx:
540
489
  None
541
490
  """
542
491
 
543
- LoggerContext.log_error(
492
+ ObservabilityContext.record_log(
493
+ ObservabilityLevel.ERROR,
544
494
  message,
545
495
  *args,
546
496
  exception=exception,
497
+ **extra,
547
498
  )
548
499
 
549
500
  @staticmethod
@@ -552,6 +503,7 @@ class ctx:
552
503
  /,
553
504
  *args: Any,
554
505
  exception: Exception | None = None,
506
+ **extra: Any,
555
507
  ) -> None:
556
508
  """
557
509
  Log using WARNING level within current scope context. When there is no current scope\
@@ -573,10 +525,12 @@ class ctx:
573
525
  None
574
526
  """
575
527
 
576
- LoggerContext.log_warning(
528
+ ObservabilityContext.record_log(
529
+ ObservabilityLevel.WARNING,
577
530
  message,
578
531
  *args,
579
532
  exception=exception,
533
+ **extra,
580
534
  )
581
535
 
582
536
  @staticmethod
@@ -584,6 +538,7 @@ class ctx:
584
538
  message: str,
585
539
  /,
586
540
  *args: Any,
541
+ **extra: Any,
587
542
  ) -> None:
588
543
  """
589
544
  Log using INFO level within current scope context. When there is no current scope\
@@ -602,9 +557,12 @@ class ctx:
602
557
  None
603
558
  """
604
559
 
605
- LoggerContext.log_info(
560
+ ObservabilityContext.record_log(
561
+ ObservabilityLevel.INFO,
606
562
  message,
607
563
  *args,
564
+ exception=None,
565
+ **extra,
608
566
  )
609
567
 
610
568
  @staticmethod
@@ -613,6 +571,7 @@ class ctx:
613
571
  /,
614
572
  *args: Any,
615
573
  exception: Exception | None = None,
574
+ **extra: Any,
616
575
  ) -> None:
617
576
  """
618
577
  Log using DEBUG level within current scope context. When there is no current scope\
@@ -634,8 +593,85 @@ class ctx:
634
593
  None
635
594
  """
636
595
 
637
- LoggerContext.log_debug(
638
- message,
639
- *args,
640
- exception=exception,
596
+ ObservabilityContext.record_log(
597
+ ObservabilityLevel.DEBUG, message, *args, exception=exception, **extra
598
+ )
599
+
600
+ @staticmethod
601
+ def event(
602
+ event: State,
603
+ /,
604
+ *,
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.
615
+
616
+ Returns
617
+ -------
618
+ None
619
+ """
620
+
621
+ ObservabilityContext.record_event(
622
+ event,
623
+ level=level,
624
+ **extra,
625
+ )
626
+
627
+ @staticmethod
628
+ def metric(
629
+ metric: str,
630
+ /,
631
+ *,
632
+ value: float | int,
633
+ 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
+ )
659
+
660
+ @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.
669
+
670
+ Returns
671
+ -------
672
+ None
673
+ """
674
+
675
+ ObservabilityContext.record_attributes(
676
+ **attributes,
641
677
  )
@@ -7,10 +7,10 @@ from typing import Any, final
7
7
 
8
8
  from haiway.state import State
9
9
 
10
- __all__ = [
10
+ __all__ = (
11
11
  "Disposable",
12
12
  "Disposables",
13
- ]
13
+ )
14
14
 
15
15
  type Disposable = AbstractAsyncContextManager[Iterable[State] | State | None]
16
16
 
@@ -3,9 +3,7 @@ from types import TracebackType
3
3
  from typing import Any, Self, final
4
4
  from uuid import uuid4
5
5
 
6
- __all__ = [
7
- "ScopeIdentifier",
8
- ]
6
+ __all__ = ("ScopeIdentifier",)
9
7
 
10
8
 
11
9
  @final
@@ -33,10 +31,11 @@ class ScopeIdentifier:
33
31
  except LookupError:
34
32
  # create root scope when missing
35
33
  trace_id: str = uuid4().hex
34
+ scope_id: str = uuid4().hex
36
35
  return cls(
37
36
  label=label,
38
- scope_id=uuid4().hex,
39
- parent_id=trace_id, # trace_id is parent_id for root
37
+ scope_id=scope_id,
38
+ parent_id=scope_id, # own id is parent_id for root
40
39
  trace_id=trace_id,
41
40
  )
42
41