haiway 0.17.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 -16
- haiway/context/__init__.py +19 -13
- haiway/context/access.py +92 -88
- 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.17.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 -176
- haiway/helpers/metrics.py +0 -465
- haiway-0.17.0.dist-info/RECORD +0 -43
- {haiway-0.17.0.dist-info → haiway-0.18.0.dist-info}/WHEEL +0 -0
- {haiway-0.17.0.dist-info → haiway-0.18.0.dist-info}/licenses/LICENSE +0 -0
haiway/types/__init__.py
CHANGED
@@ -2,7 +2,7 @@ from haiway.types.default import Default, DefaultValue
|
|
2
2
|
from haiway.types.frozen import frozenlist
|
3
3
|
from haiway.types.missing import MISSING, Missing, is_missing, not_missing, when_missing
|
4
4
|
|
5
|
-
__all__ =
|
5
|
+
__all__ = (
|
6
6
|
"MISSING",
|
7
7
|
"Default",
|
8
8
|
"DefaultValue",
|
@@ -11,4 +11,4 @@ __all__ = [
|
|
11
11
|
"is_missing",
|
12
12
|
"not_missing",
|
13
13
|
"when_missing",
|
14
|
-
|
14
|
+
)
|
haiway/types/default.py
CHANGED
haiway/types/frozen.py
CHANGED
haiway/types/missing.py
CHANGED
haiway/utils/__init__.py
CHANGED
@@ -15,7 +15,7 @@ from haiway.utils.noop import async_noop, noop
|
|
15
15
|
from haiway.utils.queue import AsyncQueue
|
16
16
|
from haiway.utils.stream import AsyncStream
|
17
17
|
|
18
|
-
__all__ =
|
18
|
+
__all__ = (
|
19
19
|
"AsyncQueue",
|
20
20
|
"AsyncStream",
|
21
21
|
"always",
|
@@ -36,4 +36,4 @@ __all__ = [
|
|
36
36
|
"noop",
|
37
37
|
"setup_logging",
|
38
38
|
"without_missing",
|
39
|
-
|
39
|
+
)
|
haiway/utils/always.py
CHANGED
haiway/utils/collections.py
CHANGED
haiway/utils/env.py
CHANGED
@@ -3,14 +3,14 @@ from collections.abc import Callable
|
|
3
3
|
from os import environ, getenv
|
4
4
|
from typing import Literal, overload
|
5
5
|
|
6
|
-
__all__ =
|
6
|
+
__all__ = (
|
7
7
|
"getenv_base64",
|
8
8
|
"getenv_bool",
|
9
9
|
"getenv_float",
|
10
10
|
"getenv_int",
|
11
11
|
"getenv_str",
|
12
12
|
"load_env",
|
13
|
-
|
13
|
+
)
|
14
14
|
|
15
15
|
|
16
16
|
@overload
|
haiway/utils/freezing.py
CHANGED
haiway/utils/logs.py
CHANGED
haiway/utils/mimic.py
CHANGED
haiway/utils/noop.py
CHANGED
haiway/utils/queue.py
CHANGED
haiway/utils/stream.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.18.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
|
@@ -35,12 +35,16 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
35
35
|
Classifier: Typing :: Typed
|
36
36
|
Requires-Python: >=3.12
|
37
37
|
Provides-Extra: dev
|
38
|
-
Requires-Dist: bandit~=1.
|
38
|
+
Requires-Dist: bandit~=1.8; extra == 'dev'
|
39
39
|
Requires-Dist: pyright~=1.1; extra == 'dev'
|
40
|
-
Requires-Dist: pytest-asyncio~=0.
|
41
|
-
Requires-Dist: pytest-cov~=
|
42
|
-
Requires-Dist: pytest~=
|
40
|
+
Requires-Dist: pytest-asyncio~=0.26; extra == 'dev'
|
41
|
+
Requires-Dist: pytest-cov~=6.1; extra == 'dev'
|
42
|
+
Requires-Dist: pytest~=8.3; extra == 'dev'
|
43
43
|
Requires-Dist: ruff~=0.11; extra == 'dev'
|
44
|
+
Provides-Extra: opentelemetry
|
45
|
+
Requires-Dist: opentelemetry-api; extra == 'opentelemetry'
|
46
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc; extra == 'opentelemetry'
|
47
|
+
Requires-Dist: opentelemetry-sdk; extra == 'opentelemetry'
|
44
48
|
Description-Content-Type: text/markdown
|
45
49
|
|
46
50
|
# 🚗 haiway 🚕 🚚 🚙
|
@@ -0,0 +1,44 @@
|
|
1
|
+
haiway/__init__.py,sha256=0ShohKDFyEyUpBpSfJWDFGopBWOdhdWyX3wC9tgmkVU,2243
|
2
|
+
haiway/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
haiway/context/__init__.py,sha256=hPQ7e-4515zD7vSEtSvzZpZ6rAqGceyE3jeVH3eE80M,966
|
4
|
+
haiway/context/access.py,sha256=Oxl5Hs6jDvD6DMt8Im8MpIXswXaFW5PGWqGF9rsryGE,17951
|
5
|
+
haiway/context/disposables.py,sha256=xE8RZYsYgXiOZY_TjWR7UiPG6dirna6y2LBZvMwTkIs,2588
|
6
|
+
haiway/context/identifier.py,sha256=i6nO-tozps7iDnpS5Se7CRch7hh6z2akjZthutxHte8,3943
|
7
|
+
haiway/context/observability.py,sha256=rkq2YYMSQE3ryXdX_OvUxsWP9G4B6wHJM0_PIgURwo4,11666
|
8
|
+
haiway/context/state.py,sha256=0oq7ctNO0urJd7rVzwwNtgpguoXuI6Tp1exfCsxrS2M,5981
|
9
|
+
haiway/context/tasks.py,sha256=QOxFdjmMp4IYff0ihHElKLCQrcVksSJmxqTlOKfoH4o,2907
|
10
|
+
haiway/context/types.py,sha256=WulPvpqUbI1vYyny-s2NItldDnk3zh1O-n_hGibFZRY,142
|
11
|
+
haiway/helpers/__init__.py,sha256=bVOCI0RHh6JNY_B8aJyw-fh1pDD10WnCPR8nVGX4NA0,582
|
12
|
+
haiway/helpers/asynchrony.py,sha256=k_A0yCWUKSFfzYZ8WvqK4wqTMljv6ykMivmERrDLHIU,6266
|
13
|
+
haiway/helpers/caching.py,sha256=4WX2Md5AOncduYB_RLLENI2s9C2zD5kNJgKZjMzPIGY,13257
|
14
|
+
haiway/helpers/observability.py,sha256=PdlTR8OGxbkWcVcnTAhpr2OzvgM2Oig1B0KhFKvQjLI,6241
|
15
|
+
haiway/helpers/retries.py,sha256=unssUKBDOENvquh6R4Ud65TuSKl4mTHgZ5N_b7mAYa4,7533
|
16
|
+
haiway/helpers/throttling.py,sha256=U6HJvSzffw47730VeiXxXSW4VVxpDx48k0oIAOpL-O4,4115
|
17
|
+
haiway/helpers/timeouted.py,sha256=_M8diuD_GN49pl5KQA5fMKn4iUHsUuhkDSatAwWXiK8,3331
|
18
|
+
haiway/helpers/tracing.py,sha256=UpwhNEoTefy1oplz_tqMFrN2AcmPRkKU7UYVENVykM0,4161
|
19
|
+
haiway/opentelemetry/__init__.py,sha256=TV-1C14mDAtcHhFZ29ActFQdrGH6x5KuGV9w-JlKYJg,91
|
20
|
+
haiway/opentelemetry/observability.py,sha256=4c22MqSTBbYoadXTBbbBJTPik1GQ7LjjpMz2qeOKXf4,13439
|
21
|
+
haiway/state/__init__.py,sha256=AaMqlMhO4zKS_XNevy3A7BHh5PxmguA-Sk_FnaNDY1Q,355
|
22
|
+
haiway/state/attributes.py,sha256=p6jUBzg62bOl0zAYTCa7NIllsaNY2Kt68IooQ9tb-y8,23311
|
23
|
+
haiway/state/path.py,sha256=-IpbUpF2QHWg3hEITkWYHJ6ZPoRVixu-SOSuWk-bbBY,21318
|
24
|
+
haiway/state/requirement.py,sha256=oKh9eqgTwxcJF4JNhU-DAbHbHsaACMNSlX-mkVjeJeY,7034
|
25
|
+
haiway/state/structure.py,sha256=rlA7qTr7rmJ_cU7_lJYq_y9Y-GO1wsaeHtjyDiIB8y8,16195
|
26
|
+
haiway/state/validation.py,sha256=LiCkItybUHT3oKG6IyLu2x6IKKvnWnabuEcVkTbEP9Y,14996
|
27
|
+
haiway/types/__init__.py,sha256=73DMgf60Ftf1gLRCSQG66Nyu3_QFjdRJggBtS4-RQkY,342
|
28
|
+
haiway/types/default.py,sha256=IIU6QA73aDUKXLNu78KQ2dLQFbyBrU74w7jlFswHl-8,2208
|
29
|
+
haiway/types/frozen.py,sha256=zLVkj85_lj6LrXjXAdv06Yy0MCj4spC8FQ-AhZMDPKg,70
|
30
|
+
haiway/types/missing.py,sha256=769MX5qpJ3zjNu6xLUH75On8FgheY06f2JYFR21gs9o,1712
|
31
|
+
haiway/utils/__init__.py,sha256=RqeTPXTVhvYp8rd5YLMSyH0hYF9y4j6aSxdbjG4VZtA,907
|
32
|
+
haiway/utils/always.py,sha256=dd6jDQ1j4DpJjTKO1J2Tv5xS8X1LnMC4kQ0D7DtKUvw,1230
|
33
|
+
haiway/utils/collections.py,sha256=pSBXhtLdhrLqmYo9YZEx716JI9S_sIztLJ5z5wi2d7Y,4162
|
34
|
+
haiway/utils/env.py,sha256=gdZcQS9l82hKm4Jojy1vnE42s89JqPFbiYODAE8I2sA,5339
|
35
|
+
haiway/utils/freezing.py,sha256=QsThd6FJ8TgErio7pCsHSnUKmVQbHZu6iEDYiqvJteo,614
|
36
|
+
haiway/utils/logs.py,sha256=NuwoqKQnMNi1FMIA91cVFnAPefUFeg3UIT50IOl3sJk,1571
|
37
|
+
haiway/utils/mimic.py,sha256=L5AS4WEL2aPMZAQZlvLvRzHl0cipI7ivky60_eL4iwY,1822
|
38
|
+
haiway/utils/noop.py,sha256=f54PSLHGEwCQNYXQHkPAW5NDE-tk5yjzkNL1pZj0TJQ,344
|
39
|
+
haiway/utils/queue.py,sha256=YTvCn3wgSwLJiLqolMx44sa3304Xkv3tJG77gvfWnZs,4114
|
40
|
+
haiway/utils/stream.py,sha256=Mjhy2S-ZDR1g_NsgS_nuBA8AgVbhrGXKvG3wjJ5mCJQ,2826
|
41
|
+
haiway-0.18.0.dist-info/METADATA,sha256=HhXh_Dn5VRN-pA94h4iBM3TB9ugkqKIUrbwCWZ6sob4,4527
|
42
|
+
haiway-0.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
43
|
+
haiway-0.18.0.dist-info/licenses/LICENSE,sha256=3phcpHVNBP8jsi77gOO0E7rgKeDeu99Pi7DSnK9YHoQ,1069
|
44
|
+
haiway-0.18.0.dist-info/RECORD,,
|
haiway/context/logging.py
DELETED
@@ -1,242 +0,0 @@
|
|
1
|
-
from contextvars import ContextVar, Token
|
2
|
-
from logging import DEBUG, ERROR, INFO, WARNING, Logger, getLogger
|
3
|
-
from time import monotonic
|
4
|
-
from types import TracebackType
|
5
|
-
from typing import Any, Self, final
|
6
|
-
|
7
|
-
from haiway.context.identifier import ScopeIdentifier
|
8
|
-
|
9
|
-
__all__ = [
|
10
|
-
"LoggerContext",
|
11
|
-
]
|
12
|
-
|
13
|
-
|
14
|
-
@final
|
15
|
-
class LoggerContext:
|
16
|
-
_context = ContextVar[Self]("LoggerContext")
|
17
|
-
|
18
|
-
@classmethod
|
19
|
-
def scope(
|
20
|
-
cls,
|
21
|
-
scope: ScopeIdentifier,
|
22
|
-
/,
|
23
|
-
*,
|
24
|
-
logger: Logger | None,
|
25
|
-
) -> Self:
|
26
|
-
current: Self
|
27
|
-
try: # check for current scope
|
28
|
-
current = cls._context.get()
|
29
|
-
|
30
|
-
except LookupError:
|
31
|
-
# create root scope when missing
|
32
|
-
return cls(
|
33
|
-
scope=scope,
|
34
|
-
logger=logger,
|
35
|
-
)
|
36
|
-
|
37
|
-
# create nested scope otherwise
|
38
|
-
return cls(
|
39
|
-
scope=scope,
|
40
|
-
logger=logger or current._logger,
|
41
|
-
)
|
42
|
-
|
43
|
-
@classmethod
|
44
|
-
def log_error(
|
45
|
-
cls,
|
46
|
-
message: str,
|
47
|
-
/,
|
48
|
-
*args: Any,
|
49
|
-
exception: BaseException | None = None,
|
50
|
-
) -> None:
|
51
|
-
try:
|
52
|
-
cls._context.get().log(
|
53
|
-
ERROR,
|
54
|
-
message,
|
55
|
-
*args,
|
56
|
-
exception=exception,
|
57
|
-
)
|
58
|
-
|
59
|
-
except LookupError:
|
60
|
-
getLogger().log(
|
61
|
-
ERROR,
|
62
|
-
message,
|
63
|
-
*args,
|
64
|
-
exc_info=exception,
|
65
|
-
)
|
66
|
-
|
67
|
-
@classmethod
|
68
|
-
def log_warning(
|
69
|
-
cls,
|
70
|
-
message: str,
|
71
|
-
/,
|
72
|
-
*args: Any,
|
73
|
-
exception: Exception | None = None,
|
74
|
-
) -> None:
|
75
|
-
try:
|
76
|
-
cls._context.get().log(
|
77
|
-
WARNING,
|
78
|
-
message,
|
79
|
-
*args,
|
80
|
-
exception=exception,
|
81
|
-
)
|
82
|
-
|
83
|
-
except LookupError:
|
84
|
-
getLogger().log(
|
85
|
-
WARNING,
|
86
|
-
message,
|
87
|
-
*args,
|
88
|
-
exc_info=exception,
|
89
|
-
)
|
90
|
-
|
91
|
-
@classmethod
|
92
|
-
def log_info(
|
93
|
-
cls,
|
94
|
-
message: str,
|
95
|
-
/,
|
96
|
-
*args: Any,
|
97
|
-
) -> None:
|
98
|
-
try:
|
99
|
-
cls._context.get().log(
|
100
|
-
INFO,
|
101
|
-
message,
|
102
|
-
*args,
|
103
|
-
)
|
104
|
-
|
105
|
-
except LookupError:
|
106
|
-
getLogger().log(
|
107
|
-
INFO,
|
108
|
-
message,
|
109
|
-
*args,
|
110
|
-
)
|
111
|
-
|
112
|
-
@classmethod
|
113
|
-
def log_debug(
|
114
|
-
cls,
|
115
|
-
message: str,
|
116
|
-
/,
|
117
|
-
*args: Any,
|
118
|
-
exception: Exception | None = None,
|
119
|
-
) -> None:
|
120
|
-
try:
|
121
|
-
cls._context.get().log(
|
122
|
-
DEBUG,
|
123
|
-
message,
|
124
|
-
*args,
|
125
|
-
exception=exception,
|
126
|
-
)
|
127
|
-
|
128
|
-
except LookupError:
|
129
|
-
getLogger().log(
|
130
|
-
DEBUG,
|
131
|
-
message,
|
132
|
-
*args,
|
133
|
-
exc_info=exception,
|
134
|
-
)
|
135
|
-
|
136
|
-
__slots__ = (
|
137
|
-
"_entered",
|
138
|
-
"_logger",
|
139
|
-
"_prefix",
|
140
|
-
"_token",
|
141
|
-
)
|
142
|
-
|
143
|
-
def __init__(
|
144
|
-
self,
|
145
|
-
scope: ScopeIdentifier,
|
146
|
-
logger: Logger | None,
|
147
|
-
) -> None:
|
148
|
-
self._prefix: str
|
149
|
-
object.__setattr__(
|
150
|
-
self,
|
151
|
-
"_prefix",
|
152
|
-
scope.unique_name,
|
153
|
-
)
|
154
|
-
self._logger: Logger
|
155
|
-
object.__setattr__(
|
156
|
-
self,
|
157
|
-
"_logger",
|
158
|
-
logger or getLogger(name=scope.label),
|
159
|
-
)
|
160
|
-
self._token: Token[LoggerContext] | None
|
161
|
-
object.__setattr__(
|
162
|
-
self,
|
163
|
-
"_token",
|
164
|
-
None,
|
165
|
-
)
|
166
|
-
self._entered: float | None
|
167
|
-
object.__setattr__(
|
168
|
-
self,
|
169
|
-
"_entered",
|
170
|
-
None,
|
171
|
-
)
|
172
|
-
|
173
|
-
def __setattr__(
|
174
|
-
self,
|
175
|
-
name: str,
|
176
|
-
value: Any,
|
177
|
-
) -> Any:
|
178
|
-
raise AttributeError(
|
179
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
180
|
-
f" attribute - '{name}' cannot be modified"
|
181
|
-
)
|
182
|
-
|
183
|
-
def __delattr__(
|
184
|
-
self,
|
185
|
-
name: str,
|
186
|
-
) -> None:
|
187
|
-
raise AttributeError(
|
188
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
189
|
-
f" attribute - '{name}' cannot be deleted"
|
190
|
-
)
|
191
|
-
|
192
|
-
def log(
|
193
|
-
self,
|
194
|
-
level: int,
|
195
|
-
message: str,
|
196
|
-
/,
|
197
|
-
*args: Any,
|
198
|
-
exception: BaseException | None = None,
|
199
|
-
) -> None:
|
200
|
-
self._logger.log(
|
201
|
-
level,
|
202
|
-
f"{self._prefix} {message}",
|
203
|
-
*args,
|
204
|
-
exc_info=exception,
|
205
|
-
)
|
206
|
-
|
207
|
-
def __enter__(self) -> None:
|
208
|
-
assert self._token is None, "Context reentrance is not allowed" # nosec: B101
|
209
|
-
assert self._entered is None, "Context reentrance is not allowed" # nosec: B101
|
210
|
-
object.__setattr__(
|
211
|
-
self,
|
212
|
-
"_token",
|
213
|
-
LoggerContext._context.set(self),
|
214
|
-
)
|
215
|
-
object.__setattr__(
|
216
|
-
self,
|
217
|
-
"_entered",
|
218
|
-
monotonic(),
|
219
|
-
)
|
220
|
-
self.log(DEBUG, "Entering context...")
|
221
|
-
|
222
|
-
def __exit__(
|
223
|
-
self,
|
224
|
-
exc_type: type[BaseException] | None,
|
225
|
-
exc_val: BaseException | None,
|
226
|
-
exc_tb: TracebackType | None,
|
227
|
-
) -> None:
|
228
|
-
assert ( # nosec: B101
|
229
|
-
self._token is not None and self._entered is not None
|
230
|
-
), "Unbalanced context enter/exit"
|
231
|
-
LoggerContext._context.reset(self._token)
|
232
|
-
object.__setattr__(
|
233
|
-
self,
|
234
|
-
"_token",
|
235
|
-
None,
|
236
|
-
)
|
237
|
-
self.log(DEBUG, f"...exiting context after {monotonic() - self._entered:.2f}s")
|
238
|
-
object.__setattr__(
|
239
|
-
self,
|
240
|
-
"_entered",
|
241
|
-
None,
|
242
|
-
)
|
haiway/context/metrics.py
DELETED
@@ -1,176 +0,0 @@
|
|
1
|
-
from contextvars import ContextVar, Token
|
2
|
-
from types import TracebackType
|
3
|
-
from typing import Any, Protocol, Self, final, runtime_checkable
|
4
|
-
|
5
|
-
from haiway.context.identifier import ScopeIdentifier
|
6
|
-
from haiway.context.logging import LoggerContext
|
7
|
-
from haiway.state import State
|
8
|
-
|
9
|
-
__all__ = [
|
10
|
-
"MetricsContext",
|
11
|
-
"MetricsHandler",
|
12
|
-
"MetricsRecording",
|
13
|
-
"MetricsScopeEntering",
|
14
|
-
"MetricsScopeExiting",
|
15
|
-
]
|
16
|
-
|
17
|
-
|
18
|
-
@runtime_checkable
|
19
|
-
class MetricsRecording(Protocol):
|
20
|
-
def __call__(
|
21
|
-
self,
|
22
|
-
scope: ScopeIdentifier,
|
23
|
-
/,
|
24
|
-
metric: State,
|
25
|
-
) -> None: ...
|
26
|
-
|
27
|
-
|
28
|
-
@runtime_checkable
|
29
|
-
class MetricsScopeEntering(Protocol):
|
30
|
-
def __call__[Metric: State](
|
31
|
-
self,
|
32
|
-
scope: ScopeIdentifier,
|
33
|
-
/,
|
34
|
-
) -> None: ...
|
35
|
-
|
36
|
-
|
37
|
-
@runtime_checkable
|
38
|
-
class MetricsScopeExiting(Protocol):
|
39
|
-
def __call__[Metric: State](
|
40
|
-
self,
|
41
|
-
scope: ScopeIdentifier,
|
42
|
-
/,
|
43
|
-
) -> None: ...
|
44
|
-
|
45
|
-
|
46
|
-
class MetricsHandler(State):
|
47
|
-
record: MetricsRecording
|
48
|
-
enter_scope: MetricsScopeEntering
|
49
|
-
exit_scope: MetricsScopeExiting
|
50
|
-
|
51
|
-
|
52
|
-
@final
|
53
|
-
class MetricsContext:
|
54
|
-
_context = ContextVar[Self]("MetricsContext")
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def scope(
|
58
|
-
cls,
|
59
|
-
scope: ScopeIdentifier,
|
60
|
-
/,
|
61
|
-
*,
|
62
|
-
metrics: MetricsHandler | None,
|
63
|
-
) -> Self:
|
64
|
-
current: Self
|
65
|
-
try: # check for current scope
|
66
|
-
current = cls._context.get()
|
67
|
-
|
68
|
-
except LookupError:
|
69
|
-
# create root scope when missing
|
70
|
-
return cls(
|
71
|
-
scope=scope,
|
72
|
-
metrics=metrics,
|
73
|
-
)
|
74
|
-
|
75
|
-
# create nested scope otherwise
|
76
|
-
return cls(
|
77
|
-
scope=scope,
|
78
|
-
metrics=metrics or current._metrics,
|
79
|
-
)
|
80
|
-
|
81
|
-
@classmethod
|
82
|
-
def record(
|
83
|
-
cls,
|
84
|
-
metric: State,
|
85
|
-
/,
|
86
|
-
) -> None:
|
87
|
-
try: # catch exceptions - we don't wan't to blow up on metrics
|
88
|
-
metrics: Self = cls._context.get()
|
89
|
-
|
90
|
-
if metrics._metrics is not None:
|
91
|
-
metrics._metrics.record(
|
92
|
-
metrics._scope,
|
93
|
-
metric,
|
94
|
-
)
|
95
|
-
|
96
|
-
except Exception as exc:
|
97
|
-
LoggerContext.log_error(
|
98
|
-
"Failed to record metric: %s",
|
99
|
-
type(metric).__qualname__,
|
100
|
-
exception=exc,
|
101
|
-
)
|
102
|
-
|
103
|
-
__slots__ = (
|
104
|
-
"_metrics",
|
105
|
-
"_scope",
|
106
|
-
"_token",
|
107
|
-
)
|
108
|
-
|
109
|
-
def __init__(
|
110
|
-
self,
|
111
|
-
scope: ScopeIdentifier,
|
112
|
-
metrics: MetricsHandler | None,
|
113
|
-
) -> None:
|
114
|
-
self._scope: ScopeIdentifier
|
115
|
-
object.__setattr__(
|
116
|
-
self,
|
117
|
-
"_scope",
|
118
|
-
scope,
|
119
|
-
)
|
120
|
-
self._metrics: MetricsHandler | None
|
121
|
-
object.__setattr__(
|
122
|
-
self,
|
123
|
-
"_metrics",
|
124
|
-
metrics,
|
125
|
-
)
|
126
|
-
self._token: Token[MetricsContext] | None
|
127
|
-
object.__setattr__(
|
128
|
-
self,
|
129
|
-
"_token",
|
130
|
-
None,
|
131
|
-
)
|
132
|
-
|
133
|
-
def __setattr__(
|
134
|
-
self,
|
135
|
-
name: str,
|
136
|
-
value: Any,
|
137
|
-
) -> Any:
|
138
|
-
raise AttributeError(
|
139
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
140
|
-
f" attribute - '{name}' cannot be modified"
|
141
|
-
)
|
142
|
-
|
143
|
-
def __delattr__(
|
144
|
-
self,
|
145
|
-
name: str,
|
146
|
-
) -> None:
|
147
|
-
raise AttributeError(
|
148
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
149
|
-
f" attribute - '{name}' cannot be deleted"
|
150
|
-
)
|
151
|
-
|
152
|
-
def __enter__(self) -> None:
|
153
|
-
assert self._token is None, "Context reentrance is not allowed" # nosec: B101
|
154
|
-
object.__setattr__(
|
155
|
-
self,
|
156
|
-
"_token",
|
157
|
-
MetricsContext._context.set(self),
|
158
|
-
)
|
159
|
-
if self._metrics is not None:
|
160
|
-
self._metrics.enter_scope(self._scope)
|
161
|
-
|
162
|
-
def __exit__(
|
163
|
-
self,
|
164
|
-
exc_type: type[BaseException] | None,
|
165
|
-
exc_val: BaseException | None,
|
166
|
-
exc_tb: TracebackType | None,
|
167
|
-
) -> None:
|
168
|
-
assert self._token is not None, "Unbalanced context enter/exit" # nosec: B101
|
169
|
-
MetricsContext._context.reset(self._token)
|
170
|
-
object.__setattr__(
|
171
|
-
self,
|
172
|
-
"_token",
|
173
|
-
None,
|
174
|
-
)
|
175
|
-
if self._metrics is not None:
|
176
|
-
self._metrics.exit_scope(self._scope)
|