haiway 0.24.3__py3-none-any.whl → 0.25.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.
- haiway/__init__.py +4 -1
- haiway/context/__init__.py +4 -0
- haiway/context/access.py +312 -70
- haiway/context/disposables.py +5 -119
- haiway/context/identifier.py +19 -44
- haiway/context/observability.py +22 -142
- haiway/context/presets.py +337 -0
- haiway/context/state.py +38 -84
- haiway/context/tasks.py +7 -10
- haiway/helpers/observability.py +4 -6
- haiway/opentelemetry/observability.py +5 -5
- haiway/state/__init__.py +2 -0
- haiway/state/immutable.py +127 -0
- haiway/state/structure.py +63 -117
- haiway/state/validation.py +95 -60
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/METADATA +1 -1
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/RECORD +19 -17
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/WHEEL +0 -0
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,337 @@
|
|
1
|
+
from collections.abc import Collection, Iterable, Mapping
|
2
|
+
from contextvars import ContextVar, Token
|
3
|
+
from types import TracebackType
|
4
|
+
from typing import ClassVar, Protocol, Self, cast
|
5
|
+
|
6
|
+
from haiway.context.disposables import Disposable, Disposables
|
7
|
+
from haiway.state import Immutable, State
|
8
|
+
from haiway.types.default import Default
|
9
|
+
|
10
|
+
__all__ = (
|
11
|
+
"ContextPresets",
|
12
|
+
"ContextPresetsRegistry",
|
13
|
+
"ContextPresetsRegistryContext",
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class ContextPresetsStatePreparing(Protocol):
|
18
|
+
async def __call__(self) -> Iterable[State] | State: ...
|
19
|
+
|
20
|
+
|
21
|
+
class ContextPresetsDisposablesPreparing(Protocol):
|
22
|
+
async def __call__(self) -> Iterable[Disposable] | Disposable: ...
|
23
|
+
|
24
|
+
|
25
|
+
class ContextPresets(Immutable):
|
26
|
+
"""
|
27
|
+
A configuration preset for context scopes.
|
28
|
+
|
29
|
+
ContextPresets allow you to define reusable combinations of state and disposables
|
30
|
+
that can be applied to scopes by name. This provides a convenient way to manage
|
31
|
+
complex application configurations and resource setups.
|
32
|
+
|
33
|
+
State Priority
|
34
|
+
--------------
|
35
|
+
When used with ctx.scope(), preset state has lower priority than explicit state:
|
36
|
+
1. Explicit state (passed to ctx.scope()) - **highest priority**
|
37
|
+
2. Explicit disposables (passed to ctx.scope()) - medium priority
|
38
|
+
3. Preset state (from presets) - low priority
|
39
|
+
4. Contextual state (from parent contexts) - **lowest priority**
|
40
|
+
|
41
|
+
Examples
|
42
|
+
--------
|
43
|
+
Basic preset with static state:
|
44
|
+
|
45
|
+
>>> from haiway import State
|
46
|
+
>>> from haiway.context import ContextPresets
|
47
|
+
>>>
|
48
|
+
>>> class DatabaseConfig(State):
|
49
|
+
... connection_string: str
|
50
|
+
... pool_size: int = 10
|
51
|
+
>>>
|
52
|
+
>>> db_preset = ContextPresets(
|
53
|
+
... name="database",
|
54
|
+
... _state=[DatabaseConfig(connection_string="postgresql://localhost/app")]
|
55
|
+
... )
|
56
|
+
|
57
|
+
Preset with dynamic state factory:
|
58
|
+
|
59
|
+
>>> async def load_config() -> DatabaseConfig:
|
60
|
+
... # Load configuration from environment or config file
|
61
|
+
... return DatabaseConfig(connection_string=os.getenv("DB_URL"))
|
62
|
+
>>>
|
63
|
+
>>> dynamic_preset = ContextPresets(
|
64
|
+
... name="dynamic_db",
|
65
|
+
... _state=[load_config]
|
66
|
+
... )
|
67
|
+
|
68
|
+
Preset with disposables:
|
69
|
+
|
70
|
+
>>> from contextlib import asynccontextmanager
|
71
|
+
>>>
|
72
|
+
>>> @asynccontextmanager
|
73
|
+
>>> async def database_connection():
|
74
|
+
... conn = await create_connection()
|
75
|
+
... try:
|
76
|
+
... yield ConnectionState(connection=conn)
|
77
|
+
... finally:
|
78
|
+
... await conn.close()
|
79
|
+
>>>
|
80
|
+
>>> async def connection_factory():
|
81
|
+
... return database_connection()
|
82
|
+
>>>
|
83
|
+
>>> db_preset = ContextPresets(
|
84
|
+
... name="database",
|
85
|
+
... _state=[DatabaseConfig(connection_string="...")],
|
86
|
+
... _disposables=[connection_factory]
|
87
|
+
... )
|
88
|
+
|
89
|
+
Using presets:
|
90
|
+
|
91
|
+
>>> from haiway import ctx
|
92
|
+
>>>
|
93
|
+
>>> with ctx.presets(db_preset):
|
94
|
+
... async with ctx.scope("database"):
|
95
|
+
... config = ctx.state(DatabaseConfig)
|
96
|
+
... # Use the preset configuration
|
97
|
+
"""
|
98
|
+
|
99
|
+
name: str
|
100
|
+
_state: Collection[ContextPresetsStatePreparing | State] = Default(())
|
101
|
+
_disposables: Collection[ContextPresetsDisposablesPreparing] = Default(())
|
102
|
+
|
103
|
+
def extended(
|
104
|
+
self,
|
105
|
+
other: Self,
|
106
|
+
) -> Self:
|
107
|
+
"""
|
108
|
+
Create a new preset by extending this preset with another.
|
109
|
+
|
110
|
+
Combines the state and disposables from both presets, keeping the name
|
111
|
+
of the current preset. The other preset's state and disposables are
|
112
|
+
appended to this preset's collections.
|
113
|
+
|
114
|
+
Parameters
|
115
|
+
----------
|
116
|
+
other : Self
|
117
|
+
Another ContextPresets instance to merge with this one.
|
118
|
+
|
119
|
+
Returns
|
120
|
+
-------
|
121
|
+
Self
|
122
|
+
A new ContextPresets instance with combined state and disposables.
|
123
|
+
"""
|
124
|
+
return self.__class__(
|
125
|
+
name=self.name,
|
126
|
+
_state=(*self._state, *other._state),
|
127
|
+
_disposables=(*self._disposables, *other._disposables),
|
128
|
+
)
|
129
|
+
|
130
|
+
def with_state(
|
131
|
+
self,
|
132
|
+
*state: ContextPresetsStatePreparing | State,
|
133
|
+
) -> Self:
|
134
|
+
"""
|
135
|
+
Create a new preset with additional state.
|
136
|
+
|
137
|
+
Returns a new ContextPresets instance with the provided state objects
|
138
|
+
or state factories added to the existing state collection.
|
139
|
+
|
140
|
+
Parameters
|
141
|
+
----------
|
142
|
+
*state : ContextPresetsStatePreparing | State
|
143
|
+
Additional state objects or state factory functions to include.
|
144
|
+
|
145
|
+
Returns
|
146
|
+
-------
|
147
|
+
Self
|
148
|
+
A new ContextPresets instance with the additional state, or the
|
149
|
+
same instance if no state was provided.
|
150
|
+
"""
|
151
|
+
if not state:
|
152
|
+
return self
|
153
|
+
|
154
|
+
return self.__class__(
|
155
|
+
name=self.name,
|
156
|
+
_state=(*self._state, *state),
|
157
|
+
_disposables=self._disposables,
|
158
|
+
)
|
159
|
+
|
160
|
+
def with_disposable(
|
161
|
+
self,
|
162
|
+
*disposable: ContextPresetsDisposablesPreparing,
|
163
|
+
) -> Self:
|
164
|
+
"""
|
165
|
+
Create a new preset with additional disposables.
|
166
|
+
|
167
|
+
Returns a new ContextPresets instance with the provided disposable
|
168
|
+
factory functions added to the existing disposables collection.
|
169
|
+
|
170
|
+
Parameters
|
171
|
+
----------
|
172
|
+
*disposable : ContextPresetsDisposablesPreparing
|
173
|
+
Additional disposable factory functions to include.
|
174
|
+
|
175
|
+
Returns
|
176
|
+
-------
|
177
|
+
Self
|
178
|
+
A new ContextPresets instance with the additional disposables, or the
|
179
|
+
same instance if no disposables were provided.
|
180
|
+
"""
|
181
|
+
if not disposable:
|
182
|
+
return self
|
183
|
+
|
184
|
+
return self.__class__(
|
185
|
+
name=self.name,
|
186
|
+
_state=self._state,
|
187
|
+
_disposables=(*self._disposables, *disposable),
|
188
|
+
)
|
189
|
+
|
190
|
+
async def prepare(self) -> Disposables:
|
191
|
+
"""
|
192
|
+
Prepare the preset for use by resolving all state and disposables.
|
193
|
+
|
194
|
+
This method evaluates all state factories and disposable factories to create
|
195
|
+
concrete instances. State objects are wrapped in a DisposableState to unify
|
196
|
+
the handling of state and disposable resources.
|
197
|
+
|
198
|
+
The method ensures concurrent safety by creating fresh instances each time
|
199
|
+
it's called, making it safe to use the same preset across multiple concurrent
|
200
|
+
scopes.
|
201
|
+
|
202
|
+
Returns
|
203
|
+
-------
|
204
|
+
Disposables
|
205
|
+
A Disposables container holding all resolved state (wrapped in DisposableState)
|
206
|
+
and disposable resources from this preset.
|
207
|
+
|
208
|
+
Note
|
209
|
+
----
|
210
|
+
This method is called automatically when using presets with ctx.scope(),
|
211
|
+
so you typically don't need to call it directly.
|
212
|
+
"""
|
213
|
+
# Collect states directly
|
214
|
+
collected_states: list[State] = []
|
215
|
+
for state in self._state:
|
216
|
+
if isinstance(state, State):
|
217
|
+
collected_states.append(state)
|
218
|
+
else:
|
219
|
+
resolved_state: Iterable[State] | State = await state()
|
220
|
+
if isinstance(resolved_state, State):
|
221
|
+
collected_states.append(resolved_state)
|
222
|
+
|
223
|
+
else:
|
224
|
+
collected_states.extend(resolved_state)
|
225
|
+
|
226
|
+
collected_disposables: list[Disposable]
|
227
|
+
if collected_states:
|
228
|
+
collected_disposables = [DisposableState(_state=collected_states)]
|
229
|
+
|
230
|
+
else:
|
231
|
+
collected_disposables = []
|
232
|
+
|
233
|
+
for disposable in self._disposables:
|
234
|
+
resolved_disposable: Iterable[Disposable] | Disposable = await disposable()
|
235
|
+
if hasattr(resolved_disposable, "__aenter__") and hasattr(
|
236
|
+
resolved_disposable, "__aexit__"
|
237
|
+
):
|
238
|
+
collected_disposables.append(cast(Disposable, resolved_disposable))
|
239
|
+
|
240
|
+
else:
|
241
|
+
collected_disposables.extend(cast(Iterable[Disposable], resolved_disposable))
|
242
|
+
|
243
|
+
return Disposables(*collected_disposables)
|
244
|
+
|
245
|
+
|
246
|
+
class DisposableState(Immutable):
|
247
|
+
_state: Iterable[State]
|
248
|
+
|
249
|
+
async def __aenter__(self) -> Iterable[State]:
|
250
|
+
return self._state
|
251
|
+
|
252
|
+
async def __aexit__(
|
253
|
+
self,
|
254
|
+
exc_type: type[BaseException] | None,
|
255
|
+
exc_val: BaseException | None,
|
256
|
+
exc_tb: TracebackType | None,
|
257
|
+
) -> None:
|
258
|
+
pass
|
259
|
+
|
260
|
+
|
261
|
+
class ContextPresetsRegistry(Immutable):
|
262
|
+
_presets: Mapping[str, ContextPresets]
|
263
|
+
|
264
|
+
def __init__(
|
265
|
+
self,
|
266
|
+
presets: Collection[ContextPresets],
|
267
|
+
) -> None:
|
268
|
+
object.__setattr__(
|
269
|
+
self,
|
270
|
+
"_presets",
|
271
|
+
{preset.name: preset for preset in presets},
|
272
|
+
)
|
273
|
+
|
274
|
+
def select(
|
275
|
+
self,
|
276
|
+
name: str,
|
277
|
+
/,
|
278
|
+
) -> ContextPresets | None:
|
279
|
+
return self._presets.get(name)
|
280
|
+
|
281
|
+
|
282
|
+
class ContextPresetsRegistryContext(Immutable):
|
283
|
+
_context: ClassVar[ContextVar[ContextPresetsRegistry]] = ContextVar[ContextPresetsRegistry](
|
284
|
+
"ContextPresetsRegistryContext"
|
285
|
+
)
|
286
|
+
|
287
|
+
@classmethod
|
288
|
+
def select(
|
289
|
+
cls,
|
290
|
+
name: str,
|
291
|
+
/,
|
292
|
+
) -> ContextPresets | None:
|
293
|
+
try:
|
294
|
+
return cls._context.get().select(name)
|
295
|
+
|
296
|
+
except LookupError:
|
297
|
+
return None # no presets
|
298
|
+
|
299
|
+
_registry: ContextPresetsRegistry
|
300
|
+
_token: Token[ContextPresetsRegistry] | None = None
|
301
|
+
|
302
|
+
def __init__(
|
303
|
+
self,
|
304
|
+
registry: ContextPresetsRegistry,
|
305
|
+
) -> None:
|
306
|
+
object.__setattr__(
|
307
|
+
self,
|
308
|
+
"_registry",
|
309
|
+
registry,
|
310
|
+
)
|
311
|
+
object.__setattr__(
|
312
|
+
self,
|
313
|
+
"_token",
|
314
|
+
None,
|
315
|
+
)
|
316
|
+
|
317
|
+
def __enter__(self) -> None:
|
318
|
+
assert self._token is None, "Context reentrance is not allowed" # nosec: B101
|
319
|
+
object.__setattr__(
|
320
|
+
self,
|
321
|
+
"_token",
|
322
|
+
ContextPresetsRegistryContext._context.set(self._registry),
|
323
|
+
)
|
324
|
+
|
325
|
+
def __exit__(
|
326
|
+
self,
|
327
|
+
exc_type: type[BaseException] | None,
|
328
|
+
exc_val: BaseException | None,
|
329
|
+
exc_tb: TracebackType | None,
|
330
|
+
) -> None:
|
331
|
+
assert self._token is not None, "Unbalanced context enter/exit" # nosec: B101
|
332
|
+
ContextPresetsRegistryContext._context.reset(self._token)
|
333
|
+
object.__setattr__(
|
334
|
+
self,
|
335
|
+
"_token",
|
336
|
+
None,
|
337
|
+
)
|
haiway/context/state.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
from asyncio import iscoroutinefunction
|
2
|
-
from collections.abc import Callable, Coroutine, Iterable, MutableMapping
|
2
|
+
from collections.abc import Callable, Collection, Coroutine, Iterable, MutableMapping
|
3
3
|
from contextvars import ContextVar, Token
|
4
4
|
from threading import Lock
|
5
5
|
from types import TracebackType
|
6
|
-
from typing import Any, Self, cast,
|
6
|
+
from typing import Any, ClassVar, Self, cast, overload
|
7
7
|
|
8
8
|
from haiway.context.types import MissingContext, MissingState
|
9
|
-
from haiway.state import State
|
9
|
+
from haiway.state import Immutable, State
|
10
10
|
from haiway.utils.mimic import mimic_function
|
11
11
|
|
12
12
|
__all__ = (
|
@@ -15,8 +15,7 @@ __all__ = (
|
|
15
15
|
)
|
16
16
|
|
17
17
|
|
18
|
-
|
19
|
-
class ScopeState:
|
18
|
+
class ScopeState(Immutable):
|
20
19
|
"""
|
21
20
|
Container for state objects within a scope.
|
22
21
|
|
@@ -25,47 +24,24 @@ class ScopeState:
|
|
25
24
|
This class is immutable after initialization.
|
26
25
|
"""
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
"_state",
|
31
|
-
)
|
27
|
+
_state: MutableMapping[type[State], State]
|
28
|
+
_lock: Lock
|
32
29
|
|
33
30
|
def __init__(
|
34
31
|
self,
|
35
32
|
state: Iterable[State],
|
36
33
|
) -> None:
|
37
|
-
self._state: MutableMapping[type[State], State]
|
38
34
|
object.__setattr__(
|
39
35
|
self,
|
40
36
|
"_state",
|
41
37
|
{type(element): element for element in state},
|
42
38
|
)
|
43
|
-
self._lock: Lock
|
44
39
|
object.__setattr__(
|
45
40
|
self,
|
46
41
|
"_lock",
|
47
42
|
Lock(),
|
48
43
|
)
|
49
44
|
|
50
|
-
def __setattr__(
|
51
|
-
self,
|
52
|
-
name: str,
|
53
|
-
value: Any,
|
54
|
-
) -> Any:
|
55
|
-
raise AttributeError(
|
56
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
57
|
-
f" attribute - '{name}' cannot be modified"
|
58
|
-
)
|
59
|
-
|
60
|
-
def __delattr__(
|
61
|
-
self,
|
62
|
-
name: str,
|
63
|
-
) -> None:
|
64
|
-
raise AttributeError(
|
65
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
66
|
-
f" attribute - '{name}' cannot be deleted"
|
67
|
-
)
|
68
|
-
|
69
45
|
def check_state[StateType: State](
|
70
46
|
self,
|
71
47
|
state: type[StateType],
|
@@ -183,20 +159,17 @@ class ScopeState:
|
|
183
159
|
Self
|
184
160
|
A new ScopeState with the combined state
|
185
161
|
"""
|
186
|
-
if state
|
187
|
-
|
188
|
-
|
189
|
-
*self._state.values(),
|
190
|
-
*state,
|
191
|
-
]
|
192
|
-
)
|
193
|
-
|
194
|
-
else:
|
162
|
+
# Fast path: if no new state, return self
|
163
|
+
state_list = list(state)
|
164
|
+
if not state_list:
|
195
165
|
return self
|
196
166
|
|
167
|
+
# Optimize: pre-allocate with known size and use dict comprehension
|
168
|
+
combined_state = {**self._state, **{type(s): s for s in state_list}}
|
169
|
+
return self.__class__(combined_state.values())
|
170
|
+
|
197
171
|
|
198
|
-
|
199
|
-
class StateContext:
|
172
|
+
class StateContext(Immutable):
|
200
173
|
"""
|
201
174
|
Context manager for state within a scope.
|
202
175
|
|
@@ -205,7 +178,26 @@ class StateContext:
|
|
205
178
|
This class is immutable after initialization.
|
206
179
|
"""
|
207
180
|
|
208
|
-
_context = ContextVar[ScopeState]("StateContext")
|
181
|
+
_context: ClassVar[ContextVar[ScopeState]] = ContextVar[ScopeState]("StateContext")
|
182
|
+
|
183
|
+
@classmethod
|
184
|
+
def current_state(cls) -> Collection[State]:
|
185
|
+
"""
|
186
|
+
Return an immutable snapshot of the current state.
|
187
|
+
|
188
|
+
Returns
|
189
|
+
-------
|
190
|
+
Collection[State]
|
191
|
+
State objects present in the current context,
|
192
|
+
or an empty tuple if no context is active.
|
193
|
+
"""
|
194
|
+
try:
|
195
|
+
scope_state: ScopeState = cls._context.get()
|
196
|
+
with scope_state._lock:
|
197
|
+
return tuple(scope_state._state.values())
|
198
|
+
|
199
|
+
except LookupError:
|
200
|
+
return () # return empty as default
|
209
201
|
|
210
202
|
@classmethod
|
211
203
|
def check_state[StateType: State](
|
@@ -306,51 +298,13 @@ class StateContext:
|
|
306
298
|
"""
|
307
299
|
try:
|
308
300
|
# update current scope context
|
309
|
-
return cls(
|
301
|
+
return cls(_state=cls._context.get().updated(state=state))
|
310
302
|
|
311
303
|
except LookupError: # create root scope when missing
|
312
|
-
return cls(
|
304
|
+
return cls(_state=ScopeState(state))
|
313
305
|
|
314
|
-
|
315
|
-
|
316
|
-
"_token",
|
317
|
-
)
|
318
|
-
|
319
|
-
def __init__(
|
320
|
-
self,
|
321
|
-
state: ScopeState,
|
322
|
-
) -> None:
|
323
|
-
self._state: ScopeState
|
324
|
-
object.__setattr__(
|
325
|
-
self,
|
326
|
-
"_state",
|
327
|
-
state,
|
328
|
-
)
|
329
|
-
self._token: Token[ScopeState] | None
|
330
|
-
object.__setattr__(
|
331
|
-
self,
|
332
|
-
"_token",
|
333
|
-
None,
|
334
|
-
)
|
335
|
-
|
336
|
-
def __setattr__(
|
337
|
-
self,
|
338
|
-
name: str,
|
339
|
-
value: Any,
|
340
|
-
) -> Any:
|
341
|
-
raise AttributeError(
|
342
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
343
|
-
f" attribute - '{name}' cannot be modified"
|
344
|
-
)
|
345
|
-
|
346
|
-
def __delattr__(
|
347
|
-
self,
|
348
|
-
name: str,
|
349
|
-
) -> None:
|
350
|
-
raise AttributeError(
|
351
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
352
|
-
f" attribute - '{name}' cannot be deleted"
|
353
|
-
)
|
306
|
+
_state: ScopeState
|
307
|
+
_token: Token[ScopeState] | None = None
|
354
308
|
|
355
309
|
def __enter__(self) -> None:
|
356
310
|
"""
|
haiway/context/tasks.py
CHANGED
@@ -2,13 +2,14 @@ from asyncio import Task, TaskGroup, get_event_loop
|
|
2
2
|
from collections.abc import Callable, Coroutine
|
3
3
|
from contextvars import ContextVar, Token, copy_context
|
4
4
|
from types import TracebackType
|
5
|
-
from typing import Any,
|
5
|
+
from typing import Any, ClassVar
|
6
|
+
|
7
|
+
from haiway.state import Immutable
|
6
8
|
|
7
9
|
__all__ = ("TaskGroupContext",)
|
8
10
|
|
9
11
|
|
10
|
-
|
11
|
-
class TaskGroupContext:
|
12
|
+
class TaskGroupContext(Immutable):
|
12
13
|
"""
|
13
14
|
Context manager for managing task groups within a scope.
|
14
15
|
|
@@ -17,7 +18,7 @@ class TaskGroupContext:
|
|
17
18
|
This class is immutable after initialization.
|
18
19
|
"""
|
19
20
|
|
20
|
-
_context = ContextVar[TaskGroup]("TaskGroupContext")
|
21
|
+
_context: ClassVar[ContextVar[TaskGroup]] = ContextVar[TaskGroup]("TaskGroupContext")
|
21
22
|
|
22
23
|
@classmethod
|
23
24
|
def run[Result, **Arguments](
|
@@ -59,10 +60,8 @@ class TaskGroupContext:
|
|
59
60
|
context=copy_context(),
|
60
61
|
)
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
"_token",
|
65
|
-
)
|
63
|
+
_group: TaskGroup
|
64
|
+
_token: Token[TaskGroup] | None = None
|
66
65
|
|
67
66
|
def __init__(
|
68
67
|
self,
|
@@ -76,13 +75,11 @@ class TaskGroupContext:
|
|
76
75
|
task_group: TaskGroup | None
|
77
76
|
The task group to use, or None to create a new one
|
78
77
|
"""
|
79
|
-
self._group: TaskGroup
|
80
78
|
object.__setattr__(
|
81
79
|
self,
|
82
80
|
"_group",
|
83
81
|
task_group if task_group is not None else TaskGroup(),
|
84
82
|
)
|
85
|
-
self._token: Token[TaskGroup] | None
|
86
83
|
object.__setattr__(
|
87
84
|
self,
|
88
85
|
"_token",
|
haiway/helpers/observability.py
CHANGED
@@ -243,7 +243,7 @@ def LoggerObservability( # noqa: C901, PLR0915
|
|
243
243
|
nonlocal root_logger
|
244
244
|
if root_scope is None:
|
245
245
|
root_scope = scope
|
246
|
-
root_logger = logger or getLogger(scope.
|
246
|
+
root_logger = logger or getLogger(scope.name)
|
247
247
|
|
248
248
|
else:
|
249
249
|
scopes[scope.parent_id].nested.append(scope_store)
|
@@ -251,7 +251,7 @@ def LoggerObservability( # noqa: C901, PLR0915
|
|
251
251
|
assert root_logger is not None # nosec: B101
|
252
252
|
root_logger.log(
|
253
253
|
ObservabilityLevel.INFO,
|
254
|
-
f"[{trace_id_hex}] {scope.unique_name} Entering scope: {scope.
|
254
|
+
f"[{trace_id_hex}] {scope.unique_name} Entering scope: {scope.name}",
|
255
255
|
)
|
256
256
|
|
257
257
|
def scope_exiting(
|
@@ -274,7 +274,7 @@ def LoggerObservability( # noqa: C901, PLR0915
|
|
274
274
|
|
275
275
|
root_logger.log(
|
276
276
|
ObservabilityLevel.INFO,
|
277
|
-
f"[{trace_id_hex}] {scope.unique_name} Exiting scope: {scope.
|
277
|
+
f"[{trace_id_hex}] {scope.unique_name} Exiting scope: {scope.name}",
|
278
278
|
)
|
279
279
|
metric_str: str = f"Metric - scope_time:{scopes[scope.scope_id].time:.3f}s"
|
280
280
|
if debug_context: # store only for summary
|
@@ -332,9 +332,7 @@ def _tree_summary(scope_store: ScopeStore) -> str:
|
|
332
332
|
str
|
333
333
|
A formatted string representation of the scope hierarchy with recorded events
|
334
334
|
"""
|
335
|
-
elements: list[str] = [
|
336
|
-
f"┍━ {scope_store.identifier.label} [{scope_store.identifier.scope_id}]:"
|
337
|
-
]
|
335
|
+
elements: list[str] = [f"┍━ {scope_store.identifier.name} [{scope_store.identifier.scope_id}]:"]
|
338
336
|
for element in scope_store.store:
|
339
337
|
if not element:
|
340
338
|
continue # skip empty
|
@@ -686,7 +686,7 @@ class OpenTelemetry:
|
|
686
686
|
|
687
687
|
scope_store: ScopeStore
|
688
688
|
if root_scope is None:
|
689
|
-
meter = metrics.get_meter(scope.
|
689
|
+
meter = metrics.get_meter(scope.name)
|
690
690
|
context: Context = get_current()
|
691
691
|
|
692
692
|
# Handle distributed tracing with external trace ID
|
@@ -722,12 +722,12 @@ class OpenTelemetry:
|
|
722
722
|
scope,
|
723
723
|
context=context,
|
724
724
|
span=tracer.start_span(
|
725
|
-
name=scope.
|
725
|
+
name=scope.name,
|
726
726
|
context=context,
|
727
727
|
links=links,
|
728
728
|
),
|
729
729
|
meter=meter,
|
730
|
-
logger=get_logger(scope.
|
730
|
+
logger=get_logger(scope.name),
|
731
731
|
)
|
732
732
|
root_scope = scope
|
733
733
|
|
@@ -737,11 +737,11 @@ class OpenTelemetry:
|
|
737
737
|
scope,
|
738
738
|
context=scopes[scope.parent_id].context,
|
739
739
|
span=tracer.start_span(
|
740
|
-
name=scope.
|
740
|
+
name=scope.name,
|
741
741
|
context=scopes[scope.parent_id].context,
|
742
742
|
),
|
743
743
|
meter=meter,
|
744
|
-
logger=get_logger(scope.
|
744
|
+
logger=get_logger(scope.name),
|
745
745
|
)
|
746
746
|
scopes[scope.parent_id].nested.append(scope_store)
|
747
747
|
|
haiway/state/__init__.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from haiway.state.attributes import AttributeAnnotation, attribute_annotations
|
2
|
+
from haiway.state.immutable import Immutable
|
2
3
|
from haiway.state.path import AttributePath
|
3
4
|
from haiway.state.requirement import AttributeRequirement
|
4
5
|
from haiway.state.structure import State
|
@@ -7,6 +8,7 @@ __all__ = (
|
|
7
8
|
"AttributeAnnotation",
|
8
9
|
"AttributePath",
|
9
10
|
"AttributeRequirement",
|
11
|
+
"Immutable",
|
10
12
|
"State",
|
11
13
|
"attribute_annotations",
|
12
14
|
)
|