haiway 0.3.1__py3-none-any.whl → 0.3.2__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 +13 -1
- haiway/context/metrics.py +23 -8
- haiway/helpers/__init__.py +4 -0
- haiway/helpers/tracing.py +136 -0
- haiway/state/validation.py +25 -1
- haiway/types/missing.py +4 -1
- {haiway-0.3.1.dist-info → haiway-0.3.2.dist-info}/METADATA +1 -1
- {haiway-0.3.1.dist-info → haiway-0.3.2.dist-info}/RECORD +11 -10
- {haiway-0.3.1.dist-info → haiway-0.3.2.dist-info}/WHEEL +1 -1
- {haiway-0.3.1.dist-info → haiway-0.3.2.dist-info}/LICENSE +0 -0
- {haiway-0.3.1.dist-info → haiway-0.3.2.dist-info}/top_level.txt +0 -0
haiway/__init__.py
CHANGED
@@ -6,7 +6,16 @@ from haiway.context import (
|
|
6
6
|
ScopeMetrics,
|
7
7
|
ctx,
|
8
8
|
)
|
9
|
-
from haiway.helpers import
|
9
|
+
from haiway.helpers import (
|
10
|
+
ArgumentsTrace,
|
11
|
+
ResultTrace,
|
12
|
+
asynchronous,
|
13
|
+
cache,
|
14
|
+
retry,
|
15
|
+
throttle,
|
16
|
+
timeout,
|
17
|
+
traced,
|
18
|
+
)
|
10
19
|
from haiway.state import State
|
11
20
|
from haiway.types import (
|
12
21
|
MISSING,
|
@@ -34,6 +43,7 @@ from haiway.utils import (
|
|
34
43
|
|
35
44
|
__all__ = [
|
36
45
|
"always",
|
46
|
+
"ArgumentsTrace",
|
37
47
|
"async_always",
|
38
48
|
"async_noop",
|
39
49
|
"asynchronous",
|
@@ -57,11 +67,13 @@ __all__ = [
|
|
57
67
|
"MissingState",
|
58
68
|
"noop",
|
59
69
|
"not_missing",
|
70
|
+
"ResultTrace",
|
60
71
|
"retry",
|
61
72
|
"ScopeMetrics",
|
62
73
|
"setup_logging",
|
63
74
|
"State",
|
64
75
|
"throttle",
|
65
76
|
"timeout",
|
77
|
+
"traced",
|
66
78
|
"when_missing",
|
67
79
|
]
|
haiway/context/metrics.py
CHANGED
@@ -10,6 +10,7 @@ from typing import Any, Self, cast, final, overload
|
|
10
10
|
from uuid import uuid4
|
11
11
|
|
12
12
|
from haiway.state import State
|
13
|
+
from haiway.types import MISSING, Missing, not_missing
|
13
14
|
from haiway.utils import freeze
|
14
15
|
|
15
16
|
__all__ = [
|
@@ -28,7 +29,13 @@ class ScopeMetrics:
|
|
28
29
|
logger: Logger | None,
|
29
30
|
) -> None:
|
30
31
|
self.trace_id: str = trace_id or uuid4().hex
|
31
|
-
self.
|
32
|
+
self.identifier: str = uuid4().hex
|
33
|
+
self.label: str = scope
|
34
|
+
self._logger_prefix: str = (
|
35
|
+
f"[{self.trace_id}] [{scope}] [{self.identifier}]"
|
36
|
+
if scope
|
37
|
+
else f"[{self.trace_id}] [{self.identifier}]"
|
38
|
+
)
|
32
39
|
self._logger: Logger = logger or getLogger(name=scope)
|
33
40
|
self._metrics: dict[type[State], State] = {}
|
34
41
|
self._nested: list[ScopeMetrics] = []
|
@@ -41,21 +48,29 @@ class ScopeMetrics:
|
|
41
48
|
self._complete() # ensure completion on deinit
|
42
49
|
|
43
50
|
def __str__(self) -> str:
|
44
|
-
return self.
|
51
|
+
return f"{self.label}[{self.identifier}]@[{self.trace_id}]"
|
45
52
|
|
46
53
|
def metrics(
|
47
54
|
self,
|
48
55
|
*,
|
49
|
-
merge: Callable[[State, State], State]
|
56
|
+
merge: Callable[[State | Missing, State], State | Missing] | None = None,
|
50
57
|
) -> list[State]:
|
58
|
+
if not merge:
|
59
|
+
return list(self._metrics.values())
|
60
|
+
|
51
61
|
metrics: dict[type[State], State] = copy(self._metrics)
|
52
62
|
for metric in chain.from_iterable(nested.metrics(merge=merge) for nested in self._nested):
|
53
63
|
metric_type: type[State] = type(metric)
|
54
|
-
|
55
|
-
metrics
|
64
|
+
merged: State | Missing = merge(
|
65
|
+
metrics.get( # current
|
66
|
+
metric_type,
|
67
|
+
MISSING,
|
68
|
+
),
|
69
|
+
metric, # received
|
70
|
+
)
|
56
71
|
|
57
|
-
|
58
|
-
metrics[metric_type] =
|
72
|
+
if not_missing(merged):
|
73
|
+
metrics[metric_type] = merged
|
59
74
|
|
60
75
|
return list(metrics.values())
|
61
76
|
|
@@ -145,7 +160,7 @@ class ScopeMetrics:
|
|
145
160
|
) -> None:
|
146
161
|
self._logger.log(
|
147
162
|
level,
|
148
|
-
f"
|
163
|
+
f"{self._logger_prefix} {message}",
|
149
164
|
*args,
|
150
165
|
exc_info=exception,
|
151
166
|
)
|
haiway/helpers/__init__.py
CHANGED
@@ -3,11 +3,15 @@ from haiway.helpers.caching import cache
|
|
3
3
|
from haiway.helpers.retries import retry
|
4
4
|
from haiway.helpers.throttling import throttle
|
5
5
|
from haiway.helpers.timeouted import timeout
|
6
|
+
from haiway.helpers.tracing import ArgumentsTrace, ResultTrace, traced
|
6
7
|
|
7
8
|
__all__ = [
|
9
|
+
"ArgumentsTrace",
|
8
10
|
"asynchronous",
|
9
11
|
"cache",
|
12
|
+
"ResultTrace",
|
10
13
|
"retry",
|
11
14
|
"throttle",
|
12
15
|
"timeout",
|
16
|
+
"traced",
|
13
17
|
]
|
@@ -0,0 +1,136 @@
|
|
1
|
+
from asyncio import iscoroutinefunction
|
2
|
+
from collections.abc import Callable, Coroutine
|
3
|
+
from typing import Any, Self, cast
|
4
|
+
|
5
|
+
from haiway.context import ctx
|
6
|
+
from haiway.state import State
|
7
|
+
from haiway.types import MISSING, Missing
|
8
|
+
from haiway.utils import mimic_function
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"traced",
|
12
|
+
"ArgumentsTrace",
|
13
|
+
"ResultTrace",
|
14
|
+
]
|
15
|
+
|
16
|
+
|
17
|
+
class ArgumentsTrace(State):
|
18
|
+
if __debug__:
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def of(cls, *args: Any, **kwargs: Any) -> Self:
|
22
|
+
return cls(
|
23
|
+
args=args if args else MISSING,
|
24
|
+
kwargs=kwargs if kwargs else MISSING,
|
25
|
+
)
|
26
|
+
|
27
|
+
else: # remove tracing for non debug runs to prevent accidental secret leaks
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def of(cls, *args: Any, **kwargs: Any) -> Self:
|
31
|
+
return cls(
|
32
|
+
args=MISSING,
|
33
|
+
kwargs=MISSING,
|
34
|
+
)
|
35
|
+
|
36
|
+
args: tuple[Any, ...] | Missing
|
37
|
+
kwargs: dict[str, Any] | Missing
|
38
|
+
|
39
|
+
|
40
|
+
class ResultTrace(State):
|
41
|
+
if __debug__:
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def of(
|
45
|
+
cls,
|
46
|
+
value: Any,
|
47
|
+
/,
|
48
|
+
) -> Self:
|
49
|
+
return cls(result=value)
|
50
|
+
|
51
|
+
else: # remove tracing for non debug runs to prevent accidental secret leaks
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def of(
|
55
|
+
cls,
|
56
|
+
value: Any,
|
57
|
+
/,
|
58
|
+
) -> Self:
|
59
|
+
return cls(result=MISSING)
|
60
|
+
|
61
|
+
result: Any | Missing
|
62
|
+
|
63
|
+
|
64
|
+
def traced[**Args, Result](
|
65
|
+
function: Callable[Args, Result],
|
66
|
+
/,
|
67
|
+
) -> Callable[Args, Result]:
|
68
|
+
if __debug__:
|
69
|
+
if iscoroutinefunction(function):
|
70
|
+
return cast(
|
71
|
+
Callable[Args, Result],
|
72
|
+
_traced_async(
|
73
|
+
function,
|
74
|
+
label=function.__name__,
|
75
|
+
),
|
76
|
+
)
|
77
|
+
else:
|
78
|
+
return _traced_sync(
|
79
|
+
function,
|
80
|
+
label=function.__name__,
|
81
|
+
)
|
82
|
+
|
83
|
+
else: # do not trace on non debug runs
|
84
|
+
return function
|
85
|
+
|
86
|
+
|
87
|
+
def _traced_sync[**Args, Result](
|
88
|
+
function: Callable[Args, Result],
|
89
|
+
/,
|
90
|
+
label: str,
|
91
|
+
) -> Callable[Args, Result]:
|
92
|
+
def traced(
|
93
|
+
*args: Args.args,
|
94
|
+
**kwargs: Args.kwargs,
|
95
|
+
) -> Result:
|
96
|
+
with ctx.scope(label):
|
97
|
+
ctx.record(ArgumentsTrace.of(*args, **kwargs))
|
98
|
+
try:
|
99
|
+
result: Result = function(*args, **kwargs)
|
100
|
+
ctx.record(ResultTrace.of(result))
|
101
|
+
return result
|
102
|
+
|
103
|
+
except BaseException as exc:
|
104
|
+
ctx.record(ResultTrace.of(exc))
|
105
|
+
raise exc
|
106
|
+
|
107
|
+
return mimic_function(
|
108
|
+
function,
|
109
|
+
within=traced,
|
110
|
+
)
|
111
|
+
|
112
|
+
|
113
|
+
def _traced_async[**Args, Result](
|
114
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
115
|
+
/,
|
116
|
+
label: str,
|
117
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
118
|
+
async def traced(
|
119
|
+
*args: Args.args,
|
120
|
+
**kwargs: Args.kwargs,
|
121
|
+
) -> Result:
|
122
|
+
with ctx.scope(label):
|
123
|
+
ctx.record(ArgumentsTrace.of(*args, **kwargs))
|
124
|
+
try:
|
125
|
+
result: Result = await function(*args, **kwargs)
|
126
|
+
ctx.record(ResultTrace.of(result))
|
127
|
+
return result
|
128
|
+
|
129
|
+
except BaseException as exc:
|
130
|
+
ctx.record(ResultTrace.of(exc))
|
131
|
+
raise exc
|
132
|
+
|
133
|
+
return mimic_function(
|
134
|
+
function,
|
135
|
+
within=traced,
|
136
|
+
)
|
haiway/state/validation.py
CHANGED
@@ -11,7 +11,7 @@ __all__ = [
|
|
11
11
|
]
|
12
12
|
|
13
13
|
|
14
|
-
def attribute_type_validator(
|
14
|
+
def attribute_type_validator( # noqa: PLR0911
|
15
15
|
annotation: AttributeAnnotation,
|
16
16
|
/,
|
17
17
|
) -> Callable[[Any], Any]:
|
@@ -31,6 +31,10 @@ def attribute_type_validator(
|
|
31
31
|
case typing.Any:
|
32
32
|
return _any_validator
|
33
33
|
|
34
|
+
# typed dicts fail on type checks
|
35
|
+
case typed_dict if typing.is_typeddict(typed_dict):
|
36
|
+
return _prepare_typed_dict_validator(typed_dict)
|
37
|
+
|
34
38
|
case type() as other_type:
|
35
39
|
return _prepare_type_validator(other_type)
|
36
40
|
|
@@ -123,3 +127,23 @@ def _prepare_type_validator(
|
|
123
127
|
)
|
124
128
|
|
125
129
|
return type_validator
|
130
|
+
|
131
|
+
|
132
|
+
def _prepare_typed_dict_validator(
|
133
|
+
validated_type: type[Any],
|
134
|
+
/,
|
135
|
+
) -> Callable[[Any], Any]:
|
136
|
+
def typed_dict_validator(
|
137
|
+
value: Any,
|
138
|
+
) -> Any:
|
139
|
+
match value:
|
140
|
+
case value if isinstance(value, dict):
|
141
|
+
# for typed dicts check only if that is a dict
|
142
|
+
return value # pyright: ignore[reportUnknownVariableType]
|
143
|
+
|
144
|
+
case _:
|
145
|
+
raise TypeError(
|
146
|
+
f"Type '{type(value)}' is not matching expected type '{validated_type}'"
|
147
|
+
)
|
148
|
+
|
149
|
+
return typed_dict_validator
|
haiway/types/missing.py
CHANGED
@@ -27,6 +27,9 @@ class Missing(metaclass=MissingType):
|
|
27
27
|
Type representing absence of a value. Use MISSING constant for its value.
|
28
28
|
"""
|
29
29
|
|
30
|
+
__slots__ = ()
|
31
|
+
__match_args__ = ()
|
32
|
+
|
30
33
|
def __bool__(self) -> bool:
|
31
34
|
return False
|
32
35
|
|
@@ -42,7 +45,7 @@ class Missing(metaclass=MissingType):
|
|
42
45
|
def __repr__(self) -> str:
|
43
46
|
return "MISSING"
|
44
47
|
|
45
|
-
def
|
48
|
+
def __getattr__(
|
46
49
|
self,
|
47
50
|
name: str,
|
48
51
|
) -> Any:
|
@@ -1,25 +1,26 @@
|
|
1
|
-
haiway/__init__.py,sha256=
|
1
|
+
haiway/__init__.py,sha256=hLc3-FDmNQEV4r-RLOiGjWtYSk7krU8vRMBaZyRU08g,1267
|
2
2
|
haiway/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
haiway/context/__init__.py,sha256=21Y3zvRo1bHASZD6B_FNkU28k1-g88RdmUyqxvYXJxg,336
|
4
4
|
haiway/context/access.py,sha256=E9aIC1RUupKk2LXik5qb13oHpW2s_tNQC0UxGcO9xr0,12761
|
5
5
|
haiway/context/disposables.py,sha256=VQX9jVo1pjqkmOYzWpsbYyF45y0XtpjorIIaeMBCwTU,1771
|
6
|
-
haiway/context/metrics.py,sha256=
|
6
|
+
haiway/context/metrics.py,sha256=TGZzNrmGPolAwVb1mBEF10mXRyVyBq-MHTucAokNuhY,9105
|
7
7
|
haiway/context/state.py,sha256=GxGwPQTK8FdSprBd83lQbA9veubp0o93_1Yk3gb7HMc,3000
|
8
8
|
haiway/context/tasks.py,sha256=xXtXIUwXOra0EePTdkcEbMOmpWwFcO3hCRfR_IfvAHk,1978
|
9
9
|
haiway/context/types.py,sha256=VvJA7wAPZ3ISpgyThVguioYUXqhHf0XkPfRd0M1ERiQ,142
|
10
|
-
haiway/helpers/__init__.py,sha256=
|
10
|
+
haiway/helpers/__init__.py,sha256=YYEORuo3xyce5_kGjfQVyaQHyp1dBh3ZlrZavR0hQzk,443
|
11
11
|
haiway/helpers/asynchrony.py,sha256=FPqmFFRDtAn8migwYHFKViKHypNHZW3cJCrh9y03AwI,5526
|
12
12
|
haiway/helpers/caching.py,sha256=Ok_WE5Whe7XqnIuLZo4rNNBFeWap-aUWX799s4b1JAQ,9536
|
13
13
|
haiway/helpers/retries.py,sha256=gIkyUlqJLDYaxIZd3qzeqGFY9y5Gp8dgZLlZ6hs8hoc,7538
|
14
14
|
haiway/helpers/throttling.py,sha256=zo0OwFq64si5KUwhd58cFHLmGAmYwRbFRJMbv9suhPs,3844
|
15
15
|
haiway/helpers/timeouted.py,sha256=1xU09hQnFdj6p48BwZl5xUvtIr3zC0ZUXehkdrduCjs,3074
|
16
|
+
haiway/helpers/tracing.py,sha256=yiK8MdDyX_fmpK9Zu5-IiZae5E8ReKQtRBBenXIPVqQ,3326
|
16
17
|
haiway/state/__init__.py,sha256=dh7l_ZImy0uHHDGD-fzMhQFmz_ej8WU8WEE2OmIoyVM,204
|
17
18
|
haiway/state/attributes.py,sha256=kkIYNlvWCM1NkgiCbE6gZDwgBVOk_TkmqWv67MmU0to,13399
|
18
19
|
haiway/state/structure.py,sha256=G-Ln72hoQtE0FmKHeZdNmXf_FA3f5-e5AGbmJ2yMNb4,7003
|
19
|
-
haiway/state/validation.py,sha256=
|
20
|
+
haiway/state/validation.py,sha256=Z6kp_KjTnnP9eVWsLmzKkEQLZkhFCOSphjdbr6VxLFQ,3628
|
20
21
|
haiway/types/__init__.py,sha256=cAJQzDgFi8AKRqpzY3HWrutaPR69tnJqeJK_mQVtYUk,252
|
21
22
|
haiway/types/frozen.py,sha256=CZhFCXnWAKEhuWSfILxA8smfdpMd5Ku694ycfLh98R8,76
|
22
|
-
haiway/types/missing.py,sha256=
|
23
|
+
haiway/types/missing.py,sha256=JiXo5xdi7H-PbIJr0fuK5wpOuQZhjrDYUkMlfIFcsaE,1705
|
23
24
|
haiway/utils/__init__.py,sha256=UA9h8YDvYI5rYujvsIS9t5Q-SWYImmk30uhR_42flqs,608
|
24
25
|
haiway/utils/always.py,sha256=2abp8Lm9rQkrfS3rm1Iqhb-IcWyVfH1BULab3KMxgOw,1234
|
25
26
|
haiway/utils/env.py,sha256=lKPOBZWyRD_gQariDGBjVLYTm0740nytPCSQpK2oRyE,3136
|
@@ -28,8 +29,8 @@ haiway/utils/logs.py,sha256=oDsc1ZdqKDjlTlctLbDcp9iX98Acr-1tdw-Pyg3DElo,1577
|
|
28
29
|
haiway/utils/mimic.py,sha256=BkVjTVP2TxxC8GChPGyDV6UXVwJmiRiSWeOYZNZFHxs,1828
|
29
30
|
haiway/utils/noop.py,sha256=qgbZlOKWY6_23Zs43OLukK2HagIQKRyR04zrFVm5rWI,344
|
30
31
|
haiway/utils/queue.py,sha256=WGW8kSusIwRYHsYRIKD2CaqhhC1pUtVgtNHFDXDtYrw,2443
|
31
|
-
haiway-0.3.
|
32
|
-
haiway-0.3.
|
33
|
-
haiway-0.3.
|
34
|
-
haiway-0.3.
|
35
|
-
haiway-0.3.
|
32
|
+
haiway-0.3.2.dist-info/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
|
33
|
+
haiway-0.3.2.dist-info/METADATA,sha256=Ze8RmuhIj2Ck3SVea8h3VbaTSf43_Y57oISLZKytNOA,3872
|
34
|
+
haiway-0.3.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
35
|
+
haiway-0.3.2.dist-info/top_level.txt,sha256=_LdXVLzUzgkvAGQnQJj5kQfoFhpPW6EF4Kj9NapniLg,7
|
36
|
+
haiway-0.3.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|