Background Tasks

A background task runs whilst the app handles requests, for example it could be started in a before_serving method and persist for the lifetime of the app, or duing a request and persist until it completes. In any case the task must be managed with care as starting, error handling, and cancellation are more complicated with background tasks.

To create a background task a nursery must be used. The nursery must also persist beyond the scope/lifespan of a request, ideally matching the lifespan of the app itself. In Quart-Trio this nursery is available on the app (note also current_app),

@app.before_serving
async def startup():
    app.nursery.start_soon(background_task)

@app.route("/")
async def index():
    app.nursery.start_soon(background_task)
    return ...

Error handling

Background tasks may raise exceptions, which if not handled will cause the app nursery to close (and result in the ASGI server handling the error). Therefore it is best to handle any errors directly, e.g.

async def wrap_background_task(coro_function, *args):
    try:
        await coro_function(*args)
    except (trio.MultiError, Exception):
        log.exception(...)
        # Do something else?

@app.route("/")
async def index():
    app.nursery.start_soon(wrap_background_task(background_task))
    return ...

Testing background tasks

By default the Quart test client is scoped to requests without any ability to run background tasks. To test with background tasks the test_app context manager must be used. This creates the nursery on the app which exists during the context. For example,

async def test_something():
    async with app.test_app() as test_app:
        assert test_app.nursery is not None
        test_client = test_app.test_client()
        test_client.get(...)
    assert app.nursery is None