haiway 0.19.5__py3-none-any.whl → 0.20.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 +2 -0
- haiway/context/__init__.py +2 -0
- haiway/context/access.py +88 -8
- haiway/context/disposables.py +63 -0
- haiway/context/identifier.py +81 -27
- haiway/context/observability.py +303 -7
- haiway/context/state.py +126 -0
- haiway/context/tasks.py +66 -0
- haiway/context/types.py +16 -0
- haiway/helpers/asynchrony.py +61 -12
- haiway/helpers/caching.py +31 -0
- haiway/helpers/concurrent.py +25 -14
- haiway/helpers/observability.py +94 -11
- haiway/helpers/retries.py +59 -18
- haiway/helpers/throttling.py +42 -15
- haiway/helpers/timeouted.py +25 -10
- haiway/helpers/tracing.py +31 -0
- haiway/opentelemetry/observability.py +346 -29
- haiway/state/attributes.py +104 -0
- haiway/state/path.py +427 -12
- haiway/state/requirement.py +196 -0
- haiway/state/structure.py +367 -1
- haiway/state/validation.py +293 -0
- haiway/types/default.py +56 -0
- haiway/types/frozen.py +18 -0
- haiway/types/missing.py +89 -0
- haiway/utils/collections.py +36 -28
- haiway/utils/env.py +145 -13
- haiway/utils/formatting.py +27 -0
- haiway/utils/freezing.py +21 -1
- haiway/utils/noop.py +34 -2
- haiway/utils/queue.py +68 -1
- haiway/utils/stream.py +83 -0
- {haiway-0.19.5.dist-info → haiway-0.20.1.dist-info}/METADATA +1 -1
- haiway-0.20.1.dist-info/RECORD +46 -0
- haiway-0.19.5.dist-info/RECORD +0 -46
- {haiway-0.19.5.dist-info → haiway-0.20.1.dist-info}/WHEEL +0 -0
- {haiway-0.19.5.dist-info → haiway-0.20.1.dist-info}/licenses/LICENSE +0 -0
haiway/utils/collections.py
CHANGED
@@ -36,14 +36,15 @@ def as_list[T](
|
|
36
36
|
Parameters
|
37
37
|
----------
|
38
38
|
collection : Iterable[T] | None
|
39
|
-
The input to be converted.
|
39
|
+
The input collection to be converted to a list.
|
40
|
+
If None is provided, None is returned.
|
40
41
|
|
41
42
|
Returns
|
42
43
|
-------
|
43
44
|
list[T] | None
|
44
|
-
A new list containing all elements of the input
|
45
|
-
|
46
|
-
None if
|
45
|
+
A new list containing all elements of the input collection,
|
46
|
+
or the original list if it was already one.
|
47
|
+
Returns None if None was provided.
|
47
48
|
"""
|
48
49
|
|
49
50
|
if collection is None:
|
@@ -79,15 +80,16 @@ def as_tuple[T](
|
|
79
80
|
|
80
81
|
Parameters
|
81
82
|
----------
|
82
|
-
collection :
|
83
|
-
The input to be converted.
|
83
|
+
collection : Iterable[T] | None
|
84
|
+
The input collection to be converted to a tuple.
|
85
|
+
If None is provided, None is returned.
|
84
86
|
|
85
87
|
Returns
|
86
88
|
-------
|
87
|
-
tuple[T] | None
|
88
|
-
A new tuple containing all elements of the input
|
89
|
-
|
90
|
-
None if
|
89
|
+
tuple[T, ...] | None
|
90
|
+
A new tuple containing all elements of the input collection,
|
91
|
+
or the original tuple if it was already one.
|
92
|
+
Returns None if None was provided.
|
91
93
|
"""
|
92
94
|
|
93
95
|
if collection is None:
|
@@ -123,15 +125,16 @@ def as_set[T](
|
|
123
125
|
|
124
126
|
Parameters
|
125
127
|
----------
|
126
|
-
collection : Set[T]
|
127
|
-
The input collection to be converted.
|
128
|
+
collection : Set[T] | None
|
129
|
+
The input collection to be converted to a set.
|
130
|
+
If None is provided, None is returned.
|
128
131
|
|
129
132
|
Returns
|
130
133
|
-------
|
131
|
-
set[T]
|
132
|
-
A new set containing all elements of the input collection
|
133
|
-
|
134
|
-
None if
|
134
|
+
set[T] | None
|
135
|
+
A new set containing all elements of the input collection,
|
136
|
+
or the original set if it was already one.
|
137
|
+
Returns None if None was provided.
|
135
138
|
"""
|
136
139
|
|
137
140
|
if collection is None:
|
@@ -167,15 +170,16 @@ def as_dict[K, V](
|
|
167
170
|
|
168
171
|
Parameters
|
169
172
|
----------
|
170
|
-
collection : Mapping[K, V]
|
171
|
-
The input collection to be converted.
|
173
|
+
collection : Mapping[K, V] | None
|
174
|
+
The input collection to be converted to a dict.
|
175
|
+
If None is provided, None is returned.
|
172
176
|
|
173
177
|
Returns
|
174
178
|
-------
|
175
|
-
dict[K, V]
|
176
|
-
A new dict containing all elements of the input collection
|
177
|
-
|
178
|
-
None if
|
179
|
+
dict[K, V] | None
|
180
|
+
A new dict containing all elements of the input collection,
|
181
|
+
or the original dict if it was already one.
|
182
|
+
Returns None if None was provided.
|
179
183
|
"""
|
180
184
|
|
181
185
|
if collection is None:
|
@@ -211,17 +215,21 @@ def without_missing[T: Mapping[str, Any]](
|
|
211
215
|
typed: type[T] | None = None,
|
212
216
|
) -> T | Mapping[str, Any]:
|
213
217
|
"""
|
214
|
-
|
218
|
+
Create a new mapping without any items that have MISSING values.
|
215
219
|
|
216
220
|
Parameters
|
217
221
|
----------
|
218
|
-
mapping : Mapping[
|
219
|
-
The input mapping to be
|
222
|
+
mapping : Mapping[str, Any]
|
223
|
+
The input mapping to be filtered.
|
224
|
+
typed : type[T] | None, default=None
|
225
|
+
Optional type to cast the result to. If provided, the result will be
|
226
|
+
cast to this type before returning.
|
220
227
|
|
221
228
|
Returns
|
222
229
|
-------
|
223
|
-
T |
|
224
|
-
A new mapping containing all items of the input mapping
|
225
|
-
|
230
|
+
T | Mapping[str, Any]
|
231
|
+
A new mapping containing all items of the input mapping,
|
232
|
+
except items with MISSING values. If typed is provided,
|
233
|
+
the result is cast to that type.
|
226
234
|
"""
|
227
235
|
return cast(T, {key: value for key, value in mapping.items() if value is not MISSING})
|
haiway/utils/env.py
CHANGED
@@ -44,6 +44,32 @@ def getenv_bool(
|
|
44
44
|
*,
|
45
45
|
required: bool = False,
|
46
46
|
) -> bool | None:
|
47
|
+
"""
|
48
|
+
Get a boolean value from an environment variable.
|
49
|
+
|
50
|
+
Interprets 'true', '1', and 't' (case-insensitive) as True,
|
51
|
+
any other value as False.
|
52
|
+
|
53
|
+
Parameters
|
54
|
+
----------
|
55
|
+
key : str
|
56
|
+
The environment variable name to retrieve
|
57
|
+
default : bool | None, optional
|
58
|
+
Value to return if the environment variable is not set
|
59
|
+
required : bool, default=False
|
60
|
+
If True and the environment variable is not set and no default is provided,
|
61
|
+
raises a ValueError
|
62
|
+
|
63
|
+
Returns
|
64
|
+
-------
|
65
|
+
bool | None
|
66
|
+
The boolean value from the environment variable, or the default value
|
67
|
+
|
68
|
+
Raises
|
69
|
+
------
|
70
|
+
ValueError
|
71
|
+
If required=True, the environment variable is not set, and no default is provided
|
72
|
+
"""
|
47
73
|
if value := getenv(key=key):
|
48
74
|
return value.lower() in ("true", "1", "t")
|
49
75
|
|
@@ -85,6 +111,30 @@ def getenv_int(
|
|
85
111
|
*,
|
86
112
|
required: bool = False,
|
87
113
|
) -> int | None:
|
114
|
+
"""
|
115
|
+
Get an integer value from an environment variable.
|
116
|
+
|
117
|
+
Parameters
|
118
|
+
----------
|
119
|
+
key : str
|
120
|
+
The environment variable name to retrieve
|
121
|
+
default : int | None, optional
|
122
|
+
Value to return if the environment variable is not set
|
123
|
+
required : bool, default=False
|
124
|
+
If True and the environment variable is not set and no default is provided,
|
125
|
+
raises a ValueError
|
126
|
+
|
127
|
+
Returns
|
128
|
+
-------
|
129
|
+
int | None
|
130
|
+
The integer value from the environment variable, or the default value
|
131
|
+
|
132
|
+
Raises
|
133
|
+
------
|
134
|
+
ValueError
|
135
|
+
If the environment variable is set but cannot be converted to an integer,
|
136
|
+
or if required=True, the environment variable is not set, and no default is provided
|
137
|
+
"""
|
88
138
|
if value := getenv(key=key):
|
89
139
|
try:
|
90
140
|
return int(value)
|
@@ -130,6 +180,30 @@ def getenv_float(
|
|
130
180
|
*,
|
131
181
|
required: bool = False,
|
132
182
|
) -> float | None:
|
183
|
+
"""
|
184
|
+
Get a float value from an environment variable.
|
185
|
+
|
186
|
+
Parameters
|
187
|
+
----------
|
188
|
+
key : str
|
189
|
+
The environment variable name to retrieve
|
190
|
+
default : float | None, optional
|
191
|
+
Value to return if the environment variable is not set
|
192
|
+
required : bool, default=False
|
193
|
+
If True and the environment variable is not set and no default is provided,
|
194
|
+
raises a ValueError
|
195
|
+
|
196
|
+
Returns
|
197
|
+
-------
|
198
|
+
float | None
|
199
|
+
The float value from the environment variable, or the default value
|
200
|
+
|
201
|
+
Raises
|
202
|
+
------
|
203
|
+
ValueError
|
204
|
+
If the environment variable is set but cannot be converted to a float,
|
205
|
+
or if required=True, the environment variable is not set, and no default is provided
|
206
|
+
"""
|
133
207
|
if value := getenv(key=key):
|
134
208
|
try:
|
135
209
|
return float(value)
|
@@ -175,6 +249,29 @@ def getenv_str(
|
|
175
249
|
*,
|
176
250
|
required: bool = False,
|
177
251
|
) -> str | None:
|
252
|
+
"""
|
253
|
+
Get a string value from an environment variable.
|
254
|
+
|
255
|
+
Parameters
|
256
|
+
----------
|
257
|
+
key : str
|
258
|
+
The environment variable name to retrieve
|
259
|
+
default : str | None, optional
|
260
|
+
Value to return if the environment variable is not set
|
261
|
+
required : bool, default=False
|
262
|
+
If True and the environment variable is not set and no default is provided,
|
263
|
+
raises a ValueError
|
264
|
+
|
265
|
+
Returns
|
266
|
+
-------
|
267
|
+
str | None
|
268
|
+
The string value from the environment variable, or the default value
|
269
|
+
|
270
|
+
Raises
|
271
|
+
------
|
272
|
+
ValueError
|
273
|
+
If required=True, the environment variable is not set, and no default is provided
|
274
|
+
"""
|
178
275
|
if value := getenv(key=key):
|
179
276
|
return value
|
180
277
|
|
@@ -222,6 +319,31 @@ def getenv_base64[Value](
|
|
222
319
|
decoder: Callable[[bytes], Value],
|
223
320
|
required: bool = False,
|
224
321
|
) -> Value | None:
|
322
|
+
"""
|
323
|
+
Get a base64-encoded value from an environment variable and decode it.
|
324
|
+
|
325
|
+
Parameters
|
326
|
+
----------
|
327
|
+
key : str
|
328
|
+
The environment variable name to retrieve
|
329
|
+
default : Value | None, optional
|
330
|
+
Value to return if the environment variable is not set
|
331
|
+
decoder : Callable[[bytes], Value]
|
332
|
+
Function to decode the base64-decoded bytes into the desired type
|
333
|
+
required : bool, default=False
|
334
|
+
If True and the environment variable is not set and no default is provided,
|
335
|
+
raises a ValueError
|
336
|
+
|
337
|
+
Returns
|
338
|
+
-------
|
339
|
+
Value | None
|
340
|
+
The decoded value from the environment variable, or the default value
|
341
|
+
|
342
|
+
Raises
|
343
|
+
------
|
344
|
+
ValueError
|
345
|
+
If required=True, the environment variable is not set, and no default is provided
|
346
|
+
"""
|
225
347
|
if value := getenv(key=key):
|
226
348
|
return decoder(b64decode(value))
|
227
349
|
|
@@ -236,22 +358,32 @@ def load_env(
|
|
236
358
|
path: str | None = None,
|
237
359
|
override: bool = True,
|
238
360
|
) -> None:
|
239
|
-
"""
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
-
|
361
|
+
"""
|
362
|
+
Load environment variables from a .env file.
|
363
|
+
|
364
|
+
A minimalist implementation that reads key-value pairs from the specified file
|
365
|
+
and sets them as environment variables. If the file doesn't exist, the function
|
366
|
+
silently continues without loading any variables.
|
367
|
+
|
368
|
+
The file format follows these rules:
|
369
|
+
- Lines starting with '#' are treated as comments and ignored
|
370
|
+
- Each variable must be on a separate line in the format `KEY=VALUE`
|
371
|
+
- No spaces or additional characters are allowed around the '=' sign
|
372
|
+
- Keys without values are ignored
|
373
|
+
- Inline comments are not supported
|
248
374
|
|
249
375
|
Parameters
|
250
376
|
----------
|
251
|
-
path: str
|
252
|
-
|
253
|
-
override: bool
|
254
|
-
|
377
|
+
path : str | None, default=None
|
378
|
+
Path to the environment file. If None, defaults to '.env'
|
379
|
+
override : bool, default=True
|
380
|
+
If True, overrides existing environment variables with values from the file.
|
381
|
+
If False, only sets variables that don't already exist in the environment.
|
382
|
+
|
383
|
+
Returns
|
384
|
+
-------
|
385
|
+
None
|
386
|
+
This function modifies the environment variables but doesn't return anything.
|
255
387
|
"""
|
256
388
|
|
257
389
|
try:
|
haiway/utils/formatting.py
CHANGED
@@ -10,6 +10,33 @@ def format_str( # noqa: PLR0911
|
|
10
10
|
value: Any,
|
11
11
|
/,
|
12
12
|
) -> str:
|
13
|
+
"""
|
14
|
+
Format any Python value into a readable string representation.
|
15
|
+
|
16
|
+
Creates a human-readable string representation of complex data structures,
|
17
|
+
with proper indentation and formatting for nested structures. This is especially
|
18
|
+
useful for logging, debugging, and observability contexts.
|
19
|
+
|
20
|
+
Parameters
|
21
|
+
----------
|
22
|
+
value : Any
|
23
|
+
The value to format as a string
|
24
|
+
|
25
|
+
Returns
|
26
|
+
-------
|
27
|
+
str
|
28
|
+
A formatted string representation of the input value
|
29
|
+
|
30
|
+
Notes
|
31
|
+
-----
|
32
|
+
- Strings are quoted, with multi-line strings using triple quotes
|
33
|
+
- Bytes are prefixed with 'b' and quoted
|
34
|
+
- Mappings (like dictionaries) are formatted with keys and values
|
35
|
+
- Sequences (like lists) are formatted with indices and values
|
36
|
+
- Objects are formatted with their attribute names and values
|
37
|
+
- MISSING values are converted to empty strings
|
38
|
+
- Nested structures maintain proper indentation
|
39
|
+
"""
|
13
40
|
# check for string
|
14
41
|
if isinstance(value, str):
|
15
42
|
if "\n" in value:
|
haiway/utils/freezing.py
CHANGED
@@ -8,7 +8,27 @@ def freeze(
|
|
8
8
|
/,
|
9
9
|
) -> None:
|
10
10
|
"""
|
11
|
-
|
11
|
+
Make an object instance immutable by preventing attribute modification.
|
12
|
+
|
13
|
+
This function modifies the given object to prevent further changes to its attributes.
|
14
|
+
It replaces the object's __setattr__ and __delattr__ methods with ones that raise
|
15
|
+
exceptions, effectively making the object immutable after this function is called.
|
16
|
+
|
17
|
+
Parameters
|
18
|
+
----------
|
19
|
+
instance : object
|
20
|
+
The object instance to make immutable
|
21
|
+
|
22
|
+
Returns
|
23
|
+
-------
|
24
|
+
None
|
25
|
+
The object is modified in-place
|
26
|
+
|
27
|
+
Notes
|
28
|
+
-----
|
29
|
+
- This only affects direct attribute assignments and deletions
|
30
|
+
- Mutable objects contained within the instance can still be modified internally
|
31
|
+
- The object's class remains unchanged, only the specific instance is affected
|
12
32
|
"""
|
13
33
|
|
14
34
|
def frozen_set(
|
haiway/utils/noop.py
CHANGED
@@ -11,7 +11,23 @@ def noop(
|
|
11
11
|
**kwargs: Any,
|
12
12
|
) -> None:
|
13
13
|
"""
|
14
|
-
Placeholder function
|
14
|
+
Placeholder function that accepts any arguments and does nothing.
|
15
|
+
|
16
|
+
This utility function is useful for cases where a callback is required
|
17
|
+
but no action should be taken, such as in testing, as a default handler,
|
18
|
+
or as a placeholder during development.
|
19
|
+
|
20
|
+
Parameters
|
21
|
+
----------
|
22
|
+
*args: Any
|
23
|
+
Any positional arguments, which are ignored
|
24
|
+
**kwargs: Any
|
25
|
+
Any keyword arguments, which are ignored
|
26
|
+
|
27
|
+
Returns
|
28
|
+
-------
|
29
|
+
None
|
30
|
+
This function performs no operation and returns nothing
|
15
31
|
"""
|
16
32
|
|
17
33
|
|
@@ -20,5 +36,21 @@ async def async_noop(
|
|
20
36
|
**kwargs: Any,
|
21
37
|
) -> None:
|
22
38
|
"""
|
23
|
-
|
39
|
+
Asynchronous placeholder function that accepts any arguments and does nothing.
|
40
|
+
|
41
|
+
This utility function is useful for cases where an async callback is required
|
42
|
+
but no action should be taken, such as in testing, as a default async handler,
|
43
|
+
or as a placeholder during asynchronous workflow development.
|
44
|
+
|
45
|
+
Parameters
|
46
|
+
----------
|
47
|
+
*args: Any
|
48
|
+
Any positional arguments, which are ignored
|
49
|
+
**kwargs: Any
|
50
|
+
Any keyword arguments, which are ignored
|
51
|
+
|
52
|
+
Returns
|
53
|
+
-------
|
54
|
+
None
|
55
|
+
This function performs no operation and returns nothing
|
24
56
|
"""
|
haiway/utils/queue.py
CHANGED
@@ -9,7 +9,25 @@ __all__ = ("AsyncQueue",)
|
|
9
9
|
class AsyncQueue[Element](AsyncIterator[Element]):
|
10
10
|
"""
|
11
11
|
Asynchronous queue supporting iteration and finishing.
|
12
|
-
|
12
|
+
|
13
|
+
A queue implementation optimized for asynchronous workflows, providing async
|
14
|
+
iteration over elements and supporting operations like enqueuing elements,
|
15
|
+
finishing the queue, and cancellation.
|
16
|
+
|
17
|
+
Cannot be concurrently consumed by multiple readers - only one consumer
|
18
|
+
can iterate through the queue at a time.
|
19
|
+
|
20
|
+
Parameters
|
21
|
+
----------
|
22
|
+
*elements : Element
|
23
|
+
Initial elements to populate the queue with
|
24
|
+
loop : AbstractEventLoop | None, default=None
|
25
|
+
Event loop to use for async operations. If None, the running loop is used.
|
26
|
+
|
27
|
+
Notes
|
28
|
+
-----
|
29
|
+
This class is immutable with respect to its attributes after initialization.
|
30
|
+
Any attempt to modify its attributes directly will raise an AttributeError.
|
13
31
|
"""
|
14
32
|
|
15
33
|
__slots__ = (
|
@@ -70,6 +88,14 @@ class AsyncQueue[Element](AsyncIterator[Element]):
|
|
70
88
|
|
71
89
|
@property
|
72
90
|
def is_finished(self) -> bool:
|
91
|
+
"""
|
92
|
+
Check if the queue has been marked as finished.
|
93
|
+
|
94
|
+
Returns
|
95
|
+
-------
|
96
|
+
bool
|
97
|
+
True if the queue has been finished, False otherwise
|
98
|
+
"""
|
73
99
|
return self._finish_reason is not None
|
74
100
|
|
75
101
|
def enqueue(
|
@@ -77,6 +103,22 @@ class AsyncQueue[Element](AsyncIterator[Element]):
|
|
77
103
|
element: Element,
|
78
104
|
/,
|
79
105
|
) -> None:
|
106
|
+
"""
|
107
|
+
Add an element to the queue.
|
108
|
+
|
109
|
+
If a consumer is waiting for an element, it will be immediately notified.
|
110
|
+
Otherwise, the element is appended to the queue.
|
111
|
+
|
112
|
+
Parameters
|
113
|
+
----------
|
114
|
+
element : Element
|
115
|
+
The element to add to the queue
|
116
|
+
|
117
|
+
Raises
|
118
|
+
------
|
119
|
+
RuntimeError
|
120
|
+
If the queue has already been finished
|
121
|
+
"""
|
80
122
|
if self.is_finished:
|
81
123
|
raise RuntimeError("AsyncQueue is already finished")
|
82
124
|
|
@@ -90,6 +132,19 @@ class AsyncQueue[Element](AsyncIterator[Element]):
|
|
90
132
|
self,
|
91
133
|
exception: BaseException | None = None,
|
92
134
|
) -> None:
|
135
|
+
"""
|
136
|
+
Mark the queue as finished, optionally with an exception.
|
137
|
+
|
138
|
+
After finishing, no more elements can be enqueued. Any waiting consumers
|
139
|
+
will be notified with the provided exception or StopAsyncIteration.
|
140
|
+
If the queue is already finished, this method does nothing.
|
141
|
+
|
142
|
+
Parameters
|
143
|
+
----------
|
144
|
+
exception : BaseException | None, default=None
|
145
|
+
Optional exception to raise in consumers. If None, StopAsyncIteration
|
146
|
+
is used to signal normal completion.
|
147
|
+
"""
|
93
148
|
if self.is_finished:
|
94
149
|
return # already finished, ignore
|
95
150
|
|
@@ -113,6 +168,12 @@ class AsyncQueue[Element](AsyncIterator[Element]):
|
|
113
168
|
self._waiting.set_exception(self._finish_reason) # pyright: ignore[reportArgumentType]
|
114
169
|
|
115
170
|
def cancel(self) -> None:
|
171
|
+
"""
|
172
|
+
Cancel the queue with a CancelledError exception.
|
173
|
+
|
174
|
+
This is a convenience method that calls finish() with a CancelledError.
|
175
|
+
Any waiting consumers will receive this exception.
|
176
|
+
"""
|
116
177
|
self.finish(exception=CancelledError())
|
117
178
|
|
118
179
|
async def __anext__(self) -> Element:
|
@@ -143,5 +204,11 @@ class AsyncQueue[Element](AsyncIterator[Element]):
|
|
143
204
|
)
|
144
205
|
|
145
206
|
def clear(self) -> None:
|
207
|
+
"""
|
208
|
+
Clear all pending elements from the queue.
|
209
|
+
|
210
|
+
This method removes all elements currently in the queue. It will only
|
211
|
+
clear the queue if no consumer is currently waiting for an element.
|
212
|
+
"""
|
146
213
|
if self._waiting is None or self._waiting.done():
|
147
214
|
self._queue.clear()
|
haiway/utils/stream.py
CHANGED
@@ -10,10 +10,32 @@ __all__ = ("AsyncStream",)
|
|
10
10
|
|
11
11
|
|
12
12
|
class AsyncStream[Element](AsyncIterator[Element]):
|
13
|
+
"""
|
14
|
+
An asynchronous stream implementation supporting push-based async iteration.
|
15
|
+
|
16
|
+
AsyncStream provides a way to create a stream of elements where producers can
|
17
|
+
asynchronously send elements and consumers can iterate over them using async
|
18
|
+
iteration. Only one consumer can iterate through the stream at a time.
|
19
|
+
|
20
|
+
This class implements a flow-controlled stream where the producer waits until
|
21
|
+
the consumer is ready to receive the next element, ensuring back-pressure.
|
22
|
+
|
23
|
+
Unlike AsyncQueue, AsyncStream cannot be reused for multiple iterations and
|
24
|
+
requires coordination between producer and consumer.
|
25
|
+
"""
|
26
|
+
|
13
27
|
def __init__(
|
14
28
|
self,
|
15
29
|
loop: AbstractEventLoop | None = None,
|
16
30
|
) -> None:
|
31
|
+
"""
|
32
|
+
Initialize a new asynchronous stream.
|
33
|
+
|
34
|
+
Parameters
|
35
|
+
----------
|
36
|
+
loop : AbstractEventLoop | None, default=None
|
37
|
+
Event loop to use for async operations. If None, the running loop is used.
|
38
|
+
"""
|
17
39
|
self._loop: AbstractEventLoop = loop or get_running_loop()
|
18
40
|
self._ready: Future[None] = self._loop.create_future()
|
19
41
|
self._waiting: Future[Element] | None = None
|
@@ -21,6 +43,14 @@ class AsyncStream[Element](AsyncIterator[Element]):
|
|
21
43
|
|
22
44
|
@property
|
23
45
|
def finished(self) -> bool:
|
46
|
+
"""
|
47
|
+
Check if the stream has been marked as finished.
|
48
|
+
|
49
|
+
Returns
|
50
|
+
-------
|
51
|
+
bool
|
52
|
+
True if the stream has been finished, False otherwise
|
53
|
+
"""
|
24
54
|
return self._finish_reason is not None
|
25
55
|
|
26
56
|
async def send(
|
@@ -28,6 +58,18 @@ class AsyncStream[Element](AsyncIterator[Element]):
|
|
28
58
|
element: Element,
|
29
59
|
/,
|
30
60
|
) -> None:
|
61
|
+
"""
|
62
|
+
Send an element to the stream.
|
63
|
+
|
64
|
+
This method waits until the consumer is ready to receive the element,
|
65
|
+
implementing back-pressure. If the stream is finished, the element will
|
66
|
+
be silently discarded.
|
67
|
+
|
68
|
+
Parameters
|
69
|
+
----------
|
70
|
+
element : Element
|
71
|
+
The element to send to the stream
|
72
|
+
"""
|
31
73
|
if self._finish_reason is not None:
|
32
74
|
return # already finished
|
33
75
|
|
@@ -47,6 +89,21 @@ class AsyncStream[Element](AsyncIterator[Element]):
|
|
47
89
|
self,
|
48
90
|
exception: BaseException | None = None,
|
49
91
|
) -> None:
|
92
|
+
"""
|
93
|
+
Mark the stream as finished, optionally with an exception.
|
94
|
+
|
95
|
+
After finishing, sent elements will be silently discarded. The consumer
|
96
|
+
will receive the provided exception or StopAsyncIteration when attempting
|
97
|
+
to get the next element.
|
98
|
+
|
99
|
+
If the stream is already finished, this method does nothing.
|
100
|
+
|
101
|
+
Parameters
|
102
|
+
----------
|
103
|
+
exception : BaseException | None, default=None
|
104
|
+
Optional exception to raise in the consumer. If None, StopAsyncIteration
|
105
|
+
is used to signal normal completion.
|
106
|
+
"""
|
50
107
|
if self.finished:
|
51
108
|
return # already finished, ignore
|
52
109
|
|
@@ -73,9 +130,35 @@ class AsyncStream[Element](AsyncIterator[Element]):
|
|
73
130
|
self._waiting.set_exception(self._finish_reason)
|
74
131
|
|
75
132
|
def cancel(self) -> None:
|
133
|
+
"""
|
134
|
+
Cancel the stream with a CancelledError exception.
|
135
|
+
|
136
|
+
This is a convenience method that calls finish() with a CancelledError.
|
137
|
+
The consumer will receive this exception when attempting to get the next element.
|
138
|
+
"""
|
76
139
|
self.finish(exception=CancelledError())
|
77
140
|
|
78
141
|
async def __anext__(self) -> Element:
|
142
|
+
"""
|
143
|
+
Get the next element from the stream.
|
144
|
+
|
145
|
+
This method is called automatically when the stream is used in an
|
146
|
+
async for loop. It waits for the next element to be sent or for
|
147
|
+
the stream to be finished.
|
148
|
+
|
149
|
+
Returns
|
150
|
+
-------
|
151
|
+
Element
|
152
|
+
The next element from the stream
|
153
|
+
|
154
|
+
Raises
|
155
|
+
------
|
156
|
+
BaseException
|
157
|
+
The exception provided to finish(), or StopAsyncIteration if
|
158
|
+
finish() was called without an exception
|
159
|
+
AssertionError
|
160
|
+
If the stream is being consumed by multiple consumers
|
161
|
+
"""
|
79
162
|
assert self._waiting is None, "AsyncStream can't be reused" # nosec: B101
|
80
163
|
|
81
164
|
if self._finish_reason:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20.1
|
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
|