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.
@@ -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
- or the original list if it was already one.
46
- None if no value was provided.
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 : Sequence[T] | None
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
- or the original tuple if it was already one.
90
- None if no value was provided.
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
- or the original set if it was already one.
134
- None if no value was provided.
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
- or the original dict if it was already one.
178
- None if no value was provided.
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
- Strip items with missing values.
218
+ Create a new mapping without any items that have MISSING values.
215
219
 
216
220
  Parameters
217
221
  ----------
218
- mapping : Mapping[K, V]
219
- The input mapping to be stripped.
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 | dict[str, Any]
224
- A new mapping containing all items of the input mapping,\
225
- except items with missing values.
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
- Minimalist implementation of environment variables file loader. \
241
- When the file is not available configuration won't be loaded.
242
- Allows only subset of formatting:
243
- - lines starting with '#' are ignored
244
- - other comments are not allowed
245
- - each element is in a new line
246
- - each element must be a `key=value` pair without whitespaces or additional characters
247
- - keys without values are ignored
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
- custom path to load environment variables, default is '.env'
253
- override: bool
254
- override existing variables on conflict if True, otherwise keep existing
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:
@@ -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
- Freeze object instance by replacing __delattr__ and __setattr__ to raising Exceptions.
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 doing nothing (no operation).
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
- Placeholder async function doing nothing (no operation).
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
- Cannot be concurrently consumed by multiple readers.
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.19.5
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