haiway 0.17.0__py3-none-any.whl → 0.18.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. haiway/__init__.py +24 -18
  2. haiway/context/__init__.py +23 -13
  3. haiway/context/access.py +127 -91
  4. haiway/context/disposables.py +2 -2
  5. haiway/context/identifier.py +4 -5
  6. haiway/context/observability.py +526 -0
  7. haiway/context/state.py +2 -2
  8. haiway/context/tasks.py +1 -3
  9. haiway/context/types.py +2 -2
  10. haiway/helpers/__init__.py +5 -7
  11. haiway/helpers/asynchrony.py +2 -2
  12. haiway/helpers/caching.py +2 -2
  13. haiway/helpers/observability.py +244 -0
  14. haiway/helpers/retries.py +1 -3
  15. haiway/helpers/throttling.py +1 -3
  16. haiway/helpers/timeouted.py +1 -3
  17. haiway/helpers/tracing.py +21 -35
  18. haiway/opentelemetry/__init__.py +3 -0
  19. haiway/opentelemetry/observability.py +452 -0
  20. haiway/state/__init__.py +2 -2
  21. haiway/state/attributes.py +2 -2
  22. haiway/state/path.py +1 -3
  23. haiway/state/requirement.py +1 -3
  24. haiway/state/structure.py +161 -30
  25. haiway/state/validation.py +2 -2
  26. haiway/types/__init__.py +2 -2
  27. haiway/types/default.py +2 -2
  28. haiway/types/frozen.py +1 -3
  29. haiway/types/missing.py +2 -2
  30. haiway/utils/__init__.py +2 -2
  31. haiway/utils/always.py +2 -2
  32. haiway/utils/collections.py +2 -2
  33. haiway/utils/env.py +2 -2
  34. haiway/utils/freezing.py +1 -3
  35. haiway/utils/logs.py +1 -3
  36. haiway/utils/mimic.py +1 -3
  37. haiway/utils/noop.py +2 -2
  38. haiway/utils/queue.py +1 -3
  39. haiway/utils/stream.py +1 -3
  40. {haiway-0.17.0.dist-info → haiway-0.18.1.dist-info}/METADATA +9 -5
  41. haiway-0.18.1.dist-info/RECORD +44 -0
  42. haiway/context/logging.py +0 -242
  43. haiway/context/metrics.py +0 -176
  44. haiway/helpers/metrics.py +0 -465
  45. haiway-0.17.0.dist-info/RECORD +0 -43
  46. {haiway-0.17.0.dist-info → haiway-0.18.1.dist-info}/WHEEL +0 -0
  47. {haiway-0.17.0.dist-info → haiway-0.18.1.dist-info}/licenses/LICENSE +0 -0
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)