Air 0.46.0 brings three features that change how you build with Air: sync route handlers that don't block the event loop, automatic cache busting for static files, and the ability to mix Jinja templates directly into Air tag trees. Upgrade with:
uv add --upgrade airWhat's new
Cache-busted static files. Drop a static/ directory into your project and Air handles the rest. Every file gets a content hash in its URL, served with immutable cache headers. HTML responses have their /static/ paths rewritten automatically, so templates, Air tags, and hardcoded HTML all benefit without opt-in. Powered by staticware.
Jinja templates inside Air tag trees. JinjaRenderer now accepts as_string=True, returning rendered HTML that can be embedded directly in Air tags. Mix and match both approaches in the same page. (#981)
jinja = air.JinjaRenderer('templates')
@app.page
def index(request: air.Request):
return air.layouts.mvpcss(
air.Title('Home Page'),
jinja(request, 'content.html', as_string=True)
)Railway deployment guide. New docs walk through deploying Air to Railway with Hypercorn, including configuration and environment setup.
What's better
Windows support. Air's CI now tests on Windows across Python 3.13 and 3.14. Windows users can rely on the test suite catching platform-specific issues.
air.__version__ available at runtime. Read the installed version programmatically without importlib boilerplate.
What's fixed
Sync handlers run in a threadpool. Route handlers written as plain def functions now run in Starlette's threadpool instead of blocking the event loop. Database queries, file I/O, and other blocking operations work naturally without async/await. Air preserves the sync/async nature of your endpoint, so Starlette dispatches it correctly.
Custom exception handlers take priority. User-defined exception handlers now override Air's defaults instead of being silently replaced. Air's built-in 404/500 handlers are also applied automatically when wrapping a custom FastAPI instance. Thanks @msaizar!
status_code honored in route decorators. @app.post("/created", status_code=201) now produces a 201 response as expected, both in the HTTP response and in OpenAPI docs.
Contributors
@audreyfeldroy (Audrey M. Roy Greenfeld) and @pydanny (Daniel Roy Greenfeld) designed and built this release: sync handler dispatch, staticware integration, cache-busted static files, the release automation pipeline, deployment docs, and the README rewrite.
Thanks to @msaizar (Martin Saizar) for fixing exception handler priority and ensuring custom handlers work correctly with Air's defaults, and for adding the missing-examples audit to the QA workflow.
Thanks to @kaapstorm (Norman Hooper) for a docs formatting fix, @garzuze (Lucas Garzuze Cordeiro) for correcting doc callout rendering, and @sankarebarri for ruff naming convention compliance.