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
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())
|
176
200
|
|
177
|
-
|
201
|
+
# 3. Add explicit disposables state (medium priority)
|
202
|
+
if self._disposables is not None:
|
203
|
+
collected_state.extend(await self._disposables.prepare())
|
178
204
|
|
179
|
-
|
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
|
+
)
|
211
|
+
|
212
|
+
resolved_state_context.__enter__()
|
213
|
+
object.__setattr__(
|
214
|
+
self,
|
215
|
+
"_resolved_state_context",
|
216
|
+
resolved_state_context,
|
217
|
+
)
|
218
|
+
|
219
|
+
return str(self._observability_context.observability.trace_identifying(self._identifier))
|
180
220
|
|
181
221
|
async def __aexit__(
|
182
222
|
self,
|
@@ -184,13 +224,26 @@ 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(
|
189
230
|
exc_type=exc_type,
|
190
231
|
exc_val=exc_val,
|
191
232
|
exc_tb=exc_tb,
|
192
233
|
)
|
193
234
|
|
235
|
+
if self._presets_disposables is not None:
|
236
|
+
await self._presets_disposables.dispose(
|
237
|
+
exc_type=exc_type,
|
238
|
+
exc_val=exc_val,
|
239
|
+
exc_tb=exc_tb,
|
240
|
+
)
|
241
|
+
object.__setattr__(
|
242
|
+
self,
|
243
|
+
"_presets_disposables",
|
244
|
+
None,
|
245
|
+
)
|
246
|
+
|
194
247
|
if task_group := self._task_group_context:
|
195
248
|
await task_group.__aexit__(
|
196
249
|
exc_type=exc_type,
|
@@ -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,
|
@@ -217,6 +275,94 @@ class ScopeContext:
|
|
217
275
|
)
|
218
276
|
|
219
277
|
|
278
|
+
class DisposablesContext(Immutable):
|
279
|
+
"""
|
280
|
+
Immutable async context manager for managing collections of disposables.
|
281
|
+
|
282
|
+
DisposablesContext captures the current contextual state upon initialization
|
283
|
+
and provides an async context manager interface for managing disposable resources.
|
284
|
+
When entered, it prepares the disposables to collect their state, merges it with
|
285
|
+
the captured state, and creates a resolved StateContext. Upon exit, it properly
|
286
|
+
disposes of the disposables and cleans up internal references.
|
287
|
+
|
288
|
+
This class should not be instantiated directly; use the ctx.disposables() factory
|
289
|
+
method to create disposables contexts.
|
290
|
+
"""
|
291
|
+
|
292
|
+
_disposables: Disposables
|
293
|
+
_captured_state: Collection[State]
|
294
|
+
_resolved_state_context: StateContext | None
|
295
|
+
|
296
|
+
def __init__(
|
297
|
+
self,
|
298
|
+
disposables: Disposables,
|
299
|
+
) -> None:
|
300
|
+
# capture current contextual state (without new additions)
|
301
|
+
object.__setattr__(
|
302
|
+
self,
|
303
|
+
"_captured_state",
|
304
|
+
StateContext.current_state(),
|
305
|
+
)
|
306
|
+
# placeholder for temporary, resolved state context
|
307
|
+
object.__setattr__(
|
308
|
+
self,
|
309
|
+
"_resolved_state_context",
|
310
|
+
None,
|
311
|
+
)
|
312
|
+
object.__setattr__(
|
313
|
+
self,
|
314
|
+
"_disposables",
|
315
|
+
disposables,
|
316
|
+
)
|
317
|
+
|
318
|
+
async def __aenter__(self) -> None:
|
319
|
+
assert self._resolved_state_context is None # nosec: B101
|
320
|
+
|
321
|
+
# Collect all state sources in priority order (lowest to highest priority)
|
322
|
+
collected_state: list[State] = []
|
323
|
+
|
324
|
+
collected_state.extend(self._captured_state)
|
325
|
+
|
326
|
+
collected_state.extend(await self._disposables.prepare())
|
327
|
+
|
328
|
+
# Create resolved state context with all collected state
|
329
|
+
resolved_state_context: StateContext = StateContext(
|
330
|
+
_state=ScopeState(tuple(collected_state))
|
331
|
+
)
|
332
|
+
|
333
|
+
resolved_state_context.__enter__()
|
334
|
+
object.__setattr__(
|
335
|
+
self,
|
336
|
+
"_resolved_state_context",
|
337
|
+
resolved_state_context,
|
338
|
+
)
|
339
|
+
|
340
|
+
async def __aexit__(
|
341
|
+
self,
|
342
|
+
exc_type: type[BaseException] | None,
|
343
|
+
exc_val: BaseException | None,
|
344
|
+
exc_tb: TracebackType | None,
|
345
|
+
) -> None:
|
346
|
+
assert self._resolved_state_context is not None # nosec: B101
|
347
|
+
|
348
|
+
await self._disposables.dispose(
|
349
|
+
exc_type=exc_type,
|
350
|
+
exc_val=exc_val,
|
351
|
+
exc_tb=exc_tb,
|
352
|
+
)
|
353
|
+
|
354
|
+
self._resolved_state_context.__exit__(
|
355
|
+
exc_type=exc_type,
|
356
|
+
exc_val=exc_val,
|
357
|
+
exc_tb=exc_tb,
|
358
|
+
)
|
359
|
+
object.__setattr__(
|
360
|
+
self,
|
361
|
+
"_resolved_state_context",
|
362
|
+
None,
|
363
|
+
)
|
364
|
+
|
365
|
+
|
220
366
|
@final
|
221
367
|
class ctx:
|
222
368
|
"""
|
@@ -258,9 +404,90 @@ class ctx:
|
|
258
404
|
"""
|
259
405
|
return ObservabilityContext.trace_id(scope_identifier)
|
260
406
|
|
407
|
+
@staticmethod
|
408
|
+
def presets(
|
409
|
+
*presets: ContextPresets,
|
410
|
+
) -> ContextPresetsRegistryContext:
|
411
|
+
"""
|
412
|
+
Create a context manager for a preset registry.
|
413
|
+
|
414
|
+
This method creates a registry of context presets that can be used within
|
415
|
+
nested scopes. Presets allow you to define reusable combinations of state
|
416
|
+
and disposables that can be referenced by name when creating scopes.
|
417
|
+
|
418
|
+
When entering this context manager, the provided presets become available
|
419
|
+
for use with ctx.scope(). The presets are looked up by their name when
|
420
|
+
creating scopes.
|
421
|
+
|
422
|
+
Parameters
|
423
|
+
----------
|
424
|
+
*presets: ContextPresets
|
425
|
+
Variable number of preset configurations to register. Each preset
|
426
|
+
must have a unique name within the registry.
|
427
|
+
|
428
|
+
Returns
|
429
|
+
-------
|
430
|
+
ContextPresetsRegistryContext
|
431
|
+
A context manager that makes the presets available in nested scopes
|
432
|
+
|
433
|
+
Examples
|
434
|
+
--------
|
435
|
+
Basic preset usage:
|
436
|
+
|
437
|
+
>>> from haiway import ctx, State
|
438
|
+
>>> from haiway.context import ContextPresets
|
439
|
+
>>>
|
440
|
+
>>> class ApiConfig(State):
|
441
|
+
... base_url: str
|
442
|
+
... timeout: int = 30
|
443
|
+
>>>
|
444
|
+
>>> # Define presets
|
445
|
+
>>> dev_preset = ContextPresets(
|
446
|
+
... name="development",
|
447
|
+
... _state=[ApiConfig(base_url="https://dev-api.example.com")]
|
448
|
+
... )
|
449
|
+
>>>
|
450
|
+
>>> prod_preset = ContextPresets(
|
451
|
+
... name="production",
|
452
|
+
... _state=[ApiConfig(base_url="https://api.example.com", timeout=60)]
|
453
|
+
... )
|
454
|
+
>>>
|
455
|
+
>>> # Use presets
|
456
|
+
>>> with ctx.presets(dev_preset, prod_preset):
|
457
|
+
... async with ctx.scope("development"):
|
458
|
+
... config = ctx.state(ApiConfig)
|
459
|
+
... assert config.base_url == "https://dev-api.example.com"
|
460
|
+
|
461
|
+
Nested preset registries:
|
462
|
+
|
463
|
+
>>> base_presets = [dev_preset, prod_preset]
|
464
|
+
>>> override_preset = ContextPresets(
|
465
|
+
... name="development",
|
466
|
+
... _state=[ApiConfig(base_url="https://staging.example.com")]
|
467
|
+
... )
|
468
|
+
>>>
|
469
|
+
>>> with ctx.presets(*base_presets):
|
470
|
+
... # Outer registry has dev and prod presets
|
471
|
+
... with ctx.presets(override_preset):
|
472
|
+
... # Inner registry overrides dev preset
|
473
|
+
... async with ctx.scope("development"):
|
474
|
+
... config = ctx.state(ApiConfig)
|
475
|
+
... assert config.base_url == "https://staging.example.com"
|
476
|
+
|
477
|
+
See Also
|
478
|
+
--------
|
479
|
+
ContextPresets : For creating individual preset configurations
|
480
|
+
ctx.scope : For creating scopes that can use presets
|
481
|
+
"""
|
482
|
+
return ContextPresetsRegistryContext(
|
483
|
+
registry=ContextPresetsRegistry(
|
484
|
+
presets=presets,
|
485
|
+
),
|
486
|
+
)
|
487
|
+
|
261
488
|
@staticmethod
|
262
489
|
def scope(
|
263
|
-
|
490
|
+
name: str,
|
264
491
|
/,
|
265
492
|
*state: State | None,
|
266
493
|
disposables: Disposables | Iterable[Disposable] | None = None,
|
@@ -271,10 +498,22 @@ class ctx:
|
|
271
498
|
Prepare scope context with given parameters. When called within an existing context\
|
272
499
|
it becomes nested with current context as its parent.
|
273
500
|
|
501
|
+
State Priority System
|
502
|
+
---------------------
|
503
|
+
State resolution follows a 4-layer priority system (highest to lowest):
|
504
|
+
|
505
|
+
1. **Explicit state** (passed to ctx.scope()) - HIGHEST priority
|
506
|
+
2. **Explicit disposables** (passed to ctx.scope()) - medium priority
|
507
|
+
3. **Preset state** (from presets) - low priority
|
508
|
+
4. **Contextual state** (from parent contexts) - LOWEST priority
|
509
|
+
|
510
|
+
When state types conflict, higher priority sources override lower priority ones.
|
511
|
+
State objects are resolved by type, with the highest priority instance winning.
|
512
|
+
|
274
513
|
Parameters
|
275
514
|
----------
|
276
|
-
|
277
|
-
name of the scope context
|
515
|
+
name: str
|
516
|
+
name of the scope context, can be associated with state presets
|
278
517
|
|
279
518
|
*state: State | None
|
280
519
|
state propagated within the scope context, will be merged with current state by\
|
@@ -313,7 +552,7 @@ class ctx:
|
|
313
552
|
resolved_disposables = Disposables(*iterable)
|
314
553
|
|
315
554
|
return ScopeContext(
|
316
|
-
|
555
|
+
name=name,
|
317
556
|
task_group=task_group,
|
318
557
|
state=tuple(element for element in state if element is not None),
|
319
558
|
disposables=resolved_disposables,
|
@@ -345,7 +584,7 @@ class ctx:
|
|
345
584
|
@staticmethod
|
346
585
|
def disposables(
|
347
586
|
*disposables: Disposable | None,
|
348
|
-
) ->
|
587
|
+
) -> DisposablesContext:
|
349
588
|
"""
|
350
589
|
Create a container for managing multiple disposable resources.
|
351
590
|
|
@@ -362,9 +601,9 @@ class ctx:
|
|
362
601
|
|
363
602
|
Returns
|
364
603
|
-------
|
365
|
-
|
604
|
+
DisposableContext
|
366
605
|
A container that manages the lifecycle of all provided disposables
|
367
|
-
and propagates their state to the context when used with ctx.scope()
|
606
|
+
and propagates their state to the context as when used with ctx.scope()
|
368
607
|
|
369
608
|
Examples
|
370
609
|
--------
|
@@ -373,16 +612,19 @@ class ctx:
|
|
373
612
|
>>> from haiway import ctx
|
374
613
|
>>> async def main():
|
375
614
|
...
|
376
|
-
... async with ctx.
|
377
|
-
...
|
378
|
-
... disposables=(database_connection(),)
|
615
|
+
... async with ctx.disposables(
|
616
|
+
... database_connection(),
|
379
617
|
... ):
|
380
618
|
... # ConnectionState is now available in context
|
381
619
|
... conn_state = ctx.state(ConnectionState)
|
382
620
|
... await conn_state.connection.execute("SELECT 1")
|
383
621
|
"""
|
384
622
|
|
385
|
-
return
|
623
|
+
return DisposablesContext(
|
624
|
+
disposables=Disposables(
|
625
|
+
*(disposable for disposable in disposables if disposable is not None)
|
626
|
+
)
|
627
|
+
)
|
386
628
|
|
387
629
|
@staticmethod
|
388
630
|
def spawn[Result, **Arguments](
|