Package: aiotools
aiotoolsIdiomatic asyncio utiltiesNOTE:This project is under early stage of development. The public APIs may break version by version.ModulesAsync Context ManagerAsync ForkAsync FunctoolsAsync ItertoolsAsync ServerAsync TaskGroupAsync TimerI also recommend to try the following asyncio libraries for your happier life.async_timeout: Provides a light-weight timeout wrapper that does not spawn subtasks.aiojobs: Provides a concurrency-limited scheduler for asyncio tasks with graceful shutdown.trio: An alternative implementation of asynchronous IO stack for Python, with focus on cancellation scopes and task groups called "nursery".ExamplesAsync Context ManagerThis is an asynchronous version ofcontextlib.contextmanagerto make it
easier to write asynchronous context managers without creating boilerplate
classes.importasyncioimportaiotools@aiotools.actxmgrasyncdefmygen(a):awaitasyncio.sleep(1)yielda+1awaitasyncio.sleep(1)asyncdefsomewhere():asyncwithmygen(1)asb:assertb==2Note that you need to wrapyieldwith 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.importasyncioimportaiotoolslock=asyncio.Lock()@aiotools.actxmgrasyncdefmygen(a):awaitlock.acquire()try:yielda+1finally:lock.release()asyncdefsomewhere():try:asyncwithmygen(1)asb:raiseRuntimeError('oops')exceptRuntimeError:print('caught!')# you can catch exceptions here.You can also create a group of async context managers, which
are entered/exited all at once usingasyncio.gather().importasyncioimportaiotools@aiotools.actxmgrasyncdefmygen(a):yielda+10asyncdefsomewhere():ctxgrp=aiotools.actxgroup(mygen(i)foriinrange(10))asyncwithctxgrpasvalues:assertlen(values)==10foriinrange(10):assertvalues[i]==i+10Async ServerThis implements a common pattern to launch asyncio-based server daemons.importasyncioimportaiotoolsasyncdefecho(reader,writer):data=awaitreader.read(100)writer.write(data)awaitwriter.drain()writer.close()@aiotools.serverasyncdefmyworker(loop,pidx,args):server=awaitasyncio.start_server(echo,'0.0.0.0',8888,reuse_port=True,loop=loop)print(f'[{pidx}] started')yield# wait until terminatedserver.close()awaitserver.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 usesaiotools.forkmodule 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 TaskGroupATaskGroupobject manages the lifecycle of sub-tasks spawned via itscreate_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 fromtrio's nursery APIand a draft implementation is adopted fromEdgeDB's Python client library.importaiotoolsasyncdefdo():asyncwithaiotools.TaskGroup()astg:tg.create_task(...)tg.create_task(...)...# at this point, all subtasks are either cancelled or done.Async Timerimportaiotoolsi=0asyncdefmytick(interval):print(i)i+=1asyncdefsomewhere():t=aiotools.create_timer(mytick,1.0)...t.cancel()awaitttis anasyncio.Taskobject.
To stop the timer, callt.cancel(); await t.
Please don't forgetawait-ingtbecause it requires extra steps to
cancel and await all pending tasks.
To make your timer function to be cancellable, add a try-except clause
catchingasyncio.CancelledErrorsince we use it as a termination
signal.You may addTimerDelayPolicyargument to control the behavior when the
timer-fired task takes longer than the timer interval.DEFAULTis to accumulate them and cancel all the remainings at once when
the timer is cancelled.CANCELis to cancel any pending previously fired tasks on every interval.importasyncioimportaiotoolsasyncdefmytick(interval):awaitasyncio.sleep(100)# cancelled on every next interval.asyncdefsomewhere():t=aiotools.create_timer(mytick,1.0,aiotools.TimerDelayPolicy.CANCEL)...t.cancel()awaittVirtual ClockIt provides a virtual clock that advances the event loop time instantly upon
any combination ofasyncio.sleep()calls in multiple coroutine tasks,
by temporarily patching the event loop selector.This is also used inour timer test suite.importaiotoolsimportpytest@pytest.mark.asyncioasyncdeftest_sleeps():loop=aiotools.compat.get_running_loop()vclock=aiotools.VirtualClock()withvclock.patch_loop():print(loop.time())# -> prints 0awaitasyncio.sleep(3600)print(loop.time())# -> prints 3600Changelog1.7.0 (2023-08-25)Breaking changesDropped the support for Python 3.7 as it's end-of-life.FixesCorrect the type annotation of the callback argument increate_timer()(#61)1.6.1 (2023-05-02)FixesPersistentTaskGroup no longer stores the history of unhandled exceptions and raises them as an exception group to prevent memory leaks (#54)1.6.0 (2023-03-14)FeaturesAddas_completed_safe()which enhancesasyncio.as_completed()usingPersistentTaskGroup(#52)1.5.9 (2022-04-26)FixesImprove checks for pidfd availability to avoid corner cases that may fail on Linux kernel 5.1 and 5.2 wheresignal.pidfd_send_signal()is available butos.pidfd_open()is not (#51)1.5.8 (2022-04-25)FixesExplicitly attach the event loop to thePidfdChildWatcherwhen first initialized (#50)1.5.7 (2022-04-12)FixesFix regression of the default imports in macOS by removing the unused code that caused the misleading fix in #47 (#49)1.5.6 (2022-04-11)FeaturesAdd theclosing_async()async context manager, in addition toaclosing()(#48)FixesAllow importing aiotools on Windows platforms, removing incompatible modules from the default__all__import list (#47)1.5.5 (2022-03-22)FeaturesAddwait_timeoutoption tostart_server()(#46)FixesResolve singal races by minimizing expose of event loop inafork()-ed child processes (#46)MiscellaneousNow the CI runs with Python 3.11a6 or later, with stdlib support ofasyncio.TaskGroup(#45)1.5.4 (2022-03-10)FeaturesPropagate task results and exceptions via separate future instances if they areawait-ed by the caller ofcreate_task()inPersistentTaskGroup, in addition to invocation of task group exception handler.  Note thatawait-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)1.5.3 (2022-03-07)FixesFix feature detection forExceptionGroupand letMultiErrorinheritExceptionGroupinstead ofBaseExceptionGroup(#42)1.5.2 (2022-03-06)FixesRestore the default export ofMultiErrorfor backward compatibility (#40)Setcurrent_ptaskgrouponly whenPersistentTaskGroupis used via theasync withstatement. (#41)1.5.1 (2022-03-06)FixesFix missing naming support ofTaskGroupin Python 3.11 (#39)1.5.0 (2022-03-06)FeaturesAdd support for Python 3.9'smsgargument toTask.cancel(). (#32)Fix "unexpected cancel" bug inTaskGroup. (#35)Rewrite PersistentTaskGroup to use Python 3.11's latest additions such asTask.uncancel()andTask.cancelling()while still supporting older Python versions (#36)AddPersistentTaskGroup.all()to enumerate all non-terminated persistent task groups (#38)1.4.0 (2022-01-10)Featuresptaskgroup: ImplementPersistentTaskGroup(#30)server: Exposeprocess_indexcontext variable for worker processes (#31)1.3.0 (2021-12-19)FixesAdd support for Python 3.10. (#28)Documentation ChangesFix documentation builds on Python 3.10 and Sphinx 4.x, by removing the 3rd-party autodoc-typehints extension and custom stylesheet overrides. (#28)1.2.2 (2021-06-07)Fixesfork:Handle children's segfault (core-dump with signals) explicitly inPidfdChildProcess(#27)1.2.1 (2021-01-12)FixesAvoid side effects of customclone()function and resorts back to the combinatino ofos.fork()andos.pidfd_open()for now (#25)1.2.0 (2021-01-12)Breaking Changesserver:Theuse_threadingargument forstart_server()is completely deprecated. (#23)FeaturesNow the primary target is Python 3.9, though we still support from Python 3.6 (#22)fork:Add a new moduleforkto support PID file descriptors in Linux 5.4+ and a POSIX-compatible fallback to asynchornously fork the Python process without signal/PID races. (#22)server:Completely rewrote the module using the newforkmodule with handling of various edge cases such as async failures of sibiling child processes (#23)1.1.1 (2020-12-16)FixesFix a potential memory leak withTaskGroupwhen it's used for long-lived asyncio tasks. (#21)1.1.0 (2020-10-18)FeaturesAdd acurrent_taskgroupcontext-variable to the taskgroup module (only available for Python 3.7 or later)FixesFix missing auto-import oftaskgroupmodule exports in theaiotoolsroot package.1.0.0 (2020-10-18)FeaturesAdopt an implementation of the taskgroup API asaiotools.taskgroupfromEdgeDB(#18)Addtimer.VirtualClockwhich provides a virtual clock that makes a block of asyncio codes usingasyncio.sleep()to complete instantly and deterministically (#19)MiscellaneousAdopt towncrier for changelog management (#15)Migrate to GitHub Actions for CI (#19)0.9.1 (2020-02-25)A maintenance release to fix up thedefermodule exports in theaiotoolsnamespace.0.9.0 (2020-02-25)defer:A new module that emulates Golang'sdefer()API with asyncio awareness.0.8.5 (2019-11-19)server:Rewrite internals of the worker main functions to use nativeasync withinstead 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 whenmultiprocessing.set_start_method("spawn")is used.NOTE: This is now the default for macOS since Python 3.8.KNOWN ISSUE:#12Remove some packaging hacks in__init__.pyand let setuptools read the version
from a separateaiotools/VERSIONtext file.0.8.3 (2019-10-07)context:Fixaclosing()'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: Addexpire_afteroption tolru_cache()function.0.7.1 (2018-08-24)Minor updates to the documentation0.7.0 (2018-08-24)Add support for Python 3.7context:Updated to work like Python 3.7context:DeprecatedAsyncContextDecoratorstuffs in Python 3.7+context:Added an alias tocontextlib.AsyncExitStackin the standard library.0.6.0 (2018-04-10)Introduce a new moduleaiotools.iterwithaiter()function which
corresponds to an async version of the builtiniter().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 interrupts0.5.1 (2018-01-11)server:Fix a race condition related to handling of worker
initialization errors with multiple workers0.5.0 (2017-11-08)func:Addlru_cache()which is a coroutine version offunctools.lru_cache()0.4.5 (2017-10-14)server:Fix a race condition related to signal handling in the
multiprocessing module during terminationserver: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 moduleaiotools.funcwithapartial()function which is an
async version offunctools.partial()in the standard library0.4.3 (2017-08-06)Addaclosing()context manager likeclosing()in the standard librarySpeed up Travis CI builds for packagingNow provide README in rst as well as CHANGES (this file)0.4.2 (2017-08-01)server: Fix spawning subprocesses in child workersAdd support foruvloop0.4.0 (2017-08-01)Adduse_threadingargument toAdd initial documentation (which currently not served
on readthedocs.io due to Python version problem)0.3.2 (2017-07-31)Addextra_procsargument tostart_server()functionAdd socket and ZeroMQ server examplesImprove CI configs0.3.1 (2017-07-26)Improve CI scriptsAdopt editorconfig0.3.0 (2017-04-26)Addstart_server()function using multiprocessing
with automatic children lifecycle managementClarify the semantics ofAsyncContextGroupusingasyncio.gather()withreturn_exceptions=True0.2.0 (2017-04-20)Add abstract types forAsyncContextManagerRenameAsyncGenContextManagertoAsyncContextManagerAddAsyncContextGroup0.1.1 (2017-04-14)Initial release