Skip to content

Command line

py2axum has three commands: generation (py2axum <package>), which writes a Cargo project; py2axum check <package>, which runs the same translation without writing anything and reports route by route; and py2axum watch <package>, which regenerates, rebuilds and restarts the binary each time the project changes.

When generation refuses a construct, it prints its error code, the file:line, why and what to do:

error[P2A0201]: app/routers/export.py:28: library call `zipfile.ZipInfo()` is not supported (not in the py2axum library map)
  = why: The call goes to a library the binary has no equivalent for: py2axum maps a closed list of library ...
  = help: Use a supported library or the standard library for the same job (see Libraries and Standard ...
  = see: https://larrymotalavigne.github.io/py2axum/reference/errors/#p2a0201

Generating a crate

py2axum api --root . --python-side auto -o build/api --name api
cargo build --release --manifest-path build/api/Cargo.toml
  • package is the directory of the package that holds the FastAPI application.
  • --root is the import root, the directory that would be on sys.path when you run the application (uvicorn api.main:app is run from it); by default, the parent of the package.
  • -o/--out is the output directory of the Rust project, --name the crate's name (and the binary's; default <package>_axum). The generated project is an ordinary Cargo project: do not edit it, change the Python and generate again (First application).
  • --python-side PATH|lifespan|mount|auto, repeatable, says what stays in a Python process next to the binary (Hybrid mode): a route pattern or a mount prefix (PATH), the lifespan, the app's last app.mount()s (mount: they get every request no translated route fully matches), or auto: every route that does not translate, and such mounts. Without it, generation fails on the first refused route.
  • --no-stream buffers list responses instead of streaming them (Large list responses).
  • --report FILE is deprecated: an internal coverage report (Markdown + JSON), outside the stable interface; use py2axum check --format json or --format markdown instead.
  • --allow-untested-versions translates even if the project locks library versions outside the tested ranges, at your own risk.
  • --python X.Y names the Python the project runs on (3.12 to 3.15; also PY2AXUM_TARGET_PYTHON): the binary reproduces its messages and behaviours, and sys.version_info is folded against it. Default: the project's .python-version, else this Python when requires-python allows it (Python versions). check and watch take it too.
  • --backend is deprecated and has no effect: there is one backend since 0.4. --backend auto and --backend dyn are still accepted until 1.0, with a warning; --backend typed is an error.

The options, their defaults and the exit statuses are a stable interface: see Versioning and deprecation.

usage: py2axum [-h] [-o OUT] [--root ROOT] [--backend {auto,typed,dyn}]
               [--python-side PATH|lifespan|mount|auto] [--report REPORT] [--name NAME]
               [--no-stream] [--allow-untested-versions] [--python X.Y]
               package

py2axum: transpile a FastAPI + SQLAlchemy + Pydantic + aiohttp package to a Rust/axum project.

positional arguments:
  package               directory of the FastAPI application package

options:
  -h, --help            show this help message and exit
  -o, --out OUT         output directory for the Rust project
  --root ROOT           import root, like sys.path (default: parent of the package)
  --backend {auto,typed,dyn}
                        deprecated, no effect: there is one backend (auto and dyn are accepted
                        until 1.0; typed was removed in 0.4)
  --python-side PATH|lifespan|mount|auto
                        what stays in a Python process next to the binary (raw routes such as
                        /api/v1/mcp, lifespan; mount: the app's last app.mount(), which gets every
                        request no translated route fully matches); auto: every route that does
                        not translate, and such mounts
  --report REPORT       deprecated (internal tool, use `py2axum check --format json|markdown`):
                        coverage report (Markdown + JSON) instead of generating
  --name NAME           crate name (default: <package>_axum)
  --no-stream           buffer list responses instead of streaming them
  --allow-untested-versions
                        translate even if the project locks library versions outside the tested
                        ranges
  --python X.Y          the Python the project runs on (default: its .python-version, else this
                        Python if the project's requires-python allows it); messages and
                        sys.version_info follow it

py2axum check

py2axum check api --root .
py2axum check api --root . --python-side auto --fail-under 80     # in CI: exit 1 below 80 % native

check takes the same package, --root, --python-side (PATH, lifespan, mount or auto) and --allow-untested-versions as generation, and:

  • --format text|json|markdown: the terminal report (default); machine-readable output on stdout, with the same verdicts, each refused route's code and suggestion, and the unblock list (--json is a synonym; its format is published as a JSON Schema, check-schema.json); or Markdown tables for a pull request comment or a CI job summary (>> "$GITHUB_STEP_SUMMARY");
  • --fail-under PCT: also exit 1 when fewer than PCT % of the routes are native;
  • --explain CODE: print why a class of construct is refused and what to do (Error codes), then exit.

Errors that concern the whole application (and versions outside the tested ranges) are listed apart: they refuse the generation whatever the routes. Exit status: 0 when generation would succeed (and native routes ≥ --fail-under), 1 otherwise. Reading its output: py2axum check.

usage: py2axum check [-h] [--root ROOT] [--backend {auto,dyn}]
                     [--python-side PATH|lifespan|mount|auto] [--format {text,json,markdown}]
                     [--json] [--explain CODE] [--fail-under PCT] [--allow-untested-versions]
                     [--python X.Y]
                     [package]

`py2axum check`: what would translate, route by route, without generating a crate.

positional arguments:
  package               directory of the FastAPI application package

options:
  -h, --help            show this help message and exit
  --root ROOT           import root, like sys.path (default: parent of the package)
  --backend {auto,dyn}  deprecated, no effect (one backend since 0.4)
  --python-side PATH|lifespan|mount|auto
                        as for generation: routes or the lifespan left to Python; auto: every
                        route that does not translate
  --format {text,json,markdown}
                        text (default), json (machine-readable, on stdout) or markdown (for a pull
                        request or a CI summary)
  --json                same as --format json
  --explain CODE        explain an error code (P2A0201...): why it is refused and what to do, then
                        exit
  --fail-under PCT      also exit 1 when fewer than PCT % of the routes are native
  --allow-untested-versions
                        do not refuse library versions outside the tested ranges
  --python X.Y          the Python the project runs on (default: its .python-version, else this
                        Python if the project's requires-python allows it); messages and
                        sys.version_info follow it

py2axum watch

DATABASE_URL=postgresql://localhost/app py2axum watch api --root . --python-side auto -o build/api --run

watch takes generation's package, -o, --root, --name, --python-side, --no-stream and --allow-untested-versions, watches the Python files under --root, and on each change regenerates, copies the changed generated files only, builds the debug profile (incremental) and, with --run, restarts the binary. A failed cycle prints the error and keeps the previous binary running. Ctrl-C stops the binary and exits. Guide: Watch mode.

usage: py2axum watch [-h] -o OUT [--root ROOT] [--name NAME]
                     [--python-side PATH|lifespan|mount|auto] [--no-stream]
                     [--allow-untested-versions] [--run] [--release] [--no-build] [--interval S]
                     [--debounce S] [--stop-timeout S] [--python X.Y]
                     package [-- ARG ...]

`py2axum watch`: regenerate, rebuild and restart the binary each time the Python project changes.

positional arguments:
  package               directory of the FastAPI application package
  -- ARG                arguments passed to the binary, after --

options:
  -h, --help            show this help message and exit
  -o, --out OUT         output directory of the Rust project
  --root ROOT           import root, like sys.path (default: parent of the package); the directory
                        watched
  --name NAME           crate name (default: <package>_axum)
  --python-side PATH|lifespan|mount|auto
                        as for generation (auto is handy while porting: refused routes do not stop
                        the cycle)
  --no-stream           buffer list responses instead of streaming them
  --allow-untested-versions
                        translate even if the project locks library versions outside the tested
                        ranges
  --run                 start the binary, and restart it after each successful build
  --release             build the release profile (slow: fat LTO); default: the debug profile,
                        incremental
  --no-build            regenerate only (an IDE or another tool builds)
  --interval S          polling interval (default 0.5 s)
  --debounce S          quiet time after the last change before a cycle starts (default 0.3 s)
  --stop-timeout S      time the binary gets to exit after SIGTERM before it is killed (default 5
                        s)
  --python X.Y          the Python the project runs on (default: its .python-version, else this
                        Python if the project's requires-python allows it); messages and
                        sys.version_info follow it

The binary's migrate

The generated binary applies the project's alembic migrations without Python when an alembic.ini was found at generation (Database migrations), on DATABASE_URL:

  • migrate [upgrade [head|<revision>]] applies the revisions from where the database is, like alembic upgrade;
  • migrate current prints the database's revision, like alembic current;
  • migrate --sql prints the SQL alembic upgrade head --sql prints.

A revision refused at generation (P2A0510, or one reading the database) stops migrate before anything is applied: apply it with alembic.