aiotools
========
[![PyPI release version](https://badge.fury.io/py/aiotools.svg)](https://pypi.org/project/aiotools/)
![Supported Python versions](https://img.shields.io/pypi/pyversions/aiotools.svg)
![Test Status](https://github.com/achimnol/aiotools/workflows/Test%20with%20pytest/badge.svg)
[![Code Coverage](https://codecov.io/gh/achimnol/aiotools/branch/master/graph/badge.svg)](https://codecov.io/gh/achimnol/aiotools)
Idiomatic asyncio utilties
*NOTE:* This project is under early stage of development. The public APIs may break version by version.
Modules
-------
* [Async Context Manager](http://aiotools.readthedocs.io/en/latest/aiotools.context.html)
* [Async Fork](http://aiotools.readthedocs.io/en/latest/aiotools.fork.html)
* [Async Functools](http://aiotools.readthedocs.io/en/latest/aiotools.func.html)
* [Async Itertools](http://aiotools.readthedocs.io/en/latest/aiotools.iter.html)
* [Async Server](http://aiotools.readthedocs.io/en/latest/aiotools.server.html)
* [Async TaskGroup](http://aiotools.readthedocs.io/en/latest/aiotools.taskgroup.html)
* [Async Timer](http://aiotools.readthedocs.io/en/latest/aiotools.timer.html)
I also recommend to try the following asyncio libraries for your happier life.
* [async_timeout](https://github.com/aio-libs/async-timeout): Provides a light-weight timeout wrapper that does not spawn subtasks.
* [aiojobs](https://github.com/aio-libs/aiojobs): Provides a concurrency-limited scheduler for asyncio tasks with graceful shutdown.
* [trio](https://github.com/python-trio/trio): An alternative implementation of asynchronous IO stack for Python, with focus on cancellation scopes and task groups called "nursery".
Examples
--------
### Async Context Manager
This is an asynchronous version of `contextlib.contextmanager` to make it
easier to write asynchronous context managers without creating boilerplate
classes.
```python
import asyncio
import aiotools
@aiotools.actxmgr
async def mygen(a):
await asyncio.sleep(1)
yield a + 1
await asyncio.sleep(1)
async def somewhere():
async with mygen(1) as b:
assert b == 2
```
Note that you need to wrap `yield` with a try-finally block to
ensure resource releases (e.g., locks), even in the case when
an exception is ocurred inside the async-with block.
```python
import asyncio
import aiotools
lock = asyncio.Lock()
@aiotools.actxmgr
async def mygen(a):
await lock.acquire()
try:
yield a + 1
finally:
lock.release()
async def somewhere():
try:
async with mygen(1) as b:
raise RuntimeError('oops')
except RuntimeError:
print('caught!') # you can catch exceptions here.
```
You can also create a group of async context managers, which
are entered/exited all at once using `asyncio.gather()`.
```python
import asyncio
import aiotools
@aiotools.actxmgr
async def mygen(a):
yield a + 10
async def somewhere():
ctxgrp = aiotools.actxgroup(mygen(i) for i in range(10))
async with ctxgrp as values:
assert len(values) == 10
for i in range(10):
assert values[i] == i + 10
```
### Async Server
This implements a common pattern to launch asyncio-based server daemons.
```python
import asyncio
import aiotools
async def echo(reader, writer):
data = await reader.read(100)
writer.write(data)
await writer.drain()
writer.close()
@aiotools.server
async def myworker(loop, pidx, args):
server = await asyncio.start_server(echo, '0.0.0.0', 8888,
reuse_port=True, loop=loop)
print(f'[{pidx}] started')
yield # wait until terminated
server.close()
await server.wait_closed()
print(f'[{pidx}] terminated')
if __name__ == '__main__':
# Run the above server using 4 worker processes.
aiotools.start_server(myworker, num_workers=4)
```
It handles SIGINT/SIGTERM signals automatically to stop the server,
as well as lifecycle management of event loops running on multiple processes.
Internally it uses `aiotools.fork` module to get kernel support to resolve
potential signal/PID related races via PID file descriptors on supported versions
(Python 3.9+ and Linux kernel 5.4+).
### Async TaskGroup
A `TaskGroup` object manages the lifecycle of sub-tasks spawned via its `create_task()`
method by guarding them with an async context manager which exits only when all sub-tasks
are either completed or cancelled.
This is motivated from [trio's nursery API](https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning)
and a draft implementation is adopted from [EdgeDB's Python client library](https://github.com/edgedb/edgedb-python).
```python
import aiotools
async def do():
async with aiotools.TaskGroup() as tg:
tg.create_task(...)
tg.create_task(...)
...
# at this point, all subtasks are either cancelled or done.
```
### Async Timer
```python
import aiotools
i = 0
async def mytick(interval):
print(i)
i += 1
async def somewhere():
t = aiotools.create_timer(mytick, 1.0)
...
t.cancel()
await t
```
`t` is an `asyncio.Task` object.
To stop the timer, call `t.cancel(); await t`.
Please don't forget `await`-ing `t` because it requires extra steps to
cancel and await all pending tasks.
To make your timer function to be cancellable, add a try-except clause
catching `asyncio.CancelledError` since we use it as a termination
signal.
You may add `TimerDelayPolicy` argument to control the behavior when the
timer-fired task takes longer than the timer interval.
`DEFAULT` is to accumulate them and cancel all the remainings at once when
the timer is cancelled.
`CANCEL` is to cancel any pending previously fired tasks on every interval.
```python
import asyncio
import aiotools
async def mytick(interval):
await asyncio.sleep(100) # cancelled on every next interval.
async def somewhere():
t = aiotools.create_timer(mytick, 1.0, aiotools.TimerDelayPolicy.CANCEL)
...
t.cancel()
await t
```
#### Virtual Clock
It provides a virtual clock that advances the event loop time instantly upon
any combination of `asyncio.sleep()` calls in multiple coroutine tasks,
by temporarily patching the event loop selector.
This is also used in [our timer test suite](https://github.com/achimnol/aiotools/blob/master/tests/test_timer.py).
```python
import aiotools
import pytest
@pytest.mark.asyncio
async def test_sleeps():
loop = aiotools.compat.get_running_loop()
vclock = aiotools.VirtualClock()
with vclock.patch_loop():
print(loop.time()) # -> prints 0
await asyncio.sleep(3600)
print(loop.time()) # -> prints 3600
```
Changelog
=========
<!--
You should *NOT* be adding new change log entries to this file, this
file is managed by towncrier. You *may* edit previous change logs to
fix problems like typo corrections or such.
To add a new change log entry, please refer
https://pip.pypa.io/en/latest/development/contributing/#news-entries
We named the news folder "changes".
WARNING: Don't drop the last line!
-->
<!-- towncrier release notes start -->
1.7.0 (2023-08-25)
---------------------------------
### Breaking changes
* Dropped the support for Python 3.7 as it's end-of-life.
### Fixes
* Correct the type annotation of the callback argument in `create_timer()` ([#61](https://github.com/achimnol/aiotools/issues/61))
1.6.1 (2023-05-02)
------------------
### Fixes
* PersistentTaskGroup no longer stores the history of unhandled exceptions and raises them as an exception group to prevent memory leaks ([#54](https://github.com/achimnol/aiotools/issues/54))
1.6.0 (2023-03-14)
------------------
### Features
* Add `as_completed_safe()` which enhances `asyncio.as_completed()` using `PersistentTaskGroup` ([#52](https://github.com/achimnol/aiotools/issues/52))
1.5.9 (2022-04-26)
------------------
### Fixes
* Improve checks for pidfd availability to avoid corner cases that may fail on Linux kernel 5.1 and 5.2 where `signal.pidfd_send_signal()` is available but `os.pidfd_open()` is not ([#51](https://github.com/achimnol/aiotools/issues/51))
1.5.8 (2022-04-25)
------------------
### Fixes
* Explicitly attach the event loop to the `PidfdChildWatcher` when first initialized ([#50](https://github.com/achimnol/aiotools/issues/50))
1.5.7 (2022-04-12)
------------------
### Fixes
* Fix regression of the default imports in macOS by removing the unused code that caused the misleading fix in #47 ([#49](https://github.com/achimnol/aiotools/issues/49))
1.5.6 (2022-04-11)
------------------
### Features
* Add the `closing_async()` async context manager, in addition to `aclosing()` ([#48](https://github.com/achimnol/aiotools/issues/48))
### Fixes
* Allow importing aiotools on Windows platforms, removing incompatible modules from the default `__all__` import list ([#47](https://github.com/achimnol/aiotools/issues/47))
1.5.5 (2022-03-22)
------------------
### Features
* Add `wait_timeout` option to `start_server()` ([#46](https://github.com/achimnol/aiotools/issues/46))
### Fixes
* Resolve singal races by minimizing expose of event loop in `afork()`-ed child processes ([#46](https://github.com/achimnol/aiotools/issues/46))
### Miscellaneous
* Now the CI runs with Python 3.11a6 or later, with stdlib support of `asyncio.TaskGroup` ([#45](https://github.com/achimnol/aiotools/issues/45))
1.5.4 (2022-03-10)
------------------
### Features
* Propagate task results and exceptions via separate future instances if they are `await`-ed by the caller of `create_task()` in `PersistentTaskGroup`, in addition to invocation of task group exception handler. Note that `await`-ing those futures hangs indefinitely in Python 3.6 but we don't fix it since Python 3.6 is EoL as of December 2021. ([#44](https://github.com/achimnol/aiotools/issues/44))
1.5.3 (2022-03-07)
------------------
### Fixes
* Fix feature detection for `ExceptionGroup` and let `MultiError` inherit `ExceptionGroup` instead of `BaseExceptionGroup` ([#42](https://github.com/achimnol/aiotools/issues/42))
1.5.2 (2022-03-06)
------------------
### Fixes
* Restore the default export of `MultiError` for backward compatibility ([#40](https://github.com/achimnol/aiotools/issues/40))
* Set `current_ptaskgroup` only when `PersistentTaskGroup` is used via the `async with` statement. ([#41](https://github.com/achimnol/aiotools/issues/41))
1.5.1 (2022-03-06)
------------------
### Fixes
* Fix missing naming support of `TaskGroup` in Python 3.11 ([#39](https://github.com/achimnol/aiotools/issues/39))
1.5.0 (2022-03-06)
------------------
### Features
* Add support for Python 3.9's `msg` argument to `Task.cancel()`. ([#32](https://github.com/achimnol/aiotools/issues/32))
* Fix "unexpected cancel" bug in `TaskGroup`. ([#35](https://github.com/achimnol/aiotools/issues/35))
* Rewrite PersistentTaskGroup to use Python 3.11's latest additions such as `Task.uncancel()` and `Task.cancelling()` while still supporting older Python versions ([#36](https://github.com/achimnol/aiotools/issues/36))
* Add `PersistentTaskGroup.all()` to enumerate all non-terminated persistent task groups ([#38](https://github.com/achimnol/aiotools/issues/38))
1.4.0 (2022-01-10)
------------------
### Features
* **ptaskgroup**: Implement `PersistentTaskGroup` ([#30](https://github.com/achimnol/aiotools/issues/30))
* **server**: Expose `process_index` context variable for worker processes ([#31](https://github.com/achimnol/aiotools/issues/31))
1.3.0 (2021-12-19)
------------------
### Fixes
* Add support for Python 3.10. ([#28](https://github.com/achimnol/aiotools/issues/28))
### Documentation Changes
* Fix documentation builds on Python 3.10 and Sphinx 4.x, by removing the 3rd-party autodoc-typehints extension and custom stylesheet overrides. ([#28](https://github.com/achimnol/aiotools/issues/28))
1.2.2 (2021-06-07)
------------------
### Fixes
* **fork:** Handle children's segfault (core-dump with signals) explicitly in `PidfdChildProcess` ([#27](https://github.com/achimnol/aiotools/issues/27))
1.2.1 (2021-01-12)
------------------
### Fixes
* Avoid side effects of custom `clone()` function and resorts back to the combinatino of `os.fork()` and `os.pidfd_open()` for now ([#25](https://github.com/achimnol/aiotools/issues/25))
1.2.0 (2021-01-12)
------------------
### Breaking Changes
* **server:** The `use_threading` argument for `start_server()` is completely deprecated. ([#23](https://github.com/achimnol/aiotools/issues/23))
### Features
* Now the primary target is Python 3.9, though we still support from Python 3.6 ([#22](https://github.com/achimnol/aiotools/issues/22))
* **fork:** Add a new module `fork` to support PID file descriptors in Linux 5.4+ and a POSIX-compatible fallback to asynchornously fork the Python process without signal/PID races. ([#22](https://github.com/achimnol/aiotools/issues/22))
* **server:** Completely rewrote the module using the new `fork` module with handling of various edge cases such as async failures of sibiling child processes ([#23](https://github.com/achimnol/aiotools/issues/23))
1.1.1 (2020-12-16)
------------------
### Fixes
* Fix a potential memory leak with `TaskGroup` when it's used for long-lived asyncio tasks. ([#21](https://github.com/achimnol/aiotools/issues/21))
1.1.0 (2020-10-18)
------------------
### Features
* Add a `current_taskgroup` context-variable to the taskgroup module (only available for Python 3.7 or later)
### Fixes
* Fix missing auto-import of `taskgroup` module exports in the `aiotools` root package.
1.0.0 (2020-10-18)
------------------
### Features
* Adopt an implementation of the taskgroup API as `aiotools.taskgroup` from [EdgeDB](https://github.com/edgedb/edgedb-python/) ([#18](https://github.com/achimnol/aiotools/issues/18))
* Add `timer.VirtualClock` which provides a virtual clock that makes a block of asyncio codes using `asyncio.sleep()` to complete instantly and deterministically ([#19](https://github.com/achimnol/aiotools/issues/19))
### Miscellaneous
* Adopt towncrier for changelog management ([#15](https://github.com/achimnol/aiotools/issues/15))
* Migrate to GitHub Actions for CI ([#19](https://github.com/achimnol/aiotools/issues/19))
0.9.1 (2020-02-25)
------------------
* A maintenance release to fix up the ``defer`` module exports in the ``aiotools`` namespace.
0.9.0 (2020-02-25)
------------------
* **defer:** A new module that emulates Golang's ``defer()`` API with asyncio awareness.
0.8.5 (2019-11-19)
------------------
* **server:** Rewrite internals of the worker main functions to use native `async with`
instead of manually unrolling `__aenter__()` and `__aexit__()` dunder methods, to keep
the code simple and avoid potential bugs.
0.8.4 (2019-11-18)
------------------
* Python 3.8 is now officially supported.
* **server:** Fix errors when `multiprocessing.set_start_method("spawn")` is used.
- NOTE: This is now the default for macOS since Python 3.8.
- KNOWN ISSUE: [#12](https://github.com/achimnol/aiotools/issues/12)
* Remove some packaging hacks in `__init__.py` and let setuptools read the version
from a separate `aiotools/VERSION` text file.
0.8.3 (2019-10-07)
------------------
* **context:** Fix `aclosing()`'s `__aexit__()` exception arguments.
0.8.2 (2019-08-28)
------------------
* **context**, **server:** Catch asyncio.CancelledError along with BaseException to
make the cancellation behavior consistent in Python 3.6, 3.7, and 3.8.
0.8.1 (2019-02-24)
------------------
* **server:** Fix yields of the received stop signal in main/worker context managers
when using threaded workers.
0.8.0 (2018-11-18)
------------------
* **server:** Updated stop signal handling and now user-defined worker/main context
managers have a way to distinguish the stop signal received. See the updated
docs for more details.
0.7.3 (2018-10-16)
------------------
* This ia a technical release to fix a test case preventing the automated CI
release procedure.
0.7.2 (2018-10-16)
------------------
* Improve support for Python 3.6/3.7 using a small compatibility module against asyncio.
* func: Add `expire_after` option to `lru_cache()` function.
0.7.1 (2018-08-24)
------------------
* Minor updates to the documentation
0.7.0 (2018-08-24)
------------------
* Add support for Python 3.7
* **context:** Updated to work like Python 3.7
* **context:** Deprecated `AsyncContextDecorator` stuffs in Python 3.7+
* **context:** Added an alias to `contextlib.AsyncExitStack` in the standard library.
0.6.0 (2018-04-10)
------------------
* Introduce a new module `aiotools.iter` with `aiter()` function which
corresponds to an async version of the builtin `iter()`.
0.5.4 (2018-02-01)
------------------
* **server:** Remove use of unncessary setpgrp syscall, which is also blocked by
Docker's default seccomp profile!
0.5.3 (2018-01-12)
------------------
* **server:** Ooops! (a finally block should have been an else block)
0.5.2 (2018-01-12)
------------------
* **server:** Improve inner beauty (code readability)
* **server:** Improve reliability and portability of worker-to-main interrupts
0.5.1 (2018-01-11)
------------------
* **server:** Fix a race condition related to handling of worker
initialization errors with multiple workers
0.5.0 (2017-11-08)
------------------
* **func:** Add `lru_cache()` which is a coroutine version of
`functools.lru_cache()`
0.4.5 (2017-10-14)
------------------
* **server:** Fix a race condition related to signal handling in the
multiprocessing module during termination
* **server:** Improve error handling during initialization of workers
(automatic shutdown of other workers and the main loop after
logging the exception)
0.4.4 (2017-09-12)
------------------
* Add a new module `aiotools.func` with `apartial()` function which is an
async version of `functools.partial()` in the standard library
0.4.3 (2017-08-06)
------------------
* Add `aclosing()` context manager like `closing()` in the standard library
* Speed up Travis CI builds for packaging
* Now provide README in rst as well as CHANGES (this file)
0.4.2 (2017-08-01)
------------------
* `server`: Fix spawning subprocesses in child workers
* Add support for `uvloop`
0.4.0 (2017-08-01)
------------------
* Add `use_threading` argument to
* Add initial documentation (which currently not served
on readthedocs.io due to Python version problem)
0.3.2 (2017-07-31)
------------------
* Add `extra_procs` argument to `start_server()` function
* Add socket and ZeroMQ server examples
* Improve CI configs
0.3.1 (2017-07-26)
------------------
* Improve CI scripts
* Adopt editorconfig
0.3.0 (2017-04-26)
------------------
* Add `start_server()` function using multiprocessing
with automatic children lifecycle management
* Clarify the semantics of `AsyncContextGroup` using
`asyncio.gather()` with `return_exceptions=True`
0.2.0 (2017-04-20)
------------------
* Add abstract types for `AsyncContextManager`
* Rename `AsyncGenContextManager` to `AsyncContextManager`
* Add `AsyncContextGroup`
0.1.1 (2017-04-14)
------------------
* Initial release
Raw data
{
"_id": null,
"home_page": "https://github.com/achimnol/aiotools",
"name": "aiotools",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "",
"author": "Joongi Kim",
"author_email": "me@daybreaker.info",
"download_url": "https://files.pythonhosted.org/packages/57/fb/db6aacb1683517d6178eb7ae8ac09e125f78a4fd4d97b2dc7ea55c108b6d/aiotools-1.7.0.tar.gz",
"platform": "any",
"description": "aiotools\n========\n\n[![PyPI release version](https://badge.fury.io/py/aiotools.svg)](https://pypi.org/project/aiotools/)\n![Supported Python versions](https://img.shields.io/pypi/pyversions/aiotools.svg)\n![Test Status](https://github.com/achimnol/aiotools/workflows/Test%20with%20pytest/badge.svg)\n[![Code Coverage](https://codecov.io/gh/achimnol/aiotools/branch/master/graph/badge.svg)](https://codecov.io/gh/achimnol/aiotools)\n\nIdiomatic asyncio utilties\n\n*NOTE:* This project is under early stage of development. The public APIs may break version by version.\n\n\nModules\n-------\n\n* [Async Context Manager](http://aiotools.readthedocs.io/en/latest/aiotools.context.html)\n* [Async Fork](http://aiotools.readthedocs.io/en/latest/aiotools.fork.html)\n* [Async Functools](http://aiotools.readthedocs.io/en/latest/aiotools.func.html)\n* [Async Itertools](http://aiotools.readthedocs.io/en/latest/aiotools.iter.html)\n* [Async Server](http://aiotools.readthedocs.io/en/latest/aiotools.server.html)\n* [Async TaskGroup](http://aiotools.readthedocs.io/en/latest/aiotools.taskgroup.html)\n* [Async Timer](http://aiotools.readthedocs.io/en/latest/aiotools.timer.html)\n\nI also recommend to try the following asyncio libraries for your happier life.\n\n* [async_timeout](https://github.com/aio-libs/async-timeout): Provides a light-weight timeout wrapper that does not spawn subtasks.\n* [aiojobs](https://github.com/aio-libs/aiojobs): Provides a concurrency-limited scheduler for asyncio tasks with graceful shutdown.\n* [trio](https://github.com/python-trio/trio): An alternative implementation of asynchronous IO stack for Python, with focus on cancellation scopes and task groups called \"nursery\".\n\n\nExamples\n--------\n\n### Async Context Manager\n\nThis is an asynchronous version of `contextlib.contextmanager` to make it\neasier to write asynchronous context managers without creating boilerplate\nclasses.\n\n```python\nimport asyncio\nimport aiotools\n\n@aiotools.actxmgr\nasync def mygen(a):\n await asyncio.sleep(1)\n yield a + 1\n await asyncio.sleep(1)\n\nasync def somewhere():\n async with mygen(1) as b:\n assert b == 2\n```\n\nNote that you need to wrap `yield` with a try-finally block to\nensure resource releases (e.g., locks), even in the case when\nan exception is ocurred inside the async-with block.\n\n```python\nimport asyncio\nimport aiotools\n\nlock = asyncio.Lock()\n\n@aiotools.actxmgr\nasync def mygen(a):\n await lock.acquire()\n try:\n yield a + 1\n finally:\n lock.release()\n\nasync def somewhere():\n try:\n async with mygen(1) as b:\n raise RuntimeError('oops')\n except RuntimeError:\n print('caught!') # you can catch exceptions here.\n```\n\nYou can also create a group of async context managers, which\nare entered/exited all at once using `asyncio.gather()`.\n\n```python\nimport asyncio\nimport aiotools\n\n@aiotools.actxmgr\nasync def mygen(a):\n yield a + 10\n\nasync def somewhere():\n ctxgrp = aiotools.actxgroup(mygen(i) for i in range(10))\n async with ctxgrp as values:\n assert len(values) == 10\n for i in range(10):\n assert values[i] == i + 10\n```\n\n### Async Server\n\nThis implements a common pattern to launch asyncio-based server daemons.\n\n```python\nimport asyncio\nimport aiotools\n\nasync def echo(reader, writer):\n data = await reader.read(100)\n writer.write(data)\n await writer.drain()\n writer.close()\n\n@aiotools.server\nasync def myworker(loop, pidx, args):\n server = await asyncio.start_server(echo, '0.0.0.0', 8888,\n reuse_port=True, loop=loop)\n print(f'[{pidx}] started')\n yield # wait until terminated\n server.close()\n await server.wait_closed()\n print(f'[{pidx}] terminated')\n\nif __name__ == '__main__':\n # Run the above server using 4 worker processes.\n aiotools.start_server(myworker, num_workers=4)\n```\n\nIt handles SIGINT/SIGTERM signals automatically to stop the server,\nas well as lifecycle management of event loops running on multiple processes.\nInternally it uses `aiotools.fork` module to get kernel support to resolve\npotential signal/PID related races via PID file descriptors on supported versions\n(Python 3.9+ and Linux kernel 5.4+).\n\n\n### Async TaskGroup\n\nA `TaskGroup` object manages the lifecycle of sub-tasks spawned via its `create_task()`\nmethod by guarding them with an async context manager which exits only when all sub-tasks\nare either completed or cancelled.\n\nThis is motivated from [trio's nursery API](https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning)\nand a draft implementation is adopted from [EdgeDB's Python client library](https://github.com/edgedb/edgedb-python).\n\n```python\nimport aiotools\n\nasync def do():\n async with aiotools.TaskGroup() as tg:\n tg.create_task(...)\n tg.create_task(...)\n ...\n # at this point, all subtasks are either cancelled or done.\n```\n\n\n### Async Timer\n\n```python\nimport aiotools\n\ni = 0\n\nasync def mytick(interval):\n print(i)\n i += 1\n\nasync def somewhere():\n t = aiotools.create_timer(mytick, 1.0)\n ...\n t.cancel()\n await t\n```\n\n`t` is an `asyncio.Task` object.\nTo stop the timer, call `t.cancel(); await t`.\nPlease don't forget `await`-ing `t` because it requires extra steps to\ncancel and await all pending tasks.\nTo make your timer function to be cancellable, add a try-except clause\ncatching `asyncio.CancelledError` since we use it as a termination\nsignal.\n\nYou may add `TimerDelayPolicy` argument to control the behavior when the\ntimer-fired task takes longer than the timer interval.\n`DEFAULT` is to accumulate them and cancel all the remainings at once when\nthe timer is cancelled.\n`CANCEL` is to cancel any pending previously fired tasks on every interval.\n\n```python\nimport asyncio\nimport aiotools\n\nasync def mytick(interval):\n await asyncio.sleep(100) # cancelled on every next interval.\n\nasync def somewhere():\n t = aiotools.create_timer(mytick, 1.0, aiotools.TimerDelayPolicy.CANCEL)\n ...\n t.cancel()\n await t\n```\n\n#### Virtual Clock\n\nIt provides a virtual clock that advances the event loop time instantly upon\nany combination of `asyncio.sleep()` calls in multiple coroutine tasks,\nby temporarily patching the event loop selector.\n\nThis is also used in [our timer test suite](https://github.com/achimnol/aiotools/blob/master/tests/test_timer.py).\n\n```python\nimport aiotools\nimport pytest\n\n@pytest.mark.asyncio\nasync def test_sleeps():\n loop = aiotools.compat.get_running_loop()\n vclock = aiotools.VirtualClock()\n with vclock.patch_loop():\n print(loop.time()) # -> prints 0\n await asyncio.sleep(3600)\n print(loop.time()) # -> prints 3600\n```\n\nChangelog\n=========\n\n<!--\n You should *NOT* be adding new change log entries to this file, this\n file is managed by towncrier. You *may* edit previous change logs to\n fix problems like typo corrections or such.\n\n To add a new change log entry, please refer\n https://pip.pypa.io/en/latest/development/contributing/#news-entries\n\n We named the news folder \"changes\".\n\n WARNING: Don't drop the last line!\n-->\n\n<!-- towncrier release notes start -->\n\n1.7.0 (2023-08-25)\n---------------------------------\n\n### Breaking changes\n* Dropped the support for Python 3.7 as it's end-of-life.\n\n### Fixes\n* Correct the type annotation of the callback argument in `create_timer()` ([#61](https://github.com/achimnol/aiotools/issues/61))\n\n\n1.6.1 (2023-05-02)\n------------------\n\n### Fixes\n* PersistentTaskGroup no longer stores the history of unhandled exceptions and raises them as an exception group to prevent memory leaks ([#54](https://github.com/achimnol/aiotools/issues/54))\n\n\n1.6.0 (2023-03-14)\n------------------\n\n### Features\n* Add `as_completed_safe()` which enhances `asyncio.as_completed()` using `PersistentTaskGroup` ([#52](https://github.com/achimnol/aiotools/issues/52))\n\n\n1.5.9 (2022-04-26)\n------------------\n\n### Fixes\n* Improve checks for pidfd availability to avoid corner cases that may fail on Linux kernel 5.1 and 5.2 where `signal.pidfd_send_signal()` is available but `os.pidfd_open()` is not ([#51](https://github.com/achimnol/aiotools/issues/51))\n\n\n1.5.8 (2022-04-25)\n------------------\n\n### Fixes\n* Explicitly attach the event loop to the `PidfdChildWatcher` when first initialized ([#50](https://github.com/achimnol/aiotools/issues/50))\n\n\n1.5.7 (2022-04-12)\n------------------\n\n### Fixes\n* Fix regression of the default imports in macOS by removing the unused code that caused the misleading fix in #47 ([#49](https://github.com/achimnol/aiotools/issues/49))\n\n\n1.5.6 (2022-04-11)\n------------------\n\n### Features\n* Add the `closing_async()` async context manager, in addition to `aclosing()` ([#48](https://github.com/achimnol/aiotools/issues/48))\n### Fixes\n* Allow importing aiotools on Windows platforms, removing incompatible modules from the default `__all__` import list ([#47](https://github.com/achimnol/aiotools/issues/47))\n\n\n1.5.5 (2022-03-22)\n------------------\n\n### Features\n* Add `wait_timeout` option to `start_server()` ([#46](https://github.com/achimnol/aiotools/issues/46))\n\n### Fixes\n* Resolve singal races by minimizing expose of event loop in `afork()`-ed child processes ([#46](https://github.com/achimnol/aiotools/issues/46))\n\n### Miscellaneous\n* Now the CI runs with Python 3.11a6 or later, with stdlib support of `asyncio.TaskGroup` ([#45](https://github.com/achimnol/aiotools/issues/45))\n\n\n1.5.4 (2022-03-10)\n------------------\n\n### Features\n* Propagate task results and exceptions via separate future instances if they are `await`-ed by the caller of `create_task()` in `PersistentTaskGroup`, in addition to invocation of task group exception handler. Note that `await`-ing those futures hangs indefinitely in Python 3.6 but we don't fix it since Python 3.6 is EoL as of December 2021. ([#44](https://github.com/achimnol/aiotools/issues/44))\n\n\n1.5.3 (2022-03-07)\n------------------\n\n### Fixes\n* Fix feature detection for `ExceptionGroup` and let `MultiError` inherit `ExceptionGroup` instead of `BaseExceptionGroup` ([#42](https://github.com/achimnol/aiotools/issues/42))\n\n\n1.5.2 (2022-03-06)\n------------------\n\n### Fixes\n* Restore the default export of `MultiError` for backward compatibility ([#40](https://github.com/achimnol/aiotools/issues/40))\n* Set `current_ptaskgroup` only when `PersistentTaskGroup` is used via the `async with` statement. ([#41](https://github.com/achimnol/aiotools/issues/41))\n\n\n1.5.1 (2022-03-06)\n------------------\n\n### Fixes\n* Fix missing naming support of `TaskGroup` in Python 3.11 ([#39](https://github.com/achimnol/aiotools/issues/39))\n\n\n1.5.0 (2022-03-06)\n------------------\n\n### Features\n* Add support for Python 3.9's `msg` argument to `Task.cancel()`. ([#32](https://github.com/achimnol/aiotools/issues/32))\n* Fix \"unexpected cancel\" bug in `TaskGroup`. ([#35](https://github.com/achimnol/aiotools/issues/35))\n* Rewrite PersistentTaskGroup to use Python 3.11's latest additions such as `Task.uncancel()` and `Task.cancelling()` while still supporting older Python versions ([#36](https://github.com/achimnol/aiotools/issues/36))\n* Add `PersistentTaskGroup.all()` to enumerate all non-terminated persistent task groups ([#38](https://github.com/achimnol/aiotools/issues/38))\n\n\n1.4.0 (2022-01-10)\n------------------\n\n### Features\n* **ptaskgroup**: Implement `PersistentTaskGroup` ([#30](https://github.com/achimnol/aiotools/issues/30))\n* **server**: Expose `process_index` context variable for worker processes ([#31](https://github.com/achimnol/aiotools/issues/31))\n\n\n1.3.0 (2021-12-19)\n------------------\n\n### Fixes\n* Add support for Python 3.10. ([#28](https://github.com/achimnol/aiotools/issues/28))\n\n### Documentation Changes\n* Fix documentation builds on Python 3.10 and Sphinx 4.x, by removing the 3rd-party autodoc-typehints extension and custom stylesheet overrides. ([#28](https://github.com/achimnol/aiotools/issues/28))\n\n\n1.2.2 (2021-06-07)\n------------------\n\n### Fixes\n* **fork:** Handle children's segfault (core-dump with signals) explicitly in `PidfdChildProcess` ([#27](https://github.com/achimnol/aiotools/issues/27))\n\n\n1.2.1 (2021-01-12)\n------------------\n\n### Fixes\n* Avoid side effects of custom `clone()` function and resorts back to the combinatino of `os.fork()` and `os.pidfd_open()` for now ([#25](https://github.com/achimnol/aiotools/issues/25))\n\n\n1.2.0 (2021-01-12)\n------------------\n\n### Breaking Changes\n* **server:** The `use_threading` argument for `start_server()` is completely deprecated. ([#23](https://github.com/achimnol/aiotools/issues/23))\n\n### Features\n* Now the primary target is Python 3.9, though we still support from Python 3.6 ([#22](https://github.com/achimnol/aiotools/issues/22))\n* **fork:** Add a new module `fork` to support PID file descriptors in Linux 5.4+ and a POSIX-compatible fallback to asynchornously fork the Python process without signal/PID races. ([#22](https://github.com/achimnol/aiotools/issues/22))\n* **server:** Completely rewrote the module using the new `fork` module with handling of various edge cases such as async failures of sibiling child processes ([#23](https://github.com/achimnol/aiotools/issues/23))\n\n\n1.1.1 (2020-12-16)\n------------------\n\n### Fixes\n* Fix a potential memory leak with `TaskGroup` when it's used for long-lived asyncio tasks. ([#21](https://github.com/achimnol/aiotools/issues/21))\n\n\n1.1.0 (2020-10-18)\n------------------\n\n### Features\n* Add a `current_taskgroup` context-variable to the taskgroup module (only available for Python 3.7 or later)\n\n### Fixes\n* Fix missing auto-import of `taskgroup` module exports in the `aiotools` root package.\n\n1.0.0 (2020-10-18)\n------------------\n\n### Features\n* Adopt an implementation of the taskgroup API as `aiotools.taskgroup` from [EdgeDB](https://github.com/edgedb/edgedb-python/) ([#18](https://github.com/achimnol/aiotools/issues/18))\n* Add `timer.VirtualClock` which provides a virtual clock that makes a block of asyncio codes using `asyncio.sleep()` to complete instantly and deterministically ([#19](https://github.com/achimnol/aiotools/issues/19))\n\n### Miscellaneous\n* Adopt towncrier for changelog management ([#15](https://github.com/achimnol/aiotools/issues/15))\n* Migrate to GitHub Actions for CI ([#19](https://github.com/achimnol/aiotools/issues/19))\n\n0.9.1 (2020-02-25)\n------------------\n\n* A maintenance release to fix up the ``defer`` module exports in the ``aiotools`` namespace.\n\n0.9.0 (2020-02-25)\n------------------\n\n* **defer:** A new module that emulates Golang's ``defer()`` API with asyncio awareness.\n\n0.8.5 (2019-11-19)\n------------------\n\n* **server:** Rewrite internals of the worker main functions to use native `async with`\n instead of manually unrolling `__aenter__()` and `__aexit__()` dunder methods, to keep\n the code simple and avoid potential bugs.\n\n0.8.4 (2019-11-18)\n------------------\n\n* Python 3.8 is now officially supported.\n* **server:** Fix errors when `multiprocessing.set_start_method(\"spawn\")` is used.\n - NOTE: This is now the default for macOS since Python 3.8.\n - KNOWN ISSUE: [#12](https://github.com/achimnol/aiotools/issues/12)\n* Remove some packaging hacks in `__init__.py` and let setuptools read the version\n from a separate `aiotools/VERSION` text file.\n\n0.8.3 (2019-10-07)\n------------------\n\n* **context:** Fix `aclosing()`'s `__aexit__()` exception arguments.\n\n0.8.2 (2019-08-28)\n------------------\n\n* **context**, **server:** Catch asyncio.CancelledError along with BaseException to\n make the cancellation behavior consistent in Python 3.6, 3.7, and 3.8.\n\n0.8.1 (2019-02-24)\n------------------\n\n* **server:** Fix yields of the received stop signal in main/worker context managers\n when using threaded workers.\n\n0.8.0 (2018-11-18)\n------------------\n\n* **server:** Updated stop signal handling and now user-defined worker/main context\n managers have a way to distinguish the stop signal received. See the updated\n docs for more details.\n\n0.7.3 (2018-10-16)\n------------------\n\n* This ia a technical release to fix a test case preventing the automated CI\n release procedure.\n\n0.7.2 (2018-10-16)\n------------------\n\n* Improve support for Python 3.6/3.7 using a small compatibility module against asyncio.\n* func: Add `expire_after` option to `lru_cache()` function.\n\n0.7.1 (2018-08-24)\n------------------\n\n* Minor updates to the documentation\n\n0.7.0 (2018-08-24)\n------------------\n\n* Add support for Python 3.7\n* **context:** Updated to work like Python 3.7\n* **context:** Deprecated `AsyncContextDecorator` stuffs in Python 3.7+\n* **context:** Added an alias to `contextlib.AsyncExitStack` in the standard library.\n\n0.6.0 (2018-04-10)\n------------------\n\n* Introduce a new module `aiotools.iter` with `aiter()` function which\n corresponds to an async version of the builtin `iter()`.\n\n0.5.4 (2018-02-01)\n------------------\n\n* **server:** Remove use of unncessary setpgrp syscall, which is also blocked by\n Docker's default seccomp profile!\n\n0.5.3 (2018-01-12)\n------------------\n\n* **server:** Ooops! (a finally block should have been an else block)\n\n0.5.2 (2018-01-12)\n------------------\n\n* **server:** Improve inner beauty (code readability)\n* **server:** Improve reliability and portability of worker-to-main interrupts\n\n0.5.1 (2018-01-11)\n------------------\n\n* **server:** Fix a race condition related to handling of worker\n initialization errors with multiple workers\n\n0.5.0 (2017-11-08)\n------------------\n\n* **func:** Add `lru_cache()` which is a coroutine version of\n `functools.lru_cache()`\n\n0.4.5 (2017-10-14)\n------------------\n\n* **server:** Fix a race condition related to signal handling in the\n multiprocessing module during termination\n* **server:** Improve error handling during initialization of workers\n (automatic shutdown of other workers and the main loop after\n logging the exception)\n\n0.4.4 (2017-09-12)\n------------------\n\n* Add a new module `aiotools.func` with `apartial()` function which is an\n async version of `functools.partial()` in the standard library\n\n0.4.3 (2017-08-06)\n------------------\n\n* Add `aclosing()` context manager like `closing()` in the standard library\n* Speed up Travis CI builds for packaging\n* Now provide README in rst as well as CHANGES (this file)\n\n0.4.2 (2017-08-01)\n------------------\n\n* `server`: Fix spawning subprocesses in child workers\n* Add support for `uvloop`\n\n0.4.0 (2017-08-01)\n------------------\n\n* Add `use_threading` argument to\n* Add initial documentation (which currently not served\n on readthedocs.io due to Python version problem)\n\n0.3.2 (2017-07-31)\n------------------\n\n* Add `extra_procs` argument to `start_server()` function\n* Add socket and ZeroMQ server examples\n* Improve CI configs\n\n0.3.1 (2017-07-26)\n------------------\n\n* Improve CI scripts\n* Adopt editorconfig\n\n0.3.0 (2017-04-26)\n------------------\n\n* Add `start_server()` function using multiprocessing\n with automatic children lifecycle management\n* Clarify the semantics of `AsyncContextGroup` using\n `asyncio.gather()` with `return_exceptions=True`\n\n0.2.0 (2017-04-20)\n------------------\n\n* Add abstract types for `AsyncContextManager`\n* Rename `AsyncGenContextManager` to `AsyncContextManager`\n* Add `AsyncContextGroup`\n\n0.1.1 (2017-04-14)\n------------------\n\n* Initial release\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Idiomatic asyncio utilities",
"version": "1.7.0",
"project_urls": {
"Code Coverage": "https://codecov.io/github/achimnol/aiotools",
"Documentation": "https://aiotools.readthedocs.io",
"Homepage": "https://github.com/achimnol/aiotools",
"Source": "https://github.com/achimnol/aiotools",
"Tracker": "https://github.com/achimnol/aiotools/issues"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "7f099c8a0985b01b94974f1cca1ea35f5937ed77a62c13e01491efc15719b70f",
"md5": "1fc3696ba24c9b5e5d7f671f6e4f05c9",
"sha256": "008e4b448869e00352034106fcab4ed849e62d0ec7c89661d5650006f1a7b3a9"
},
"downloads": -1,
"filename": "aiotools-1.7.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "1fc3696ba24c9b5e5d7f671f6e4f05c9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 37801,
"upload_time": "2023-08-25T12:17:20",
"upload_time_iso_8601": "2023-08-25T12:17:20.262636Z",
"url": "https://files.pythonhosted.org/packages/7f/09/9c8a0985b01b94974f1cca1ea35f5937ed77a62c13e01491efc15719b70f/aiotools-1.7.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "57fbdb6aacb1683517d6178eb7ae8ac09e125f78a4fd4d97b2dc7ea55c108b6d",
"md5": "70a54cd128c1a1b09d88da14bcbc0231",
"sha256": "2f348af526e6e02485341924af88daaf79a1f7b2e9284544ffbf759080d38581"
},
"downloads": -1,
"filename": "aiotools-1.7.0.tar.gz",
"has_sig": false,
"md5_digest": "70a54cd128c1a1b09d88da14bcbc0231",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 60878,
"upload_time": "2023-08-25T12:17:23",
"upload_time_iso_8601": "2023-08-25T12:17:23.240709Z",
"url": "https://files.pythonhosted.org/packages/57/fb/db6aacb1683517d6178eb7ae8ac09e125f78a4fd4d97b2dc7ea55c108b6d/aiotools-1.7.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-08-25 12:17:23",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "achimnol",
"github_project": "aiotools",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "aiotools"
}