haiway 0.16.0__py3-none-any.whl → 0.18.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 +18 -18
- haiway/context/__init__.py +19 -15
- haiway/context/access.py +92 -144
- haiway/context/disposables.py +2 -2
- haiway/context/identifier.py +4 -5
- haiway/context/observability.py +452 -0
- haiway/context/state.py +2 -2
- haiway/context/tasks.py +1 -3
- haiway/context/types.py +2 -2
- haiway/helpers/__init__.py +7 -6
- haiway/helpers/asynchrony.py +2 -2
- haiway/helpers/caching.py +2 -2
- haiway/helpers/observability.py +219 -0
- haiway/helpers/retries.py +1 -3
- haiway/helpers/throttling.py +1 -3
- haiway/helpers/timeouted.py +1 -3
- haiway/helpers/tracing.py +25 -17
- haiway/opentelemetry/__init__.py +3 -0
- haiway/opentelemetry/observability.py +420 -0
- haiway/state/__init__.py +2 -2
- haiway/state/attributes.py +2 -2
- haiway/state/path.py +1 -3
- haiway/state/requirement.py +1 -3
- haiway/state/structure.py +161 -30
- haiway/state/validation.py +2 -2
- haiway/types/__init__.py +2 -2
- haiway/types/default.py +2 -2
- haiway/types/frozen.py +1 -3
- haiway/types/missing.py +2 -2
- haiway/utils/__init__.py +2 -2
- haiway/utils/always.py +2 -2
- haiway/utils/collections.py +2 -2
- haiway/utils/env.py +2 -2
- haiway/utils/freezing.py +1 -3
- haiway/utils/logs.py +1 -3
- haiway/utils/mimic.py +1 -3
- haiway/utils/noop.py +2 -2
- haiway/utils/queue.py +1 -3
- haiway/utils/stream.py +1 -3
- {haiway-0.16.0.dist-info → haiway-0.18.0.dist-info}/METADATA +9 -5
- haiway-0.18.0.dist-info/RECORD +44 -0
- haiway/context/logging.py +0 -242
- haiway/context/metrics.py +0 -214
- haiway/helpers/metrics.py +0 -501
- haiway-0.16.0.dist-info/RECORD +0 -43
- {haiway-0.16.0.dist-info → haiway-0.18.0.dist-info}/WHEEL +0 -0
- {haiway-0.16.0.dist-info → haiway-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,452 @@
|
|
1
|
+
from contextvars import ContextVar, Token
|
2
|
+
from enum import IntEnum
|
3
|
+
from logging import DEBUG as DEBUG_LOGGING
|
4
|
+
from logging import ERROR as ERROR_LOGGING
|
5
|
+
from logging import INFO as INFO_LOGGING
|
6
|
+
from logging import WARNING as WARNING_LOGGING
|
7
|
+
from logging import Logger, getLogger
|
8
|
+
from types import TracebackType
|
9
|
+
from typing import Any, Final, Protocol, Self, final, runtime_checkable
|
10
|
+
|
11
|
+
from haiway.context.identifier import ScopeIdentifier
|
12
|
+
|
13
|
+
# from haiway.context.logging import LoggerContext
|
14
|
+
from haiway.state import State
|
15
|
+
|
16
|
+
__all__ = (
|
17
|
+
"DEBUG",
|
18
|
+
"ERROR",
|
19
|
+
"INFO",
|
20
|
+
"WARNING",
|
21
|
+
"Observability",
|
22
|
+
"ObservabilityContext",
|
23
|
+
"ObservabilityEventRecording",
|
24
|
+
"ObservabilityLevel",
|
25
|
+
"ObservabilityLogRecording",
|
26
|
+
"ObservabilityMetricRecording",
|
27
|
+
"ObservabilityScopeEntering",
|
28
|
+
"ObservabilityScopeExiting",
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
class ObservabilityLevel(IntEnum):
|
33
|
+
# values from logging package
|
34
|
+
ERROR = ERROR_LOGGING
|
35
|
+
WARNING = WARNING_LOGGING
|
36
|
+
INFO = INFO_LOGGING
|
37
|
+
DEBUG = DEBUG_LOGGING
|
38
|
+
|
39
|
+
|
40
|
+
ERROR: Final[int] = ObservabilityLevel.ERROR
|
41
|
+
WARNING: Final[int] = ObservabilityLevel.WARNING
|
42
|
+
INFO: Final[int] = ObservabilityLevel.INFO
|
43
|
+
DEBUG: Final[int] = ObservabilityLevel.DEBUG
|
44
|
+
|
45
|
+
|
46
|
+
@runtime_checkable
|
47
|
+
class ObservabilityLogRecording(Protocol):
|
48
|
+
def __call__(
|
49
|
+
self,
|
50
|
+
scope: ScopeIdentifier,
|
51
|
+
/,
|
52
|
+
level: ObservabilityLevel,
|
53
|
+
message: str,
|
54
|
+
*args: Any,
|
55
|
+
exception: BaseException | None,
|
56
|
+
) -> None: ...
|
57
|
+
|
58
|
+
|
59
|
+
@runtime_checkable
|
60
|
+
class ObservabilityEventRecording(Protocol):
|
61
|
+
def __call__(
|
62
|
+
self,
|
63
|
+
scope: ScopeIdentifier,
|
64
|
+
/,
|
65
|
+
*,
|
66
|
+
level: ObservabilityLevel,
|
67
|
+
event: State,
|
68
|
+
) -> None: ...
|
69
|
+
|
70
|
+
|
71
|
+
@runtime_checkable
|
72
|
+
class ObservabilityMetricRecording(Protocol):
|
73
|
+
def __call__(
|
74
|
+
self,
|
75
|
+
scope: ScopeIdentifier,
|
76
|
+
/,
|
77
|
+
*,
|
78
|
+
metric: str,
|
79
|
+
value: float | int,
|
80
|
+
unit: str | None,
|
81
|
+
) -> None: ...
|
82
|
+
|
83
|
+
|
84
|
+
@runtime_checkable
|
85
|
+
class ObservabilityScopeEntering(Protocol):
|
86
|
+
def __call__[Metric: State](
|
87
|
+
self,
|
88
|
+
scope: ScopeIdentifier,
|
89
|
+
/,
|
90
|
+
) -> None: ...
|
91
|
+
|
92
|
+
|
93
|
+
@runtime_checkable
|
94
|
+
class ObservabilityScopeExiting(Protocol):
|
95
|
+
def __call__[Metric: State](
|
96
|
+
self,
|
97
|
+
scope: ScopeIdentifier,
|
98
|
+
/,
|
99
|
+
*,
|
100
|
+
exception: BaseException | None,
|
101
|
+
) -> None: ...
|
102
|
+
|
103
|
+
|
104
|
+
class Observability: # avoiding State inheritance to prevent propagation as scope state
|
105
|
+
__slots__ = (
|
106
|
+
"event_recording",
|
107
|
+
"log_recording",
|
108
|
+
"metric_recording",
|
109
|
+
"scope_entering",
|
110
|
+
"scope_exiting",
|
111
|
+
)
|
112
|
+
|
113
|
+
def __init__(
|
114
|
+
self,
|
115
|
+
log_recording: ObservabilityLogRecording,
|
116
|
+
metric_recording: ObservabilityMetricRecording,
|
117
|
+
event_recording: ObservabilityEventRecording,
|
118
|
+
scope_entering: ObservabilityScopeEntering,
|
119
|
+
scope_exiting: ObservabilityScopeExiting,
|
120
|
+
) -> None:
|
121
|
+
self.log_recording: ObservabilityLogRecording
|
122
|
+
object.__setattr__(
|
123
|
+
self,
|
124
|
+
"log_recording",
|
125
|
+
log_recording,
|
126
|
+
)
|
127
|
+
self.metric_recording: ObservabilityMetricRecording
|
128
|
+
object.__setattr__(
|
129
|
+
self,
|
130
|
+
"metric_recording",
|
131
|
+
metric_recording,
|
132
|
+
)
|
133
|
+
self.event_recording: ObservabilityEventRecording
|
134
|
+
object.__setattr__(
|
135
|
+
self,
|
136
|
+
"event_recording",
|
137
|
+
event_recording,
|
138
|
+
)
|
139
|
+
self.scope_entering: ObservabilityScopeEntering
|
140
|
+
object.__setattr__(
|
141
|
+
self,
|
142
|
+
"scope_entering",
|
143
|
+
scope_entering,
|
144
|
+
)
|
145
|
+
self.scope_exiting: ObservabilityScopeExiting
|
146
|
+
object.__setattr__(
|
147
|
+
self,
|
148
|
+
"scope_exiting",
|
149
|
+
scope_exiting,
|
150
|
+
)
|
151
|
+
|
152
|
+
def __setattr__(
|
153
|
+
self,
|
154
|
+
name: str,
|
155
|
+
value: Any,
|
156
|
+
) -> Any:
|
157
|
+
raise AttributeError(
|
158
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
159
|
+
f" attribute - '{name}' cannot be modified"
|
160
|
+
)
|
161
|
+
|
162
|
+
def __delattr__(
|
163
|
+
self,
|
164
|
+
name: str,
|
165
|
+
) -> None:
|
166
|
+
raise AttributeError(
|
167
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
168
|
+
f" attribute - '{name}' cannot be deleted"
|
169
|
+
)
|
170
|
+
|
171
|
+
|
172
|
+
def _logger_observability(
|
173
|
+
logger: Logger,
|
174
|
+
/,
|
175
|
+
) -> Observability:
|
176
|
+
def log_recording(
|
177
|
+
scope: ScopeIdentifier,
|
178
|
+
/,
|
179
|
+
level: ObservabilityLevel,
|
180
|
+
message: str,
|
181
|
+
*args: Any,
|
182
|
+
exception: BaseException | None,
|
183
|
+
) -> None:
|
184
|
+
logger.log(
|
185
|
+
level,
|
186
|
+
f"{scope.unique_name} {message}",
|
187
|
+
*args,
|
188
|
+
exc_info=exception,
|
189
|
+
)
|
190
|
+
|
191
|
+
def event_recording(
|
192
|
+
scope: ScopeIdentifier,
|
193
|
+
/,
|
194
|
+
*,
|
195
|
+
level: ObservabilityLevel,
|
196
|
+
event: State,
|
197
|
+
) -> None:
|
198
|
+
logger.log(
|
199
|
+
level,
|
200
|
+
f"{scope.unique_name} Recorded event:\n{event.to_str(pretty=True)}",
|
201
|
+
)
|
202
|
+
|
203
|
+
def metric_recording(
|
204
|
+
scope: ScopeIdentifier,
|
205
|
+
/,
|
206
|
+
*,
|
207
|
+
metric: str,
|
208
|
+
value: float | int,
|
209
|
+
unit: str | None,
|
210
|
+
) -> None:
|
211
|
+
logger.log(
|
212
|
+
INFO,
|
213
|
+
f"{scope.unique_name} Recorded metric - {metric}:{value}{unit or ''}",
|
214
|
+
)
|
215
|
+
|
216
|
+
def scope_entering[Metric: State](
|
217
|
+
scope: ScopeIdentifier,
|
218
|
+
/,
|
219
|
+
) -> None:
|
220
|
+
logger.log(
|
221
|
+
DEBUG,
|
222
|
+
f"{scope.unique_name} Entering scope: {scope.label}",
|
223
|
+
)
|
224
|
+
|
225
|
+
def scope_exiting[Metric: State](
|
226
|
+
scope: ScopeIdentifier,
|
227
|
+
/,
|
228
|
+
*,
|
229
|
+
exception: BaseException | None,
|
230
|
+
) -> None:
|
231
|
+
logger.log(
|
232
|
+
DEBUG,
|
233
|
+
f"{scope.unique_name} Exiting scope: {scope.label}",
|
234
|
+
exc_info=exception,
|
235
|
+
)
|
236
|
+
|
237
|
+
return Observability(
|
238
|
+
log_recording=log_recording,
|
239
|
+
event_recording=event_recording,
|
240
|
+
metric_recording=metric_recording,
|
241
|
+
scope_entering=scope_entering,
|
242
|
+
scope_exiting=scope_exiting,
|
243
|
+
)
|
244
|
+
|
245
|
+
|
246
|
+
@final
|
247
|
+
class ObservabilityContext:
|
248
|
+
_context = ContextVar[Self]("ObservabilityContext")
|
249
|
+
|
250
|
+
@classmethod
|
251
|
+
def scope(
|
252
|
+
cls,
|
253
|
+
scope: ScopeIdentifier,
|
254
|
+
/,
|
255
|
+
*,
|
256
|
+
observability: Observability | Logger | None,
|
257
|
+
) -> Self:
|
258
|
+
current: Self
|
259
|
+
try: # check for current scope
|
260
|
+
current = cls._context.get()
|
261
|
+
|
262
|
+
except LookupError:
|
263
|
+
resolved_observability: Observability
|
264
|
+
match observability:
|
265
|
+
case Observability() as observability:
|
266
|
+
resolved_observability = observability
|
267
|
+
|
268
|
+
case None:
|
269
|
+
resolved_observability = _logger_observability(getLogger(scope.label))
|
270
|
+
|
271
|
+
case Logger() as logger:
|
272
|
+
resolved_observability = _logger_observability(logger)
|
273
|
+
|
274
|
+
# create root scope when missing
|
275
|
+
return cls(
|
276
|
+
scope=scope,
|
277
|
+
observability=resolved_observability,
|
278
|
+
)
|
279
|
+
|
280
|
+
# create nested scope otherwise
|
281
|
+
resolved_observability: Observability
|
282
|
+
match observability:
|
283
|
+
case None:
|
284
|
+
resolved_observability = current.observability
|
285
|
+
|
286
|
+
case Logger() as logger:
|
287
|
+
resolved_observability = _logger_observability(logger)
|
288
|
+
|
289
|
+
case observability:
|
290
|
+
resolved_observability = observability
|
291
|
+
|
292
|
+
return cls(
|
293
|
+
scope=scope,
|
294
|
+
observability=resolved_observability,
|
295
|
+
)
|
296
|
+
|
297
|
+
@classmethod
|
298
|
+
def record_log(
|
299
|
+
cls,
|
300
|
+
level: ObservabilityLevel,
|
301
|
+
message: str,
|
302
|
+
/,
|
303
|
+
*args: Any,
|
304
|
+
exception: BaseException | None,
|
305
|
+
) -> None:
|
306
|
+
try: # catch exceptions - we don't wan't to blow up on observability
|
307
|
+
context: Self = cls._context.get()
|
308
|
+
|
309
|
+
if context.observability is not None:
|
310
|
+
context.observability.log_recording(
|
311
|
+
context._scope,
|
312
|
+
level,
|
313
|
+
message,
|
314
|
+
*args,
|
315
|
+
exception=exception,
|
316
|
+
)
|
317
|
+
|
318
|
+
except LookupError:
|
319
|
+
getLogger().log(
|
320
|
+
level,
|
321
|
+
message,
|
322
|
+
*args,
|
323
|
+
exc_info=exception,
|
324
|
+
)
|
325
|
+
|
326
|
+
@classmethod
|
327
|
+
def record_event(
|
328
|
+
cls,
|
329
|
+
event: State,
|
330
|
+
/,
|
331
|
+
*,
|
332
|
+
level: ObservabilityLevel,
|
333
|
+
) -> None:
|
334
|
+
try: # catch exceptions - we don't wan't to blow up on observability
|
335
|
+
context: Self = cls._context.get()
|
336
|
+
|
337
|
+
if context.observability is not None:
|
338
|
+
context.observability.event_recording(
|
339
|
+
context._scope,
|
340
|
+
level=level,
|
341
|
+
event=event,
|
342
|
+
)
|
343
|
+
|
344
|
+
except Exception as exc:
|
345
|
+
cls.record_log(
|
346
|
+
ERROR,
|
347
|
+
f"Failed to record event: {type(event).__qualname__}",
|
348
|
+
exception=exc,
|
349
|
+
)
|
350
|
+
|
351
|
+
@classmethod
|
352
|
+
def record_metric(
|
353
|
+
cls,
|
354
|
+
metric: str,
|
355
|
+
/,
|
356
|
+
*,
|
357
|
+
value: float | int,
|
358
|
+
unit: str | None,
|
359
|
+
) -> None:
|
360
|
+
try: # catch exceptions - we don't wan't to blow up on observability
|
361
|
+
context: Self = cls._context.get()
|
362
|
+
|
363
|
+
if context.observability is not None:
|
364
|
+
context.observability.metric_recording(
|
365
|
+
context._scope,
|
366
|
+
metric=metric,
|
367
|
+
value=value,
|
368
|
+
unit=unit,
|
369
|
+
)
|
370
|
+
|
371
|
+
except Exception as exc:
|
372
|
+
cls.record_log(
|
373
|
+
ERROR,
|
374
|
+
f"Failed to record metric: {metric}",
|
375
|
+
exception=exc,
|
376
|
+
)
|
377
|
+
|
378
|
+
__slots__ = (
|
379
|
+
"_scope",
|
380
|
+
"_token",
|
381
|
+
"observability",
|
382
|
+
)
|
383
|
+
|
384
|
+
def __init__(
|
385
|
+
self,
|
386
|
+
scope: ScopeIdentifier,
|
387
|
+
observability: Observability | None,
|
388
|
+
) -> None:
|
389
|
+
self._scope: ScopeIdentifier
|
390
|
+
object.__setattr__(
|
391
|
+
self,
|
392
|
+
"_scope",
|
393
|
+
scope,
|
394
|
+
)
|
395
|
+
self.observability: Observability
|
396
|
+
object.__setattr__(
|
397
|
+
self,
|
398
|
+
"observability",
|
399
|
+
observability,
|
400
|
+
)
|
401
|
+
self._token: Token[ObservabilityContext] | None
|
402
|
+
object.__setattr__(
|
403
|
+
self,
|
404
|
+
"_token",
|
405
|
+
None,
|
406
|
+
)
|
407
|
+
|
408
|
+
def __setattr__(
|
409
|
+
self,
|
410
|
+
name: str,
|
411
|
+
value: Any,
|
412
|
+
) -> Any:
|
413
|
+
raise AttributeError(
|
414
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
415
|
+
f" attribute - '{name}' cannot be modified"
|
416
|
+
)
|
417
|
+
|
418
|
+
def __delattr__(
|
419
|
+
self,
|
420
|
+
name: str,
|
421
|
+
) -> None:
|
422
|
+
raise AttributeError(
|
423
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
424
|
+
f" attribute - '{name}' cannot be deleted"
|
425
|
+
)
|
426
|
+
|
427
|
+
def __enter__(self) -> None:
|
428
|
+
assert self._token is None, "Context reentrance is not allowed" # nosec: B101
|
429
|
+
object.__setattr__(
|
430
|
+
self,
|
431
|
+
"_token",
|
432
|
+
ObservabilityContext._context.set(self),
|
433
|
+
)
|
434
|
+
self.observability.scope_entering(self._scope)
|
435
|
+
|
436
|
+
def __exit__(
|
437
|
+
self,
|
438
|
+
exc_type: type[BaseException] | None,
|
439
|
+
exc_val: BaseException | None,
|
440
|
+
exc_tb: TracebackType | None,
|
441
|
+
) -> None:
|
442
|
+
assert self._token is not None, "Unbalanced context enter/exit" # nosec: B101
|
443
|
+
ObservabilityContext._context.reset(self._token)
|
444
|
+
object.__setattr__(
|
445
|
+
self,
|
446
|
+
"_token",
|
447
|
+
None,
|
448
|
+
)
|
449
|
+
self.observability.scope_exiting(
|
450
|
+
self._scope,
|
451
|
+
exception=exc_val,
|
452
|
+
)
|
haiway/context/state.py
CHANGED
haiway/context/tasks.py
CHANGED
haiway/context/types.py
CHANGED
haiway/helpers/__init__.py
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
from haiway.helpers.asynchrony import asynchronous, wrap_async
|
2
2
|
from haiway.helpers.caching import CacheMakeKey, CacheRead, CacheWrite, cache
|
3
|
-
from haiway.helpers.metrics import MetricsHolder, MetricsLogger
|
4
3
|
from haiway.helpers.retries import retry
|
5
4
|
from haiway.helpers.throttling import throttle
|
6
5
|
from haiway.helpers.timeouted import timeout
|
7
|
-
from haiway.helpers.tracing import
|
6
|
+
from haiway.helpers.tracing import (
|
7
|
+
ArgumentsTrace,
|
8
|
+
ResultTrace,
|
9
|
+
traced,
|
10
|
+
)
|
8
11
|
|
9
|
-
__all__ =
|
12
|
+
__all__ = (
|
10
13
|
"ArgumentsTrace",
|
11
14
|
"CacheMakeKey",
|
12
15
|
"CacheRead",
|
13
16
|
"CacheWrite",
|
14
|
-
"MetricsHolder",
|
15
|
-
"MetricsLogger",
|
16
17
|
"ResultTrace",
|
17
18
|
"asynchronous",
|
18
19
|
"cache",
|
@@ -21,4 +22,4 @@ __all__ = [
|
|
21
22
|
"timeout",
|
22
23
|
"traced",
|
23
24
|
"wrap_async",
|
24
|
-
|
25
|
+
)
|
haiway/helpers/asynchrony.py
CHANGED
haiway/helpers/caching.py
CHANGED
@@ -8,12 +8,12 @@ from typing import Any, NamedTuple, Protocol, cast, overload
|
|
8
8
|
from haiway.context.access import ctx
|
9
9
|
from haiway.utils.mimic import mimic_function
|
10
10
|
|
11
|
-
__all__ =
|
11
|
+
__all__ = (
|
12
12
|
"CacheMakeKey",
|
13
13
|
"CacheRead",
|
14
14
|
"CacheWrite",
|
15
15
|
"cache",
|
16
|
-
|
16
|
+
)
|
17
17
|
|
18
18
|
|
19
19
|
class CacheMakeKey[**Args, Key](Protocol):
|