Skip to content

Async support

Engines

Typer supports asyncio out of the box. Trio is supported through anyio, which can be installed as optional dependency:

$ pip install typer[anyio]
---> 100%
Successfully installed typer anyio

Default engine selection

none anyio trio anyio + trio
asyncio asyncio via anyio asyncio* trio via anyio

* If you don't want to install anyio when using trio, provide your own async_runner function

Using with run()

Async functions can be run just like normal functions:

import asyncio

import typer

app = typer.Typer()


async def main():
    await asyncio.sleep(1)
    typer.echo("Hello World")


if __name__ == "__main__":
    typer.run(main)

Or using anyio:

import anyio
import typer

app = typer.Typer()


async def main():
    await anyio.sleep(1)
    typer.echo("Hello World")


if __name__ == "__main__":
    typer.run(main)

Using with commands

Async functions can be registered as commands explicitely just like synchronous functions:

import asyncio

import typer

app = typer.Typer(async_runner=asyncio.run)


@app.command()
async def wait(seconds: int):
    await asyncio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds")


if __name__ == "__main__":
    app()

Or using anyio:

import anyio
import typer

app = typer.Typer()


@app.command()
async def wait(seconds: int):
    await anyio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds")


if __name__ == "__main__":
    app()

Or using trio via anyio:

import trio
import typer

app = typer.Typer()


@app.command()
async def wait(seconds: int):
    await trio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds")


if __name__ == "__main__":
    app()

Customizing async engine

You can customize the async engine by providing an additional parameter async_runner to the Typer instance or to the command decorator.

When both are provided, the one from the decorator will take precedence over the one from the Typer instance.

Customize a single command:

import asyncio

import trio
import typer

app = typer.Typer()


@app.command()
async def wait_trio(seconds: int):
    await trio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds using trio (default)")


@app.command(async_runner=asyncio.run)
async def wait_asyncio(seconds: int):
    await asyncio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds using asyncio (custom runner)")


if __name__ == "__main__":
    app()

Customize the default engine for the Typer instance:

import asyncio

import anyio
import typer

app = typer.Typer(async_runner=lambda c: anyio.run(lambda: c, backend="asyncio"))


@app.command()
async def wait_anyio(seconds: int):
    await anyio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds using asyncio via anyio")


@app.command()
async def wait_asyncio(seconds: int):
    await asyncio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds using asyncio")


if __name__ == "__main__":
    app()

Using with callback

The callback function supports asynchronous functions with the async_runner parameter as well:

import asyncio

import trio
import typer

app = typer.Typer()


@app.command()
async def wait_trio(seconds: int):
    await trio.sleep(seconds)
    typer.echo(f"Waited for {seconds} seconds using trio (default)")


@app.callback(async_runner=lambda c: asyncio.run(c))
async def wait_asyncio(seconds: int):
    await asyncio.sleep(seconds)
    typer.echo(
        f"Waited for {seconds} seconds before running command using asyncio (customized)"
    )


if __name__ == "__main__":
    app()

Because the asynchronous functions are wrapped in a synchronous context before being executed, it is possible to mix async engines between the callback and commands.