Package: aioresult
Welcome toaioresult!This is a very small library to capture the result of an asynchronous operation, either an async
function (with theResultCaptureclass) or more generally (with theFutureclass). It works
withTrio nurseriesandanyio
task groups. It is not needed for Python 3.11asyncio task groupsbecause
those already return an object representing the task, allowing the result to be retrieved.Code is hosted on github:https://github.com/arthur-tacca/aioresultDocumentation is on ReadTheDocs:Overview (this page):https://aioresult.readthedocs.io/en/v1.0/overview.htmlCapturing a result:https://aioresult.readthedocs.io/en/v1.0/result_capture.htmlFuture objects:https://aioresult.readthedocs.io/en/v1.0/future.htmlUtility functions for waiting:https://aioresult.readthedocs.io/en/v1.0/wait.htmlThe package is on PyPI:https://pypi.org/project/aioresult/Quick OverviewTheResultCaptureclass runs an async function in a nursery and stores its return value (or
raised exception) for later:async with trio.open_nursery() as n:
    result1 = ResultCapture.start_soon(n, foo, 1)
    result2 = ResultCapture.start_soon(n, foo, 2)
# At this point the tasks have completed, and results are stashed in ResultCapture objects
print("results", result1.result(), result2.result())When stored in a list, the effect is very similar to theasyncio gather() function:async with trio.open_nursery() as n:
    results = [ResultCapture.start_soon(n, foo, i) for i in range(10)]
print("results:", *[r.result() for r in results])NoteA key design decision about theResultCaptureclass is thatexceptions are allowed
to propagate out of the task into their enclosing nursery. This is unlike some similar
libraries, which consume the exception in its original context and rethrow it later. In practice,
aioresult’s behaviour is simpler and less error prone.There is also a simpleFutureclass that shares a lot of its code withResultCapture. The
result is retrieved the same way, but it is set explicitly rather than captured from a task. It is
most often used when an API wants to return a value that will be demultiplexed from a shared
connection:# When making a request, create a future, store it for later and return to caller
f = aioresult.Future()

# The result is set, usually inside a networking API
f.set_result(result)

# The calling code can wait for the result then retrieve it
await f.wait_done()
print("result:", f.result())The interface inFutureandResultCaptureto wait for a result and retrieve it is shared in
a base classResultBase.There are also a few simple utility functions to help waiting for results:wait_any()andwait_all()to wait for one or all of a collection of tasks to complete, andresults_to_channel()to allow using the results as they become available.Installation and UsageInstall into a suitable virtual environment withpip:pip install aioresultaioresult can be used with Trio nurseries:import trio
from aioresult import ResultCapture

async def wait_and_return(i):
    await trio.sleep(i)
    return i

async def use_aioresult():
    async with trio.open_nursery() as n:
        results = [ResultCapture.start_soon(n, wait_and_return, i) for i in range(5)]
    print("results:", *[r.result() for r in results])

if __name__ == "__main__":
    trio.run(use_aioresult)It can also be used with anyio task groups:import asyncio
import anyio
from aioresult import ResultCapture

async def wait_and_return(i):
    await anyio.sleep(i)
    return i

async def use_aioresult():
     async with anyio.create_task_group() as tg:
         results = [ResultCapture.start_soon(tg, wait_and_return, i) for i in range(5)]
     print("results:", *[r.result() for r in results])

if __name__ == "__main__":
    asyncio.run(use_aioresult())ContributingThis library is deliberately small and limited in scope, so it is essentially “done”. An exception
to this is that the typing annotations are not exhaustive and have not been tested with any type
checker, so contributions to improve this would be welcome. I could perhaps also be persuaded to
add support for optionally including a per-task cancel scope (seeissue #2).To test any changes, install the test requirements (see thepyproject.tomlfile) and runpytestin the root of the repository:python -m pytestTo also get coverage information, run it with thecoveragecommand:coverage run -m pytestYou can then usecoverage htmlto get a nice HTML output of exactly what code has been tested
and what has been missed.LicenseCopyright Arthur Tacca 2022 - 2024Distributed under the Boost Software License, Version 1.0.
See accompanying file LICENSE or the copy athttps://www.boost.org/LICENSE_1_0.txtThis is similar to other liberal licenses like MIT and BSD: you can use this library without the
need to share your program’s source code, so long as you provide attribution of aioresult.The Boost license has the additional provision that you do not even need to provide attribution if
you are distributing your software in binary form only, e.g. if you have compiled to an executable
withNuitka.  (Bundlers likepyinstallerandpy2exedon’t count for this because they still include the source
code internally.)