haiway 0.24.3__py3-none-any.whl → 0.25.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 +4 -1
- haiway/context/__init__.py +4 -0
- haiway/context/access.py +214 -63
- 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.0.dist-info}/METADATA +1 -1
- {haiway-0.24.3.dist-info → haiway-0.25.0.dist-info}/RECORD +19 -17
- {haiway-0.24.3.dist-info → haiway-0.25.0.dist-info}/WHEEL +0 -0
- {haiway-0.24.3.dist-info → haiway-0.25.0.dist-info}/licenses/LICENSE +0 -0
haiway/__init__.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from haiway.context import (
|
2
|
+
ContextPresets,
|
2
3
|
Disposable,
|
3
4
|
Disposables,
|
4
5
|
MissingContext,
|
@@ -26,7 +27,7 @@ from haiway.helpers import (
|
|
26
27
|
timeout,
|
27
28
|
traced,
|
28
29
|
)
|
29
|
-
from haiway.state import AttributePath, AttributeRequirement, State
|
30
|
+
from haiway.state import AttributePath, AttributeRequirement, Immutable, State
|
30
31
|
from haiway.types import (
|
31
32
|
MISSING,
|
32
33
|
Default,
|
@@ -64,12 +65,14 @@ __all__ = (
|
|
64
65
|
"AsyncStream",
|
65
66
|
"AttributePath",
|
66
67
|
"AttributeRequirement",
|
68
|
+
"ContextPresets",
|
67
69
|
"Default",
|
68
70
|
"DefaultValue",
|
69
71
|
"Disposable",
|
70
72
|
"Disposables",
|
71
73
|
"File",
|
72
74
|
"FileAccess",
|
75
|
+
"Immutable",
|
73
76
|
"LoggerObservability",
|
74
77
|
"Missing",
|
75
78
|
"MissingContext",
|
haiway/context/__init__.py
CHANGED
@@ -14,12 +14,16 @@ from haiway.context.observability import (
|
|
14
14
|
ObservabilityScopeExiting,
|
15
15
|
ObservabilityTraceIdentifying,
|
16
16
|
)
|
17
|
+
from haiway.context.presets import ContextPresets
|
17
18
|
from haiway.context.state import StateContext
|
18
19
|
from haiway.context.types import MissingContext, MissingState
|
20
|
+
from haiway.state import Immutable
|
19
21
|
|
20
22
|
__all__ = (
|
23
|
+
"ContextPresets",
|
21
24
|
"Disposable",
|
22
25
|
"Disposables",
|
26
|
+
"Immutable",
|
23
27
|
"MissingContext",
|
24
28
|
"MissingState",
|
25
29
|
"Observability",
|
haiway/context/access.py
CHANGED
@@ -8,6 +8,7 @@ from collections.abc import (
|
|
8
8
|
AsyncGenerator,
|
9
9
|
AsyncIterator,
|
10
10
|
Callable,
|
11
|
+
Collection,
|
11
12
|
Coroutine,
|
12
13
|
Iterable,
|
13
14
|
Mapping,
|
@@ -24,16 +25,20 @@ from haiway.context.observability import (
|
|
24
25
|
ObservabilityContext,
|
25
26
|
ObservabilityLevel,
|
26
27
|
)
|
28
|
+
from haiway.context.presets import (
|
29
|
+
ContextPresets,
|
30
|
+
ContextPresetsRegistry,
|
31
|
+
ContextPresetsRegistryContext,
|
32
|
+
)
|
27
33
|
from haiway.context.state import ScopeState, StateContext
|
28
34
|
from haiway.context.tasks import TaskGroupContext
|
29
|
-
from haiway.state import State
|
35
|
+
from haiway.state import Immutable, State
|
30
36
|
from haiway.utils.stream import AsyncStream
|
31
37
|
|
32
38
|
__all__ = ("ctx",)
|
33
39
|
|
34
40
|
|
35
|
-
|
36
|
-
class ScopeContext:
|
41
|
+
class ScopeContext(Immutable):
|
37
42
|
"""
|
38
43
|
Context manager for executing code within a defined scope.
|
39
44
|
|
@@ -45,42 +50,62 @@ class ScopeContext:
|
|
45
50
|
to create scope contexts.
|
46
51
|
"""
|
47
52
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
_identifier: ScopeIdentifier
|
54
|
+
_state: Collection[State]
|
55
|
+
_captured_state: Collection[State]
|
56
|
+
_resolved_state_context: StateContext | None
|
57
|
+
_disposables: Disposables | None
|
58
|
+
_presets: ContextPresets | None
|
59
|
+
_presets_disposables: Disposables | None
|
60
|
+
_observability_context: ObservabilityContext
|
61
|
+
_task_group_context: TaskGroupContext | None
|
55
62
|
|
56
63
|
def __init__(
|
57
64
|
self,
|
58
|
-
|
65
|
+
name: str,
|
59
66
|
task_group: TaskGroup | None,
|
60
67
|
state: tuple[State, ...],
|
61
68
|
disposables: Disposables | None,
|
62
69
|
observability: Observability | Logger | None,
|
63
70
|
) -> None:
|
64
|
-
self._identifier: ScopeIdentifier
|
65
71
|
object.__setattr__(
|
66
72
|
self,
|
67
73
|
"_identifier",
|
68
|
-
ScopeIdentifier.scope(
|
74
|
+
ScopeIdentifier.scope(name),
|
69
75
|
)
|
70
|
-
#
|
71
|
-
self._state_context: StateContext
|
76
|
+
# store explicit state separately for priority control
|
72
77
|
object.__setattr__(
|
73
78
|
self,
|
74
|
-
"
|
75
|
-
|
79
|
+
"_state",
|
80
|
+
state,
|
81
|
+
)
|
82
|
+
# capture current contextual state (without new additions)
|
83
|
+
object.__setattr__(
|
84
|
+
self,
|
85
|
+
"_captured_state",
|
86
|
+
StateContext.current_state(),
|
87
|
+
)
|
88
|
+
# placeholder for temporary, resolved state context
|
89
|
+
object.__setattr__(
|
90
|
+
self,
|
91
|
+
"_resolved_state_context",
|
92
|
+
None,
|
76
93
|
)
|
77
|
-
self._disposables: Disposables | None
|
78
94
|
object.__setattr__(
|
79
95
|
self,
|
80
96
|
"_disposables",
|
81
97
|
disposables,
|
82
98
|
)
|
83
|
-
|
99
|
+
object.__setattr__(
|
100
|
+
self,
|
101
|
+
"_presets",
|
102
|
+
ContextPresetsRegistryContext.select(name),
|
103
|
+
)
|
104
|
+
object.__setattr__(
|
105
|
+
self,
|
106
|
+
"_presets_disposables",
|
107
|
+
None,
|
108
|
+
)
|
84
109
|
object.__setattr__(
|
85
110
|
self,
|
86
111
|
"_observability_context",
|
@@ -90,7 +115,6 @@ class ScopeContext:
|
|
90
115
|
observability=observability,
|
91
116
|
),
|
92
117
|
)
|
93
|
-
self._task_group_context: TaskGroupContext | None
|
94
118
|
object.__setattr__(
|
95
119
|
self,
|
96
120
|
"_task_group_context",
|
@@ -99,35 +123,27 @@ class ScopeContext:
|
|
99
123
|
else None,
|
100
124
|
)
|
101
125
|
|
102
|
-
def __setattr__(
|
103
|
-
self,
|
104
|
-
name: str,
|
105
|
-
value: Any,
|
106
|
-
) -> Any:
|
107
|
-
raise AttributeError(
|
108
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
109
|
-
f" attribute - '{name}' cannot be modified"
|
110
|
-
)
|
111
|
-
|
112
|
-
def __delattr__(
|
113
|
-
self,
|
114
|
-
name: str,
|
115
|
-
) -> None:
|
116
|
-
raise AttributeError(
|
117
|
-
f"Can't modify immutable {self.__class__.__qualname__},"
|
118
|
-
f" attribute - '{name}' cannot be deleted"
|
119
|
-
)
|
120
|
-
|
121
126
|
def __enter__(self) -> str:
|
122
127
|
assert ( # nosec: B101
|
123
128
|
self._task_group_context is None or self._identifier.is_root
|
124
129
|
), "Can't enter synchronous context with task group"
|
125
130
|
assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
|
131
|
+
assert self._resolved_state_context is None # nosec: B101
|
132
|
+
assert self._presets is None, "Can't enter synchronous context with presets" # nosec: B101
|
126
133
|
self._identifier.__enter__()
|
127
134
|
self._observability_context.__enter__()
|
128
|
-
|
135
|
+
# For sync context, only use explicit state (no presets or disposables allowed)
|
136
|
+
resolved_state_context: StateContext = StateContext(
|
137
|
+
_state=ScopeState((*self._captured_state, *self._state))
|
138
|
+
)
|
139
|
+
object.__setattr__(
|
140
|
+
self,
|
141
|
+
"_resolved_state_context",
|
142
|
+
resolved_state_context,
|
143
|
+
)
|
144
|
+
resolved_state_context.__enter__()
|
129
145
|
|
130
|
-
return self._observability_context.observability.trace_identifying(self._identifier)
|
146
|
+
return str(self._observability_context.observability.trace_identifying(self._identifier))
|
131
147
|
|
132
148
|
def __exit__(
|
133
149
|
self,
|
@@ -135,11 +151,17 @@ class ScopeContext:
|
|
135
151
|
exc_val: BaseException | None,
|
136
152
|
exc_tb: TracebackType | None,
|
137
153
|
) -> None:
|
138
|
-
self.
|
154
|
+
assert self._resolved_state_context is not None # nosec: B101
|
155
|
+
self._resolved_state_context.__exit__(
|
139
156
|
exc_type=exc_type,
|
140
157
|
exc_val=exc_val,
|
141
158
|
exc_tb=exc_tb,
|
142
159
|
)
|
160
|
+
object.__setattr__(
|
161
|
+
self,
|
162
|
+
"_resolved_state_context",
|
163
|
+
None,
|
164
|
+
)
|
143
165
|
self._observability_context.__exit__(
|
144
166
|
exc_type=exc_type,
|
145
167
|
exc_val=exc_val,
|
@@ -152,31 +174,49 @@ class ScopeContext:
|
|
152
174
|
)
|
153
175
|
|
154
176
|
async def __aenter__(self) -> str:
|
177
|
+
assert self._presets_disposables is None # nosec: B101
|
178
|
+
assert self._resolved_state_context is None # nosec: B101
|
155
179
|
self._identifier.__enter__()
|
156
180
|
self._observability_context.__enter__()
|
157
181
|
|
158
182
|
if task_group := self._task_group_context:
|
159
183
|
await task_group.__aenter__()
|
160
184
|
|
161
|
-
#
|
162
|
-
|
163
|
-
|
185
|
+
# Collect all state sources in priority order (lowest to highest priority)
|
186
|
+
collected_state: list[State] = []
|
187
|
+
|
188
|
+
# 1. Add contextual state first (lowest priority)
|
189
|
+
collected_state.extend(self._captured_state)
|
190
|
+
|
191
|
+
# 2. Add preset state (low priority, overrides contextual)
|
192
|
+
if self._presets is not None:
|
193
|
+
presets_disposables: Disposables = await self._presets.prepare()
|
164
194
|
object.__setattr__(
|
165
195
|
self,
|
166
|
-
"
|
167
|
-
|
168
|
-
state=ScopeState(
|
169
|
-
(
|
170
|
-
*self._state_context._state._state.values(),
|
171
|
-
*await disposables.prepare(),
|
172
|
-
)
|
173
|
-
),
|
174
|
-
),
|
196
|
+
"_presets_disposables",
|
197
|
+
presets_disposables,
|
175
198
|
)
|
199
|
+
collected_state.extend(await presets_disposables.prepare())
|
200
|
+
|
201
|
+
# 3. Add explicit disposables state (medium priority)
|
202
|
+
if self._disposables is not None:
|
203
|
+
collected_state.extend(await self._disposables.prepare())
|
204
|
+
|
205
|
+
# 4. Add explicit state last (highest priority)
|
206
|
+
collected_state.extend(self._state)
|
207
|
+
# Create resolved state context with all collected state
|
208
|
+
resolved_state_context: StateContext = StateContext(
|
209
|
+
_state=ScopeState(tuple(collected_state))
|
210
|
+
)
|
176
211
|
|
177
|
-
|
212
|
+
resolved_state_context.__enter__()
|
213
|
+
object.__setattr__(
|
214
|
+
self,
|
215
|
+
"_resolved_state_context",
|
216
|
+
resolved_state_context,
|
217
|
+
)
|
178
218
|
|
179
|
-
return self._observability_context.observability.trace_identifying(self._identifier)
|
219
|
+
return str(self._observability_context.observability.trace_identifying(self._identifier))
|
180
220
|
|
181
221
|
async def __aexit__(
|
182
222
|
self,
|
@@ -184,12 +224,25 @@ class ScopeContext:
|
|
184
224
|
exc_val: BaseException | None,
|
185
225
|
exc_tb: TracebackType | None,
|
186
226
|
) -> None:
|
187
|
-
|
188
|
-
|
227
|
+
assert self._resolved_state_context is not None # nosec: B101
|
228
|
+
if self._disposables is not None:
|
229
|
+
await self._disposables.dispose(
|
230
|
+
exc_type=exc_type,
|
231
|
+
exc_val=exc_val,
|
232
|
+
exc_tb=exc_tb,
|
233
|
+
)
|
234
|
+
|
235
|
+
if self._presets_disposables is not None:
|
236
|
+
await self._presets_disposables.dispose(
|
189
237
|
exc_type=exc_type,
|
190
238
|
exc_val=exc_val,
|
191
239
|
exc_tb=exc_tb,
|
192
240
|
)
|
241
|
+
object.__setattr__(
|
242
|
+
self,
|
243
|
+
"_presets_disposables",
|
244
|
+
None,
|
245
|
+
)
|
193
246
|
|
194
247
|
if task_group := self._task_group_context:
|
195
248
|
await task_group.__aexit__(
|
@@ -198,11 +251,16 @@ class ScopeContext:
|
|
198
251
|
exc_tb=exc_tb,
|
199
252
|
)
|
200
253
|
|
201
|
-
self.
|
254
|
+
self._resolved_state_context.__exit__(
|
202
255
|
exc_type=exc_type,
|
203
256
|
exc_val=exc_val,
|
204
257
|
exc_tb=exc_tb,
|
205
258
|
)
|
259
|
+
object.__setattr__(
|
260
|
+
self,
|
261
|
+
"_resolved_state_context",
|
262
|
+
None,
|
263
|
+
)
|
206
264
|
|
207
265
|
self._observability_context.__exit__(
|
208
266
|
exc_type=exc_type,
|
@@ -258,9 +316,90 @@ class ctx:
|
|
258
316
|
"""
|
259
317
|
return ObservabilityContext.trace_id(scope_identifier)
|
260
318
|
|
319
|
+
@staticmethod
|
320
|
+
def presets(
|
321
|
+
*presets: ContextPresets,
|
322
|
+
) -> ContextPresetsRegistryContext:
|
323
|
+
"""
|
324
|
+
Create a context manager for a preset registry.
|
325
|
+
|
326
|
+
This method creates a registry of context presets that can be used within
|
327
|
+
nested scopes. Presets allow you to define reusable combinations of state
|
328
|
+
and disposables that can be referenced by name when creating scopes.
|
329
|
+
|
330
|
+
When entering this context manager, the provided presets become available
|
331
|
+
for use with ctx.scope(). The presets are looked up by their name when
|
332
|
+
creating scopes.
|
333
|
+
|
334
|
+
Parameters
|
335
|
+
----------
|
336
|
+
*presets: ContextPresets
|
337
|
+
Variable number of preset configurations to register. Each preset
|
338
|
+
must have a unique name within the registry.
|
339
|
+
|
340
|
+
Returns
|
341
|
+
-------
|
342
|
+
ContextPresetsRegistryContext
|
343
|
+
A context manager that makes the presets available in nested scopes
|
344
|
+
|
345
|
+
Examples
|
346
|
+
--------
|
347
|
+
Basic preset usage:
|
348
|
+
|
349
|
+
>>> from haiway import ctx, State
|
350
|
+
>>> from haiway.context import ContextPresets
|
351
|
+
>>>
|
352
|
+
>>> class ApiConfig(State):
|
353
|
+
... base_url: str
|
354
|
+
... timeout: int = 30
|
355
|
+
>>>
|
356
|
+
>>> # Define presets
|
357
|
+
>>> dev_preset = ContextPresets(
|
358
|
+
... name="development",
|
359
|
+
... _state=[ApiConfig(base_url="https://dev-api.example.com")]
|
360
|
+
... )
|
361
|
+
>>>
|
362
|
+
>>> prod_preset = ContextPresets(
|
363
|
+
... name="production",
|
364
|
+
... _state=[ApiConfig(base_url="https://api.example.com", timeout=60)]
|
365
|
+
... )
|
366
|
+
>>>
|
367
|
+
>>> # Use presets
|
368
|
+
>>> with ctx.presets(dev_preset, prod_preset):
|
369
|
+
... async with ctx.scope("development"):
|
370
|
+
... config = ctx.state(ApiConfig)
|
371
|
+
... assert config.base_url == "https://dev-api.example.com"
|
372
|
+
|
373
|
+
Nested preset registries:
|
374
|
+
|
375
|
+
>>> base_presets = [dev_preset, prod_preset]
|
376
|
+
>>> override_preset = ContextPresets(
|
377
|
+
... name="development",
|
378
|
+
... _state=[ApiConfig(base_url="https://staging.example.com")]
|
379
|
+
... )
|
380
|
+
>>>
|
381
|
+
>>> with ctx.presets(*base_presets):
|
382
|
+
... # Outer registry has dev and prod presets
|
383
|
+
... with ctx.presets(override_preset):
|
384
|
+
... # Inner registry overrides dev preset
|
385
|
+
... async with ctx.scope("development"):
|
386
|
+
... config = ctx.state(ApiConfig)
|
387
|
+
... assert config.base_url == "https://staging.example.com"
|
388
|
+
|
389
|
+
See Also
|
390
|
+
--------
|
391
|
+
ContextPresets : For creating individual preset configurations
|
392
|
+
ctx.scope : For creating scopes that can use presets
|
393
|
+
"""
|
394
|
+
return ContextPresetsRegistryContext(
|
395
|
+
registry=ContextPresetsRegistry(
|
396
|
+
presets=presets,
|
397
|
+
),
|
398
|
+
)
|
399
|
+
|
261
400
|
@staticmethod
|
262
401
|
def scope(
|
263
|
-
|
402
|
+
name: str,
|
264
403
|
/,
|
265
404
|
*state: State | None,
|
266
405
|
disposables: Disposables | Iterable[Disposable] | None = None,
|
@@ -271,10 +410,22 @@ class ctx:
|
|
271
410
|
Prepare scope context with given parameters. When called within an existing context\
|
272
411
|
it becomes nested with current context as its parent.
|
273
412
|
|
413
|
+
State Priority System
|
414
|
+
---------------------
|
415
|
+
State resolution follows a 4-layer priority system (highest to lowest):
|
416
|
+
|
417
|
+
1. **Explicit state** (passed to ctx.scope()) - HIGHEST priority
|
418
|
+
2. **Explicit disposables** (passed to ctx.scope()) - medium priority
|
419
|
+
3. **Preset state** (from presets) - low priority
|
420
|
+
4. **Contextual state** (from parent contexts) - LOWEST priority
|
421
|
+
|
422
|
+
When state types conflict, higher priority sources override lower priority ones.
|
423
|
+
State objects are resolved by type, with the highest priority instance winning.
|
424
|
+
|
274
425
|
Parameters
|
275
426
|
----------
|
276
|
-
|
277
|
-
name of the scope context
|
427
|
+
name: str
|
428
|
+
name of the scope context, can be associated with state presets
|
278
429
|
|
279
430
|
*state: State | None
|
280
431
|
state propagated within the scope context, will be merged with current state by\
|
@@ -313,7 +464,7 @@ class ctx:
|
|
313
464
|
resolved_disposables = Disposables(*iterable)
|
314
465
|
|
315
466
|
return ScopeContext(
|
316
|
-
|
467
|
+
name=name,
|
317
468
|
task_group=task_group,
|
318
469
|
state=tuple(element for element in state if element is not None),
|
319
470
|
disposables=resolved_disposables,
|
haiway/context/disposables.py
CHANGED
@@ -2,19 +2,15 @@ from asyncio import (
|
|
2
2
|
AbstractEventLoop,
|
3
3
|
gather,
|
4
4
|
get_running_loop,
|
5
|
-
iscoroutinefunction,
|
6
5
|
run_coroutine_threadsafe,
|
7
6
|
wrap_future,
|
8
7
|
)
|
9
|
-
from collections.abc import
|
8
|
+
from collections.abc import Collection, Iterable
|
10
9
|
from contextlib import AbstractAsyncContextManager
|
11
10
|
from itertools import chain
|
12
11
|
from types import TracebackType
|
13
|
-
from typing import Any, final
|
14
12
|
|
15
|
-
from haiway.
|
16
|
-
from haiway.state import State
|
17
|
-
from haiway.utils.mimic import mimic_function
|
13
|
+
from haiway.state import Immutable, State
|
18
14
|
|
19
15
|
__all__ = (
|
20
16
|
"Disposable",
|
@@ -57,8 +53,7 @@ Creating a disposable database connection:
|
|
57
53
|
"""
|
58
54
|
|
59
55
|
|
60
|
-
|
61
|
-
class Disposables:
|
56
|
+
class Disposables(Immutable):
|
62
57
|
"""
|
63
58
|
A container for multiple Disposable resources that manages their lifecycle.
|
64
59
|
|
@@ -112,11 +107,8 @@ class Disposables:
|
|
112
107
|
... # All resources cleaned up automatically
|
113
108
|
"""
|
114
109
|
|
115
|
-
|
116
|
-
|
117
|
-
"_loop",
|
118
|
-
"_state_context",
|
119
|
-
)
|
110
|
+
_disposables: Collection[Disposable]
|
111
|
+
_loop: AbstractEventLoop | None
|
120
112
|
|
121
113
|
def __init__(
|
122
114
|
self,
|
@@ -130,44 +122,17 @@ class Disposables:
|
|
130
122
|
*disposables: Disposable
|
131
123
|
Variable number of disposable resources to be managed together.
|
132
124
|
"""
|
133
|
-
self._disposables: tuple[Disposable, ...]
|
134
125
|
object.__setattr__(
|
135
126
|
self,
|
136
127
|
"_disposables",
|
137
128
|
disposables,
|
138
129
|
)
|
139
|
-
self._state_context: StateContext | None
|
140
|
-
object.__setattr__(
|
141
|
-
self,
|
142
|
-
"_state_context",
|
143
|
-
None,
|
144
|
-
)
|
145
|
-
self._loop: AbstractEventLoop | None
|
146
130
|
object.__setattr__(
|
147
131
|
self,
|
148
132
|
"_loop",
|
149
133
|
None,
|
150
134
|
)
|
151
135
|
|
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
136
|
def __bool__(self) -> bool:
|
172
137
|
"""
|
173
138
|
Check if this container has any disposables.
|
@@ -215,23 +180,6 @@ class Disposables:
|
|
215
180
|
)
|
216
181
|
)
|
217
182
|
|
218
|
-
async def __aenter__(self) -> None:
|
219
|
-
"""
|
220
|
-
Enter all contained disposables asynchronously.
|
221
|
-
|
222
|
-
Enters all disposables in parallel and collects any State objects they return updating
|
223
|
-
current state context.
|
224
|
-
"""
|
225
|
-
|
226
|
-
assert self._state_context is None, "Context reentrance is not allowed" # nosec: B101
|
227
|
-
state_context = StateContext.updated(await self.prepare())
|
228
|
-
state_context.__enter__()
|
229
|
-
object.__setattr__(
|
230
|
-
self,
|
231
|
-
"_state_context",
|
232
|
-
state_context,
|
233
|
-
)
|
234
|
-
|
235
183
|
async def _cleanup(
|
236
184
|
self,
|
237
185
|
/,
|
@@ -320,65 +268,3 @@ class Disposables:
|
|
320
268
|
|
321
269
|
case _:
|
322
270
|
raise BaseExceptionGroup("Disposables cleanup errors", exceptions)
|
323
|
-
|
324
|
-
async def __aexit__(
|
325
|
-
self,
|
326
|
-
exc_type: type[BaseException] | None,
|
327
|
-
exc_val: BaseException | None,
|
328
|
-
exc_tb: TracebackType | None,
|
329
|
-
) -> None:
|
330
|
-
"""
|
331
|
-
Exit all contained disposables asynchronously.
|
332
|
-
|
333
|
-
Properly disposes of all resources by calling their __aexit__ methods in parallel.
|
334
|
-
If multiple disposables raise exceptions, they are collected into a BaseExceptionGroup.
|
335
|
-
Additionally, produced state context will be also exited resetting state to previous.
|
336
|
-
|
337
|
-
Parameters
|
338
|
-
----------
|
339
|
-
exc_type: type[BaseException] | None
|
340
|
-
The type of exception that caused the context to be exited
|
341
|
-
exc_val: BaseException | None
|
342
|
-
The exception that caused the context to be exited
|
343
|
-
exc_tb: TracebackType | None
|
344
|
-
The traceback for the exception that caused the context to be exited
|
345
|
-
|
346
|
-
Raises
|
347
|
-
------
|
348
|
-
BaseExceptionGroup
|
349
|
-
If multiple disposables raise exceptions during exit
|
350
|
-
"""
|
351
|
-
assert self._state_context is not None, "Unbalanced context enter/exit" # nosec: B101
|
352
|
-
try:
|
353
|
-
self._state_context.__exit__(
|
354
|
-
exc_type,
|
355
|
-
exc_val,
|
356
|
-
exc_tb,
|
357
|
-
)
|
358
|
-
object.__setattr__(
|
359
|
-
self,
|
360
|
-
"_state_context",
|
361
|
-
None,
|
362
|
-
)
|
363
|
-
|
364
|
-
finally:
|
365
|
-
await self.dispose(
|
366
|
-
exc_type,
|
367
|
-
exc_val,
|
368
|
-
exc_tb,
|
369
|
-
)
|
370
|
-
|
371
|
-
def __call__[Result, **Arguments](
|
372
|
-
self,
|
373
|
-
function: Callable[Arguments, Coroutine[Any, Any, Result]],
|
374
|
-
) -> Callable[Arguments, Coroutine[Any, Any, Result]]:
|
375
|
-
assert iscoroutinefunction(function) # nosec: B101
|
376
|
-
|
377
|
-
async def async_context(
|
378
|
-
*args: Arguments.args,
|
379
|
-
**kwargs: Arguments.kwargs,
|
380
|
-
) -> Result:
|
381
|
-
async with self:
|
382
|
-
return await function(*args, **kwargs)
|
383
|
-
|
384
|
-
return mimic_function(function, within=async_context)
|