miniasync ========= **miniasync** is a small library build on top of asyncio_ to faciliate running small portions of asynchronous code in an otherwise synchronous application. A typical use case is an application which is build as a synchronous application, but at some point needs to make an http request to two different web services and await the results before continuing. In synchronous Python you'd have to do each request in turn - **miniasync** makes it easier to run both requests in parallel without having to write asyncio_ boilerplate code. - Source: https://gitlab.com/aliceh75/miniasync - Package: https://pypi.python.org/pypi/miniasync Please note: - While **miniasync** helps get rid of boilerplate code, you still need to know and understand how asyncio_ works; - **miniasync** uses the `async/await`_ syntax introduced introduced in Python 3.5, and so only works from Python 3.5 up. .. toctree:: :maxdepth: 2 :caption: Contents: .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _async/await: https://www.python.org/dev/peps/pep-0492/ Getting started =============== The simplest way to use **miniasync** is to use the ``miniasync.run`` function. This function takes any number of `coroutine objects`_ (ie. the values returned by invoking a coroutine function), runs the associated tasks concurently with asyncio and returns the list of values returned by each of the coroutine, in the same order. ``miniasync.run`` only returns when all tasks are completed, cancelled or an exception has been raised (See `Handling Exceptions`_). An invocation of ``miniasync.run`` would look like this:: import aiofiles import miniasync async def get_file_content(filename): async with aiofiles.open(filename, mode='r') as f: return await f.read() results = miniasync.run( get_file_content('file1.txt'), get_file_content('file2.txt'), ) assert results == [ '', '' ] .. _coroutine objects: https://docs.python.org/3/library/asyncio-task.html#coroutines .. _Handling Exceptions: Handling exceptions =================== By default ``miniasync.run`` will raise an exception if any of the tasks raises an exception (and it then cancels the tasks that haven't finished). Sometimes if one task raises an expected exception, you will still want the other tasks to complete and return their results. You can do this by specifying you want some exceptions returned, rather than raised. In such case, should one of the specified exception be raised, ``miniasync.run`` will return the exception instead of a result:: import miniasync from aiohttp.client import ClientError def notify_people(notification): results = miniasync.run( post_on_social_network_lambda(notification), post_on_social_network_delta(notification), return_exceptions=(ClientError,) ) # Now entries in results may contain a result, # OR an instance of ClientError (or one of it's # descendant classes). # Other exceptions are raised normally.If this # happens, all other coroutines are cancelled As noted above if there is an unhandled exceptions raised in one of the coroutines, the other coroutines are cancelled. With asyncio, cancelations happen by raising a `CancelledError Exception`_. To account for this, you must make sure to enclose any ``await`` statement in a ``try/except`` or ``try/finally`` block. .. _CancelledError Exception: https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.CancelledError Using the asyncio event loop ============================ Whenever you invoke ``miniasync.run``, it creates it's own local `asyncio event loop`_. This provides some isolation as a single invocation of ``miniasync.run`` will not run other async tasks defined outside of the invocation (though a library invoked by one of the coroutines could add tasks on the event loop). Sometimes however you want to access the event loop before running ``miniasync.run`` - in particular because many async libraries (for example `asyncio Queues`_) are linked to the `asyncio event loop`_ that exists at the point of invocation. To do this **miniasync** provides a context manager, ``miniasync.loop``. Instead of running ``miniasync.run`` you would run:: import miniasync with miniasync.loop() as loop: loop.run(coroutine1(), coroutine2()) Note that we are not invoking ``miniasync.run`` (this would create yet another loop), but ``loop.run``. You can now create objects that rely on the current loop within the block, for example:: import asyncio import miniasync async def coro1(q): q.put_nowait('world') return 'hello' async def coro2(q): return await q.get() with miniasync.loop() as loop: q = asyncio.Queue() results = loop.run( coro1(q), coro2(q) ) assert results == ['hello', 'world'] Some libraries are best used with some preparation that can only be done in an async function (for example sharing a single `aiohttp Session object`_ amongst coroutines). In that case you can nest calls to ``miniasync.run``:: import aiohttp import miniasync async def post_on_social_network_lambda(session, notification): async with session.post('http://social_network.lambda', data=notification) as reply: return await reply.text() async def post_on_social_network_delta(session, notification): async with session.post('http://social_network.delta', data=notification) as reply: return await reply.text() async def post_on_social_networks(notification): with miniasync.loop() as loop: async with aiohttp.ClientSession() as session: results = loop.run( post_on_social_network_lambda(session, notification), post_on_social_network_delta(session, notification) ) return "Social network lambda said %s, and social network delta said %s" % ( results[0], results[1] ) def notify_people(notification): results = miniasync.run(post_on_social_networks(notification)) return results[0] .. _aiohttp Session object: https://docs.aiohttp.org/en/stable/client_reference.html#client-session .. _asyncio event loop: https://docs.python.org/3/library/asyncio-eventloop.html .. _asyncio Queues: https://docs.python.org/3/library/asyncio-queue.html API === .. autofunction:: miniasync.run .. autofunction:: miniasync.loop .. autoclass:: miniasync.MiniAsync :members: License ======= Copyright © 2018, Alice Heaton. Released under the `LGPL 3 License`_ .. _LGPL 3 License: https://www.gnu.org/licenses/lgpl-3.0.html