Async libraries#

Muffin speaks ASGI, so the framework itself does not care which event loop runs it: asyncio, trio and curio all work. What does require a choice is every piece of code that starts a loop: the CLI, scripts, tests and background tooling.

Selecting a backend#

asyncio is the default. Override it for a process with the MUFFIN_AIOLIB environment variable, or per command with the CLI option:

$ MUFFIN_AIOLIB=trio muffin app run
$ muffin app --aiolib curio cleanup

Installed libraries are detected at import time. --aiolib only appears in muffin app --help when more than one of them is importable.

Running coroutines from sync code#

aio_run runs a coroutine on the selected backend, the same way the CLI does:

from muffin.utils import aio_run

async def main():
    ...

aio_run(main)

Inside a coroutine, current_async_library() reports the backend that is actually running:

from muffin.utils import current_async_library

async def main():
    assert current_async_library() in {"asyncio", "trio", "curio"}

One process, one backend#

The choice is cached in the process the first time a loop is started, so the first call wins: set MUFFIN_AIOLIB (or --aiolib) before anything runs a loop. Starting a second loop on another backend in the same process does not switch backends; it silently keeps the first one.

That is why tests parametrize the backend instead of switching at runtime. See Testing.

Writing backend-agnostic code#

Handlers, plugins and helpers should avoid asyncio-only APIs: asyncio.sleep, asyncio.create_task and asyncio.Queue do not exist in trio or curio. If the code has to run on all three, keep such calls behind plugin code that ships its own compat layer (the shipped plugins do), or pick a single backend for the project and say so in its README.

uvloop#

Under asyncio, uvicorn can install uvloop as the event loop policy; Muffin needs no changes for that:

$ pip install "uvicorn[standard]"
$ uvicorn app:app --loop uvloop

Deployment covers the rest of the server setup.