haiway 0.18.1__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
@@ -10,9 +10,9 @@ from types import TracebackType
10
10
  from typing import Any, Final, Protocol, Self, final, runtime_checkable
11
11
 
12
12
  from haiway.context.identifier import ScopeIdentifier
13
-
14
- # from haiway.context.logging import LoggerContext
15
13
  from haiway.state import State
14
+ from haiway.types import Missing
15
+ from haiway.utils.formatting import format_str
16
16
 
17
17
  __all__ = (
18
18
  "DEBUG",
@@ -46,7 +46,16 @@ INFO: Final[int] = ObservabilityLevel.INFO
46
46
  DEBUG: Final[int] = ObservabilityLevel.DEBUG
47
47
 
48
48
  type ObservabilityAttribute = (
49
- Sequence[str] | Sequence[float] | Sequence[int] | Sequence[bool] | str | float | int | bool
49
+ Sequence[str]
50
+ | Sequence[float]
51
+ | Sequence[int]
52
+ | Sequence[bool]
53
+ | str
54
+ | float
55
+ | int
56
+ | bool
57
+ | None
58
+ | Missing
50
59
  )
51
60
 
52
61
 
@@ -60,7 +69,6 @@ class ObservabilityLogRecording(Protocol):
60
69
  message: str,
61
70
  *args: Any,
62
71
  exception: BaseException | None,
63
- **extra: Any,
64
72
  ) -> None: ...
65
73
 
66
74
 
@@ -70,10 +78,10 @@ class ObservabilityEventRecording(Protocol):
70
78
  self,
71
79
  scope: ScopeIdentifier,
72
80
  /,
73
- *,
74
81
  level: ObservabilityLevel,
75
- event: State,
76
- **extra: Any,
82
+ *,
83
+ event: str,
84
+ attributes: Mapping[str, ObservabilityAttribute],
77
85
  ) -> None: ...
78
86
 
79
87
 
@@ -83,11 +91,12 @@ class ObservabilityMetricRecording(Protocol):
83
91
  self,
84
92
  scope: ScopeIdentifier,
85
93
  /,
94
+ level: ObservabilityLevel,
86
95
  *,
87
96
  metric: str,
88
97
  value: float | int,
89
98
  unit: str | None,
90
- **extra: Any,
99
+ attributes: Mapping[str, ObservabilityAttribute],
91
100
  ) -> None: ...
92
101
 
93
102
 
@@ -97,7 +106,8 @@ class ObservabilityAttributesRecording(Protocol):
97
106
  self,
98
107
  scope: ScopeIdentifier,
99
108
  /,
100
- **attributes: ObservabilityAttribute,
109
+ level: ObservabilityLevel,
110
+ attributes: Mapping[str, ObservabilityAttribute],
101
111
  ) -> None: ...
102
112
 
103
113
 
@@ -209,7 +219,6 @@ def _logger_observability(
209
219
  message: str,
210
220
  *args: Any,
211
221
  exception: BaseException | None,
212
- **extra: Any,
213
222
  ) -> None:
214
223
  logger.log(
215
224
  level,
@@ -221,42 +230,51 @@ def _logger_observability(
221
230
  def event_recording(
222
231
  scope: ScopeIdentifier,
223
232
  /,
224
- *,
225
233
  level: ObservabilityLevel,
226
- event: State,
227
- **extra: Any,
234
+ *,
235
+ event: str,
236
+ attributes: Mapping[str, ObservabilityAttribute],
228
237
  ) -> None:
229
238
  logger.log(
230
239
  level,
231
- f"{scope.unique_name} Recorded event:\n{event.to_str(pretty=True)}",
240
+ f"{scope.unique_name} Recorded event: {event} {format_str(attributes)}",
232
241
  )
233
242
 
234
243
  def metric_recording(
235
244
  scope: ScopeIdentifier,
236
245
  /,
246
+ level: ObservabilityLevel,
237
247
  *,
238
248
  metric: str,
239
249
  value: float | int,
240
250
  unit: str | None,
241
- **extra: Any,
251
+ attributes: Mapping[str, ObservabilityAttribute],
242
252
  ) -> None:
243
- logger.log(
244
- INFO,
245
- f"{scope.unique_name} Recorded metric: {metric}={value}{unit or ''}",
246
- )
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
+ )
247
265
 
248
266
  def attributes_recording(
249
267
  scope: ScopeIdentifier,
250
268
  /,
251
- **attributes: ObservabilityAttribute,
269
+ level: ObservabilityLevel,
270
+ attributes: Mapping[str, ObservabilityAttribute],
252
271
  ) -> None:
253
272
  if not attributes:
254
273
  return
255
274
 
256
275
  logger.log(
257
- INFO,
258
- f"{scope.unique_name} Recorded attributes:"
259
- f"\n{'\n'.join([f'{k}: {v}' for k, v in attributes.items()])}",
276
+ level,
277
+ f"{scope.unique_name} Recorded attributes: {format_str(attributes)}",
260
278
  )
261
279
 
262
280
  def scope_entering[Metric: State](
@@ -349,7 +367,6 @@ class ObservabilityContext:
349
367
  /,
350
368
  *args: Any,
351
369
  exception: BaseException | None,
352
- **extra: Any,
353
370
  ) -> None:
354
371
  try: # catch exceptions - we don't wan't to blow up on observability
355
372
  context: Self = cls._context.get()
@@ -361,7 +378,6 @@ class ObservabilityContext:
361
378
  message,
362
379
  *args,
363
380
  exception=exception,
364
- **extra,
365
381
  )
366
382
 
367
383
  except LookupError:
@@ -375,11 +391,11 @@ class ObservabilityContext:
375
391
  @classmethod
376
392
  def record_event(
377
393
  cls,
378
- event: State,
394
+ level: ObservabilityLevel,
395
+ event: str,
379
396
  /,
380
397
  *,
381
- level: ObservabilityLevel,
382
- **extra: Any,
398
+ attributes: Mapping[str, ObservabilityAttribute],
383
399
  ) -> None:
384
400
  try: # catch exceptions - we don't wan't to blow up on observability
385
401
  context: Self = cls._context.get()
@@ -389,7 +405,7 @@ class ObservabilityContext:
389
405
  context._scope,
390
406
  level=level,
391
407
  event=event,
392
- **extra,
408
+ attributes=attributes,
393
409
  )
394
410
 
395
411
  except Exception as exc:
@@ -402,12 +418,13 @@ class ObservabilityContext:
402
418
  @classmethod
403
419
  def record_metric(
404
420
  cls,
421
+ level: ObservabilityLevel,
405
422
  metric: str,
406
423
  /,
407
424
  *,
408
425
  value: float | int,
409
426
  unit: str | None,
410
- **extra: Any,
427
+ attributes: Mapping[str, ObservabilityAttribute],
411
428
  ) -> None:
412
429
  try: # catch exceptions - we don't wan't to blow up on observability
413
430
  context: Self = cls._context.get()
@@ -415,10 +432,11 @@ class ObservabilityContext:
415
432
  if context.observability is not None:
416
433
  context.observability.metric_recording(
417
434
  context._scope,
435
+ level=level,
418
436
  metric=metric,
419
437
  value=value,
420
438
  unit=unit,
421
- **extra,
439
+ attributes=attributes,
422
440
  )
423
441
 
424
442
  except Exception as exc:
@@ -431,7 +449,10 @@ class ObservabilityContext:
431
449
  @classmethod
432
450
  def record_attributes(
433
451
  cls,
434
- **attributes: ObservabilityAttribute,
452
+ level: ObservabilityLevel,
453
+ /,
454
+ *,
455
+ attributes: Mapping[str, ObservabilityAttribute],
435
456
  ) -> None:
436
457
  try: # catch exceptions - we don't wan't to blow up on observability
437
458
  context: Self = cls._context.get()
@@ -439,13 +460,14 @@ class ObservabilityContext:
439
460
  if context.observability is not None:
440
461
  context.observability.attributes_recording(
441
462
  context._scope,
442
- **attributes,
463
+ level=level,
464
+ attributes=attributes,
443
465
  )
444
466
 
445
467
  except Exception as exc:
446
468
  cls.record_log(
447
469
  ERROR,
448
- f"Failed to record attributes: {attributes}",
470
+ "Failed to record attributes",
449
471
  exception=exc,
450
472
  )
451
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,10 +1,12 @@
1
- from collections.abc import Sequence
2
- from logging import Logger
1
+ from collections.abc import Mapping
2
+ from logging import Logger, getLogger
3
3
  from time import monotonic
4
4
  from typing import Any
5
5
 
6
6
  from haiway.context import Observability, ObservabilityLevel, ScopeIdentifier
7
+ from haiway.context.observability import ObservabilityAttribute
7
8
  from haiway.state import State
9
+ from haiway.utils.formatting import format_str
8
10
 
9
11
  __all__ = ("LoggerObservability",)
10
12
 
@@ -62,12 +64,13 @@ class ScopeStore:
62
64
 
63
65
 
64
66
  def LoggerObservability( # noqa: C901, PLR0915
65
- logger: Logger,
67
+ logger: Logger | None = None,
66
68
  /,
67
69
  *,
68
- summarize_context: bool = __debug__,
70
+ debug_context: bool = __debug__,
69
71
  ) -> Observability:
70
72
  root_scope: ScopeIdentifier | None = None
73
+ root_logger: Logger | None = logger
71
74
  scopes: dict[str, ScopeStore] = {}
72
75
 
73
76
  def log_recording(
@@ -77,12 +80,12 @@ def LoggerObservability( # noqa: C901, PLR0915
77
80
  message: str,
78
81
  *args: Any,
79
82
  exception: BaseException | None,
80
- **extra: Any,
81
83
  ) -> None:
82
84
  assert root_scope is not None # nosec: B101
85
+ assert root_logger is not None # nosec: B101
83
86
  assert scope.scope_id in scopes # nosec: B101
84
87
 
85
- logger.log(
88
+ root_logger.log(
86
89
  level,
87
90
  f"{scope.unique_name} {message}",
88
91
  *args,
@@ -92,19 +95,20 @@ def LoggerObservability( # noqa: C901, PLR0915
92
95
  def event_recording(
93
96
  scope: ScopeIdentifier,
94
97
  /,
95
- *,
96
98
  level: ObservabilityLevel,
97
- event: State,
98
- **extra: Any,
99
+ *,
100
+ event: str,
101
+ attributes: Mapping[str, ObservabilityAttribute],
99
102
  ) -> None:
100
103
  assert root_scope is not None # nosec: B101
104
+ assert root_logger is not None # nosec: B101
101
105
  assert scope.scope_id in scopes # nosec: B101
102
106
 
103
- event_str: str = f"Event:\n{event.to_str(pretty=True)}"
104
- 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
105
109
  scopes[scope.scope_id].store.append(event_str)
106
110
 
107
- logger.log(
111
+ root_logger.log(
108
112
  level,
109
113
  f"{scope.unique_name} {event_str}",
110
114
  )
@@ -112,41 +116,50 @@ def LoggerObservability( # noqa: C901, PLR0915
112
116
  def metric_recording(
113
117
  scope: ScopeIdentifier,
114
118
  /,
119
+ level: ObservabilityLevel,
115
120
  *,
116
121
  metric: str,
117
122
  value: float | int,
118
123
  unit: str | None,
119
- **extra: Any,
124
+ attributes: Mapping[str, ObservabilityAttribute],
120
125
  ) -> None:
121
126
  assert root_scope is not None # nosec: B101
127
+ assert root_logger is not None # nosec: B101
122
128
  assert scope.scope_id in scopes # nosec: B101
123
129
 
124
- metric_str: str = f"Metric: {metric}={value}{unit or ''}"
125
- 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
126
138
  scopes[scope.scope_id].store.append(metric_str)
127
139
 
128
- logger.log(
129
- ObservabilityLevel.INFO,
140
+ root_logger.log(
141
+ level,
130
142
  f"{scope.unique_name} {metric_str}",
131
143
  )
132
144
 
133
145
  def attributes_recording(
134
146
  scope: ScopeIdentifier,
135
147
  /,
136
- **attributes: Sequence[str | float | int] | str | float | int,
148
+ level: ObservabilityLevel,
149
+ attributes: Mapping[str, ObservabilityAttribute],
137
150
  ) -> None:
151
+ assert root_scope is not None # nosec: B101
152
+ assert root_logger is not None # nosec: B101
153
+
138
154
  if not attributes:
139
155
  return
140
156
 
141
- attributes_str: str = (
142
- f"{scope.unique_name} Attributes:"
143
- f"\n{'\n'.join([f'{k}: {v}' for k, v in attributes.items()])}"
144
- )
145
- if summarize_context: # store only for summary
157
+ attributes_str: str = f"Attributes: {format_str(attributes)}"
158
+ if debug_context: # store only for summary
146
159
  scopes[scope.scope_id].store.append(attributes_str)
147
160
 
148
- logger.log(
149
- ObservabilityLevel.INFO,
161
+ root_logger.log(
162
+ level,
150
163
  attributes_str,
151
164
  )
152
165
 
@@ -158,18 +171,21 @@ def LoggerObservability( # noqa: C901, PLR0915
158
171
  scope_store: ScopeStore = ScopeStore(scope)
159
172
  scopes[scope.scope_id] = scope_store
160
173
 
161
- logger.log(
162
- ObservabilityLevel.INFO,
163
- f"{scope.unique_name} Entering scope: {scope.label}",
164
- )
165
-
166
174
  nonlocal root_scope
175
+ nonlocal root_logger
167
176
  if root_scope is None:
168
177
  root_scope = scope
178
+ root_logger = logger or getLogger(scope.label)
169
179
 
170
180
  else:
171
181
  scopes[scope.parent_id].nested.append(scope_store)
172
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
+
173
189
  def scope_exiting[Metric: State](
174
190
  scope: ScopeIdentifier,
175
191
  /,
@@ -177,8 +193,10 @@ def LoggerObservability( # noqa: C901, PLR0915
177
193
  exception: BaseException | None,
178
194
  ) -> None:
179
195
  nonlocal root_scope
196
+ nonlocal root_logger
180
197
  nonlocal scopes
181
198
  assert root_scope is not None # nosec: B101
199
+ assert root_logger is not None # nosec: B101
182
200
  assert scope.scope_id in scopes # nosec: B101
183
201
 
184
202
  scopes[scope.scope_id].exit()
@@ -186,15 +204,15 @@ def LoggerObservability( # noqa: C901, PLR0915
186
204
  if not scopes[scope.scope_id].try_complete():
187
205
  return # not completed yet or already completed
188
206
 
189
- logger.log(
207
+ root_logger.log(
190
208
  ObservabilityLevel.INFO,
191
209
  f"{scope.unique_name} Exiting scope: {scope.label}",
192
210
  )
193
211
  metric_str: str = f"Metric - scope_time:{scopes[scope.scope_id].time:.3f}s"
194
- if summarize_context: # store only for summary
212
+ if debug_context: # store only for summary
195
213
  scopes[scope.scope_id].store.append(metric_str)
196
214
 
197
- logger.log(
215
+ root_logger.log(
198
216
  ObservabilityLevel.INFO,
199
217
  f"{scope.unique_name} {metric_str}",
200
218
  )
@@ -206,14 +224,15 @@ def LoggerObservability( # noqa: C901, PLR0915
206
224
 
207
225
  # check for root completion
208
226
  if scopes[root_scope.scope_id].completed:
209
- if summarize_context:
210
- logger.log(
227
+ if debug_context:
228
+ root_logger.log(
211
229
  ObservabilityLevel.DEBUG,
212
230
  f"Observability summary:\n{_tree_summary(scopes[root_scope.scope_id])}",
213
231
  )
214
232
 
215
233
  # finished root - cleanup state
216
234
  root_scope = None
235
+ root_logger = None
217
236
  scopes = {}
218
237
 
219
238
  return Observability(