haiway 0.18.2__py3-none-any.whl → 0.19.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.
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
@@ -7,17 +7,14 @@ from logging import INFO as INFO_LOGGING
7
7
  from logging import WARNING as WARNING_LOGGING
8
8
  from logging import Logger, getLogger
9
9
  from types import TracebackType
10
- from typing import Any, Final, Protocol, Self, final, runtime_checkable
10
+ from typing import Any, 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
- "DEBUG",
18
- "ERROR",
19
- "INFO",
20
- "WARNING",
21
18
  "Observability",
22
19
  "ObservabilityAttribute",
23
20
  "ObservabilityAttributesRecording",
@@ -39,11 +36,6 @@ class ObservabilityLevel(IntEnum):
39
36
  DEBUG = DEBUG_LOGGING
40
37
 
41
38
 
42
- ERROR: Final[int] = ObservabilityLevel.ERROR
43
- WARNING: Final[int] = ObservabilityLevel.WARNING
44
- INFO: Final[int] = ObservabilityLevel.INFO
45
- DEBUG: Final[int] = ObservabilityLevel.DEBUG
46
-
47
39
  type ObservabilityAttribute = (
48
40
  Sequence[str]
49
41
  | Sequence[float]
@@ -68,7 +60,6 @@ class ObservabilityLogRecording(Protocol):
68
60
  message: str,
69
61
  *args: Any,
70
62
  exception: BaseException | None,
71
- **extra: Any,
72
63
  ) -> None: ...
73
64
 
74
65
 
@@ -78,10 +69,10 @@ class ObservabilityEventRecording(Protocol):
78
69
  self,
79
70
  scope: ScopeIdentifier,
80
71
  /,
81
- *,
82
72
  level: ObservabilityLevel,
83
- event: State,
84
- **extra: Any,
73
+ *,
74
+ event: str,
75
+ attributes: Mapping[str, ObservabilityAttribute],
85
76
  ) -> None: ...
86
77
 
87
78
 
@@ -91,11 +82,12 @@ class ObservabilityMetricRecording(Protocol):
91
82
  self,
92
83
  scope: ScopeIdentifier,
93
84
  /,
85
+ level: ObservabilityLevel,
94
86
  *,
95
87
  metric: str,
96
88
  value: float | int,
97
89
  unit: str | None,
98
- **extra: Any,
90
+ attributes: Mapping[str, ObservabilityAttribute],
99
91
  ) -> None: ...
100
92
 
101
93
 
@@ -105,7 +97,8 @@ class ObservabilityAttributesRecording(Protocol):
105
97
  self,
106
98
  scope: ScopeIdentifier,
107
99
  /,
108
- **attributes: ObservabilityAttribute,
100
+ level: ObservabilityLevel,
101
+ attributes: Mapping[str, ObservabilityAttribute],
109
102
  ) -> None: ...
110
103
 
111
104
 
@@ -217,7 +210,6 @@ def _logger_observability(
217
210
  message: str,
218
211
  *args: Any,
219
212
  exception: BaseException | None,
220
- **extra: Any,
221
213
  ) -> None:
222
214
  logger.log(
223
215
  level,
@@ -229,42 +221,51 @@ def _logger_observability(
229
221
  def event_recording(
230
222
  scope: ScopeIdentifier,
231
223
  /,
232
- *,
233
224
  level: ObservabilityLevel,
234
- event: State,
235
- **extra: Any,
225
+ *,
226
+ event: str,
227
+ attributes: Mapping[str, ObservabilityAttribute],
236
228
  ) -> None:
237
229
  logger.log(
238
230
  level,
239
- f"{scope.unique_name} Recorded event:\n{event.to_str(pretty=True)}",
231
+ f"{scope.unique_name} Recorded event: {event} {format_str(attributes)}",
240
232
  )
241
233
 
242
234
  def metric_recording(
243
235
  scope: ScopeIdentifier,
244
236
  /,
237
+ level: ObservabilityLevel,
245
238
  *,
246
239
  metric: str,
247
240
  value: float | int,
248
241
  unit: str | None,
249
- **extra: Any,
242
+ attributes: Mapping[str, ObservabilityAttribute],
250
243
  ) -> None:
251
- logger.log(
252
- INFO,
253
- f"{scope.unique_name} Recorded metric: {metric}={value}{unit or ''}",
254
- )
244
+ if attributes:
245
+ logger.log(
246
+ level,
247
+ f"{scope.unique_name} Recorded metric: {metric}={value}{unit or ''}"
248
+ f"\n{format_str(attributes)}",
249
+ )
250
+
251
+ else:
252
+ logger.log(
253
+ level,
254
+ f"{scope.unique_name} Recorded metric: {metric}={value}{unit or ''}",
255
+ )
255
256
 
256
257
  def attributes_recording(
257
258
  scope: ScopeIdentifier,
258
259
  /,
259
- **attributes: ObservabilityAttribute,
260
+ level: ObservabilityLevel,
261
+ attributes: Mapping[str, ObservabilityAttribute],
260
262
  ) -> None:
261
263
  if not attributes:
262
264
  return
263
265
 
264
266
  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
267
+ level,
268
+ f"{scope.unique_name} Recorded attributes: {format_str(attributes)}",
268
269
  )
269
270
 
270
271
  def scope_entering[Metric: State](
@@ -272,7 +273,7 @@ def _logger_observability(
272
273
  /,
273
274
  ) -> None:
274
275
  logger.log(
275
- DEBUG,
276
+ ObservabilityLevel.DEBUG,
276
277
  f"{scope.unique_name} Entering scope: {scope.label}",
277
278
  )
278
279
 
@@ -283,7 +284,7 @@ def _logger_observability(
283
284
  exception: BaseException | None,
284
285
  ) -> None:
285
286
  logger.log(
286
- DEBUG,
287
+ ObservabilityLevel.DEBUG,
287
288
  f"{scope.unique_name} Exiting scope: {scope.label}",
288
289
  exc_info=exception,
289
290
  )
@@ -357,7 +358,6 @@ class ObservabilityContext:
357
358
  /,
358
359
  *args: Any,
359
360
  exception: BaseException | None,
360
- **extra: Any,
361
361
  ) -> None:
362
362
  try: # catch exceptions - we don't wan't to blow up on observability
363
363
  context: Self = cls._context.get()
@@ -369,7 +369,6 @@ class ObservabilityContext:
369
369
  message,
370
370
  *args,
371
371
  exception=exception,
372
- **extra,
373
372
  )
374
373
 
375
374
  except LookupError:
@@ -383,11 +382,11 @@ class ObservabilityContext:
383
382
  @classmethod
384
383
  def record_event(
385
384
  cls,
386
- event: State,
385
+ level: ObservabilityLevel,
386
+ event: str,
387
387
  /,
388
388
  *,
389
- level: ObservabilityLevel,
390
- **extra: Any,
389
+ attributes: Mapping[str, ObservabilityAttribute],
391
390
  ) -> None:
392
391
  try: # catch exceptions - we don't wan't to blow up on observability
393
392
  context: Self = cls._context.get()
@@ -397,12 +396,12 @@ class ObservabilityContext:
397
396
  context._scope,
398
397
  level=level,
399
398
  event=event,
400
- **extra,
399
+ attributes=attributes,
401
400
  )
402
401
 
403
402
  except Exception as exc:
404
403
  cls.record_log(
405
- ERROR,
404
+ ObservabilityLevel.ERROR,
406
405
  f"Failed to record event: {type(event).__qualname__}",
407
406
  exception=exc,
408
407
  )
@@ -410,12 +409,13 @@ class ObservabilityContext:
410
409
  @classmethod
411
410
  def record_metric(
412
411
  cls,
412
+ level: ObservabilityLevel,
413
413
  metric: str,
414
414
  /,
415
415
  *,
416
416
  value: float | int,
417
417
  unit: str | None,
418
- **extra: Any,
418
+ attributes: Mapping[str, ObservabilityAttribute],
419
419
  ) -> None:
420
420
  try: # catch exceptions - we don't wan't to blow up on observability
421
421
  context: Self = cls._context.get()
@@ -423,15 +423,16 @@ class ObservabilityContext:
423
423
  if context.observability is not None:
424
424
  context.observability.metric_recording(
425
425
  context._scope,
426
+ level=level,
426
427
  metric=metric,
427
428
  value=value,
428
429
  unit=unit,
429
- **extra,
430
+ attributes=attributes,
430
431
  )
431
432
 
432
433
  except Exception as exc:
433
434
  cls.record_log(
434
- ERROR,
435
+ ObservabilityLevel.ERROR,
435
436
  f"Failed to record metric: {metric}",
436
437
  exception=exc,
437
438
  )
@@ -439,7 +440,10 @@ class ObservabilityContext:
439
440
  @classmethod
440
441
  def record_attributes(
441
442
  cls,
442
- **attributes: ObservabilityAttribute,
443
+ level: ObservabilityLevel,
444
+ /,
445
+ *,
446
+ attributes: Mapping[str, ObservabilityAttribute],
443
447
  ) -> None:
444
448
  try: # catch exceptions - we don't wan't to blow up on observability
445
449
  context: Self = cls._context.get()
@@ -447,13 +451,14 @@ class ObservabilityContext:
447
451
  if context.observability is not None:
448
452
  context.observability.attributes_recording(
449
453
  context._scope,
450
- **attributes,
454
+ level=level,
455
+ attributes=attributes,
451
456
  )
452
457
 
453
458
  except Exception as exc:
454
459
  cls.record_log(
455
- ERROR,
456
- f"Failed to record attributes: {attributes}",
460
+ ObservabilityLevel.ERROR,
461
+ "Failed to record attributes",
457
462
  exception=exc,
458
463
  )
459
464
 
@@ -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(