asyncio-extensions 0.3.0__tar.gz → 0.3.2__tar.gz

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.
Files changed (25) hide show
  1. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/.github/workflows/release.yml +2 -2
  2. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/.github/workflows/tests.yml +2 -2
  3. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/.pre-commit-config.yaml +1 -1
  4. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/CHANGELOG.md +24 -13
  5. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/PKG-INFO +5 -4
  6. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/README.md +3 -2
  7. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/src/asyncio_extensions/_compat.py +1 -1
  8. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/src/asyncio_extensions/_iterators.py +13 -6
  9. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/src/asyncio_extensions/_task_groups.py +1 -0
  10. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/tests/test_iterators.py +19 -3
  11. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/.github/dependabot.yml +0 -0
  12. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/.gitignore +0 -0
  13. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/AGENTS.md +0 -0
  14. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/CLAUDE.md +0 -0
  15. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/LICENSE.txt +0 -0
  16. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/codecov.yml +0 -0
  17. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/pyproject.toml +0 -0
  18. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/src/asyncio_extensions/__init__.py +0 -0
  19. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/src/asyncio_extensions/_scheduling.py +0 -0
  20. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/src/asyncio_extensions/_sync.py +0 -0
  21. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/src/asyncio_extensions/py.typed +0 -0
  22. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/tests/__init__.py +0 -0
  23. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/tests/test_scheduling.py +0 -0
  24. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/tests/test_sync.py +0 -0
  25. {asyncio_extensions-0.3.0 → asyncio_extensions-0.3.2}/tests/test_task_groups.py +0 -0
@@ -18,7 +18,7 @@ jobs:
18
18
  persist-credentials: false
19
19
 
20
20
  - name: Set up Python
21
- uses: actions/setup-python@v6
21
+ uses: actions/setup-python@v7
22
22
  with:
23
23
  python-version: "3.x"
24
24
 
@@ -54,4 +54,4 @@ jobs:
54
54
  path: dist/
55
55
 
56
56
  - name: Publish distribution 📦 to PyPI
57
- uses: pypa/gh-action-pypi-publish@v1.14.0
57
+ uses: pypa/gh-action-pypi-publish@v1.14.2
@@ -22,7 +22,7 @@ jobs:
22
22
  - uses: actions/checkout@v7
23
23
 
24
24
  - name: Set up Python 3.13
25
- uses: actions/setup-python@v6
25
+ uses: actions/setup-python@v7
26
26
  with:
27
27
  python-version: "3.13"
28
28
 
@@ -49,7 +49,7 @@ jobs:
49
49
  - uses: actions/checkout@v7
50
50
 
51
51
  - name: Set up Python ${{ matrix.python-version }}
52
- uses: actions/setup-python@v6
52
+ uses: actions/setup-python@v7
53
53
  with:
54
54
  python-version: ${{ matrix.python-version }}
55
55
  allow-prereleases: true
@@ -29,7 +29,7 @@ repos:
29
29
  - id: trailing-whitespace
30
30
 
31
31
  - repo: https://github.com/commitizen-tools/commitizen
32
- rev: v4.16.4
32
+ rev: v4.18.1
33
33
  hooks:
34
34
  - id: commitizen
35
35
  - id: commitizen-branch
@@ -8,6 +8,20 @@ This project adheres to both [Semantic Versioning](https://semver.org/spec/v2.0.
8
8
 
9
9
  ## [UNRELEASED]
10
10
 
11
+ ## [0.3.2] - 2026-09-24
12
+
13
+ ### Fixed
14
+
15
+ - `merge_iterables` no longer raises `ValueError` when called with no arguments.
16
+
17
+ ## [0.3.1] - 2026-09-23
18
+
19
+ ### Changed
20
+
21
+ - `drain` now returns the number of items consumed as an `int` instead of `None`.
22
+
23
+ ## [0.3.0] - 2026-07-03
24
+
11
25
  ### Added
12
26
 
13
27
  - `drain` coroutine to consume and discard remaining items from an async iterator or async iterable.
@@ -18,7 +32,7 @@ This project adheres to both [Semantic Versioning](https://semver.org/spec/v2.0.
18
32
  - Type validation now includes Pyrefly checks for enhanced type safety.
19
33
  - Enabled mypy strict mode to enforce stricter type checking standards.
20
34
  - `STOP` is now a proper sentinel instance (via `typing_extensions.sentinel` on Python < 3.15) instead of a class used as a sentinel. Behaviour is unchanged; only the type changes.
21
- - Use built-in TaskGroup on Python 3.15+
35
+ - Use built-in `TaskGroup` on Python 3.15+.
22
36
 
23
37
  ## [0.2.0] - 2026-05-15
24
38
 
@@ -45,23 +59,16 @@ This project adheres to both [Semantic Versioning](https://semver.org/spec/v2.0.
45
59
 
46
60
  ### Added
47
61
 
48
- - Expose `iscoroutinefunction` and `markcoroutinefunction` functions.
49
- - `iterate_queue` async generator to consume an `asyncio.Queue` as an `async for` loop, stopping automatically when the queue is shut down.
50
- - Expose `iscoroutinefunction` and `markcoroutinefunction` functions.
51
62
  - `iterate_queue` async generator to consume an `asyncio.Queue` as an `async for` loop, stopping automatically when the queue is shut down or when the `STOP` sentinel is dequeued.
52
63
  - `fill_queue` coroutine to fill an `asyncio.Queue` from any sync or async iterable.
53
64
  - `merge_iterables` async context manager to merge multiple sync or async iterables into a single interleaved stream.
54
65
  - `STOP` sentinel object used to signal the end of an `iterate_queue` stream.
66
+ - Expose `iscoroutinefunction` and `markcoroutinefunction` functions.
55
67
 
56
68
  ### Changed
57
69
 
58
70
  - Rename `noop` to `identity` so the utility clearly expresses returning the passed value after yielding once to the event loop.
59
71
 
60
-
61
- ### Changed
62
-
63
- - Renamed `noop` to `identity` so the utility clearly expresses returning the passed value after yielding once to the event loop.
64
-
65
72
  ## [0.0.4] - 2026-03-18
66
73
 
67
74
  ### Added
@@ -75,13 +82,13 @@ This project adheres to both [Semantic Versioning](https://semver.org/spec/v2.0.
75
82
  - This CHANGELOG file.
76
83
  - pre-commit config.
77
84
  - test coverage using Codecov
78
- - LimitedTaskGroup to execute tasks limiting concurrency
79
- - heartbeat function to run something at regular interval
85
+ - `LimitedTaskGroup` to execute tasks limiting concurrency.
86
+ - `heartbeat` function to run something at regular interval.
80
87
 
81
88
  ### Changed
82
89
 
83
90
  - Package building and publishing are run in separate jobs to avoid credential sharing.
84
- - Improved README
91
+ - Improved README.
85
92
 
86
93
  ## [0.0.2] - 2025-12-23
87
94
 
@@ -97,7 +104,11 @@ This project adheres to both [Semantic Versioning](https://semver.org/spec/v2.0.
97
104
  - `checkpoint` function to yield control to the event loop.
98
105
  - `sleep_forever` function.
99
106
 
100
- [unreleased]: https://github.com/hartungstenio/asyncio-extensions/compare/0.1.0...HEAD
107
+ [unreleased]: https://github.com/hartungstenio/asyncio-extensions/compare/0.3.2...HEAD
108
+ [0.3.2]: https://github.com/hartungstenio/asyncio-extensions/compare/0.3.1...0.3.2
109
+ [0.3.1]: https://github.com/hartungstenio/asyncio-extensions/compare/0.3.0...0.3.1
110
+ [0.3.0]: https://github.com/hartungstenio/asyncio-extensions/compare/0.2.0...0.3.0
111
+ [0.2.0]: https://github.com/hartungstenio/asyncio-extensions/compare/0.1.0...0.2.0
101
112
  [0.1.0]: https://github.com/hartungstenio/asyncio-extensions/compare/0.0.5...0.1.0
102
113
  [0.0.5]: https://github.com/hartungstenio/asyncio-extensions/compare/0.0.4...0.0.5
103
114
  [0.0.4]: https://github.com/hartungstenio/asyncio-extensions/compare/0.0.3...0.0.4
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: asyncio-extensions
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Some (maybe) useful extensions for asyncio
5
5
  Project-URL: Changelog, https://github.com/hartungstenio/asyncio-extensions/blob/main/CHANGELOG.md
6
6
  Project-URL: Documentation, https://github.com/hartungstenio/asyncio-extensions#readme
@@ -272,7 +272,7 @@ await fill_queue(source(), queue)
272
272
 
273
273
  ### drain
274
274
 
275
- The `drain` coroutine consumes and discards the remaining items from an async iterator or async iterable. This is useful when you want to stop processing early but must ensure the source is exhausted so producers can finish and resources can be released.
275
+ The `drain` coroutine consumes and discards the remaining items from an async iterator or async iterable, returning the number of items consumed. This is useful when you want to stop processing early but must ensure the source is exhausted so producers can finish and resources can be released.
276
276
 
277
277
  Example — process only the first N items, then discard the rest so the producer can complete:
278
278
 
@@ -287,7 +287,8 @@ async for idx, item in enumerate(iter_items()):
287
287
  break
288
288
  print(item)
289
289
  # ensure the remaining items are consumed and the producer can finish
290
- await drain(stream)
290
+ discarded = await drain(stream)
291
+ print(f"discarded {discarded} items")
291
292
  ```
292
293
  ### merge_iterables
293
294
 
@@ -242,7 +242,7 @@ await fill_queue(source(), queue)
242
242
 
243
243
  ### drain
244
244
 
245
- The `drain` coroutine consumes and discards the remaining items from an async iterator or async iterable. This is useful when you want to stop processing early but must ensure the source is exhausted so producers can finish and resources can be released.
245
+ The `drain` coroutine consumes and discards the remaining items from an async iterator or async iterable, returning the number of items consumed. This is useful when you want to stop processing early but must ensure the source is exhausted so producers can finish and resources can be released.
246
246
 
247
247
  Example — process only the first N items, then discard the rest so the producer can complete:
248
248
 
@@ -257,7 +257,8 @@ async for idx, item in enumerate(iter_items()):
257
257
  break
258
258
  print(item)
259
259
  # ensure the remaining items are consumed and the producer can finish
260
- await drain(stream)
260
+ discarded = await drain(stream)
261
+ print(f"discarded {discarded} items")
261
262
  ```
262
263
  ### merge_iterables
263
264
 
@@ -50,7 +50,7 @@ if sys.version_info >= (3, 15):
50
50
 
51
51
  sentinel = builtins.sentinel
52
52
  else:
53
- from typing_extensions import sentinel # type: ignore[attr-defined]
53
+ from typing_extensions import sentinel
54
54
 
55
55
  __all__ = [
56
56
  "CreateTaskParams",
@@ -96,20 +96,26 @@ async def fill_queue(itr: AsyncIterable[T] | Iterable[T], queue: asyncio.Queue[T
96
96
  await queue.put(it)
97
97
 
98
98
 
99
- async def drain(itr: AsyncIterable[T] | Iterable[T]) -> None:
100
- """Consume the remaining items from *itr*.
99
+ async def drain(itr: AsyncIterable[T] | Iterable[T]) -> int:
100
+ """Consume the remaining items from *itr* and return the count drained.
101
101
 
102
102
  Accepts both sync and async iterables.
103
103
 
104
104
  Args:
105
105
  itr: The iterable (sync or async) to consume.
106
106
 
107
+ Returns:
108
+ The number of items consumed.
109
+
107
110
  Example::
108
111
 
109
- await drain(range(10))
112
+ count = await drain(range(10))
113
+ assert count == 10
110
114
  """
115
+ count = 0
111
116
  async for _ in asyncify_iterable(itr):
112
- pass
117
+ count += 1
118
+ return count
113
119
 
114
120
 
115
121
  @asynccontextmanager
@@ -144,8 +150,9 @@ async def merge_iterables(
144
150
  tasks = [tg.create_task(fill_queue(itr, queue)) for itr in itrs]
145
151
 
146
152
  async def join() -> None:
147
- await asyncio.wait(tasks)
148
- await queue.put(STOP)
153
+ if tasks:
154
+ await asyncio.wait(tasks)
155
+ await queue.put(STOP) # type: ignore[arg-type]
149
156
 
150
157
  tg.create_task(join())
151
158
 
@@ -78,4 +78,5 @@ class LimitedTaskGroup(TaskGroup):
78
78
  Returns:
79
79
  The created :class:`asyncio.Task`.
80
80
  """
81
+ # pyrefly: ignore [open-unpacking]
81
82
  return super().create_task(self._task_wrapper(coro), **kwargs)
@@ -141,10 +141,12 @@ async def test_fill_queue_full_queue_blocks_until_space_available() -> None:
141
141
 
142
142
 
143
143
  async def test_drain_sync_iterator() -> None:
144
- itr = iter([1, 2, 3])
144
+ given = [1, 2, 3]
145
+ itr = iter(given)
145
146
 
146
- await drain(itr)
147
+ count = await drain(itr)
147
148
 
149
+ assert count == len(given)
148
150
  with pytest.raises(StopIteration):
149
151
  next(itr)
150
152
 
@@ -156,12 +158,19 @@ async def test_drain_async_iterator() -> None:
156
158
 
157
159
  itr = aiter(gen())
158
160
 
159
- await drain(itr)
161
+ count = await drain(itr)
160
162
 
163
+ assert count == 3 # noqa: PLR2004
161
164
  with pytest.raises(StopAsyncIteration):
162
165
  await anext(itr)
163
166
 
164
167
 
168
+ async def test_drain_empty_iterator_returns_zero() -> None:
169
+ count = await drain(iter([]))
170
+
171
+ assert count == 0
172
+
173
+
165
174
  # merge_iterables
166
175
 
167
176
 
@@ -197,6 +206,13 @@ async def test_merge_iterables_empty_sources_yields_nothing() -> None:
197
206
  assert results == []
198
207
 
199
208
 
209
+ async def test_merge_iterables_no_sources_yields_nothing() -> None:
210
+ async with merge_iterables() as stream:
211
+ results = [item async for item in stream]
212
+
213
+ assert results == []
214
+
215
+
200
216
  async def test_merge_iterables_early_exit_cancels_background_tasks() -> None:
201
217
  initial_tasks = len(asyncio.all_tasks())
202
218
  itrs = [count(), count(1)]