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 +0 -2
- haiway/context/access.py +53 -64
- haiway/context/observability.py +47 -33
- haiway/helpers/__init__.py +1 -2
- haiway/helpers/observability.py +53 -35
- haiway/helpers/tracing.py +35 -43
- haiway/opentelemetry/observability.py +53 -31
- haiway/state/structure.py +3 -151
- haiway/utils/__init__.py +2 -0
- haiway/utils/formatting.py +148 -0
- {haiway-0.18.2.dist-info → haiway-0.19.0.dist-info}/METADATA +1 -1
- {haiway-0.18.2.dist-info → haiway-0.19.0.dist-info}/RECORD +14 -13
- {haiway-0.18.2.dist-info → haiway-0.19.0.dist-info}/WHEEL +0 -0
- {haiway-0.18.2.dist-info → haiway-0.19.0.dist-info}/licenses/LICENSE +0 -0
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
|
602
|
-
|
603
|
+
def record(
|
604
|
+
level: ObservabilityLevel = ObservabilityLevel.DEBUG,
|
603
605
|
/,
|
604
606
|
*,
|
605
|
-
|
606
|
-
|
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
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
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
|
629
|
-
|
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
|
-
|
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
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
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
|
-
|
671
|
-
|
672
|
-
|
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
|
-
|
676
|
-
|
677
|
-
|
662
|
+
else:
|
663
|
+
ObservabilityContext.record_attributes(
|
664
|
+
level,
|
665
|
+
attributes=attributes or {},
|
666
|
+
)
|
haiway/context/observability.py
CHANGED
@@ -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
|
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
|
-
|
84
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
235
|
-
|
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
|
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
|
-
|
251
|
+
attributes: Mapping[str, ObservabilityAttribute],
|
250
252
|
) -> None:
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
394
|
+
level: ObservabilityLevel,
|
395
|
+
event: str,
|
387
396
|
/,
|
388
397
|
*,
|
389
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
470
|
+
"Failed to record attributes",
|
457
471
|
exception=exc,
|
458
472
|
)
|
459
473
|
|
haiway/helpers/__init__.py
CHANGED
@@ -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
|
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",
|
haiway/helpers/observability.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
from
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
99
|
-
|
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
|
105
|
-
if
|
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
|
-
|
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
|
-
|
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
|
126
|
-
if
|
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
|
-
|
130
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
150
|
-
|
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
|
-
|
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
|
212
|
+
if debug_context: # store only for summary
|
196
213
|
scopes[scope.scope_id].store.append(metric_str)
|
197
214
|
|
198
|
-
|
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
|
211
|
-
|
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,
|
3
|
+
from typing import Any, cast, overload
|
4
4
|
|
5
5
|
from haiway.context import ctx
|
6
|
-
from haiway.
|
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.
|
100
|
-
|
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.
|
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.
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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.
|
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.
|
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.
|
108
|
-
trace_id=self.
|
109
|
-
trace_flags=self.
|
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:
|
132
|
+
event: str,
|
131
133
|
/,
|
134
|
+
*,
|
135
|
+
attributes: Mapping[str, ObservabilityAttribute],
|
132
136
|
) -> None:
|
133
137
|
self.span.add_event(
|
134
|
-
|
135
|
-
attributes=
|
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=
|
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
|
-
|
297
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
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.
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
42
|
-
haiway-0.
|
43
|
-
haiway-0.
|
44
|
-
haiway-0.
|
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,,
|
File without changes
|
File without changes
|