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
packageis the directory of the package that holds the FastAPI application.--rootis the import root, the directory that would be onsys.pathwhen you run the application (uvicorn api.main:appis run from it); by default, the parent of the package.-o/--outis the output directory of the Rust project,--namethe 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 lastapp.mount()s (mount: they get every request no translated route fully matches), orauto: every route that does not translate, and such mounts. Without it, generation fails on the first refused route.--no-streambuffers list responses instead of streaming them (Large list responses).--report FILEis deprecated: an internal coverage report (Markdown + JSON), outside the stable interface; usepy2axum check --format jsonor--format markdowninstead.--allow-untested-versionstranslates even if the project locks library versions outside the tested ranges, at your own risk.--python X.Ynames the Python the project runs on (3.12 to 3.15; alsoPY2AXUM_TARGET_PYTHON): the binary reproduces its messages and behaviours, andsys.version_infois folded against it. Default: the project's.python-version, else this Python whenrequires-pythonallows it (Python versions).checkandwatchtake it too.--backendis deprecated and has no effect: there is one backend since 0.4.--backend autoand--backend dynare still accepted until 1.0, with a warning;--backend typedis 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'scodeandsuggestion, and theunblocklist (--jsonis 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 thanPCT% 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, likealembic upgrade;migrate currentprints the database's revision, likealembic current;migrate --sqlprints the SQLalembic upgrade head --sqlprints.
A revision refused at generation (P2A0510, or one reading the database) stops migrate before anything is
applied: apply it with alembic.