Skip to main content

fastapi-router-lazy

Latest Version MIT License Build Status Codecov semantic-release

Lazy, on-demand loading of FastAPI routers.

Large FastAPI applications pay for every router at startup: importing the module, building each route's dependency graph and response model, generating its OpenAPI schema. fastapi-router-lazy mounts a tiny stub per route and loads the real router only on the first request matching one of its paths.

The full startup win — unused router modules are never imported — comes from generating the route metadata ahead of time and shipping it, so the app reads that cache at startup instead of importing every module. The default in-process extractor is the simplest wiring, but it imports each module at startup to read its routes: it mounts on demand without deferring the imports. The documentation covers which strategy defers imports.

The core needs nothing but FastAPI and works with plain fastapi.APIRouter.

Install

pip install fastapi-router-lazy
# variant/version-aware extraction:
pip install "fastapi-router-lazy[variants]"

Usage

The examples below use "myapp" as the name of your importable Python package — the one the extractor walks to find modules named router.py, each exposing an APIRouter:

myapp/                  # <- the package name you pass to route_infos_extractor
├── __init__.py
├── users/
│   └── router.py       # exposes `router = APIRouter()`
└── items/
    └── router.py

Given that layout, lazy loading is two steps.

1. Generate the route metadata (at build time — imports each module once and writes routes.json):

from pathlib import Path

from fastapi_router_lazy import route_infos_extractor

route_infos_extractor("myapp", cache=True, cache_file=Path("routes.json"))

2. Run lazily (at runtime — reads the metadata and imports no router module until its first matching request):

from pathlib import Path

from fastapi import FastAPI

from fastapi_router_lazy import (
    RouterLoader,
    lazy_middleware_factory,
    route_infos_extractor,
)

app = FastAPI()

# strict=True: use the prebuilt metadata, never re-import at startup.
extractor = route_infos_extractor(
    "myapp", cache=True, cache_file=Path("routes.json"), strict=True
)
loader = RouterLoader(extractor, app)

middleware = lazy_middleware_factory(loader)
app.add_middleware(middleware)

# Register the stubs; real routers load on first matching request.
loader.load(middleware)

The request that triggers the loading of a router carries an x-fastapi-router-lazy-loading-router: <module>:<variable> header, so the application can log which request paid for the import (read it with Header(alias=LAZY_LOADING_ROUTER_HEADER)). The middleware strips any header of that name sent by the client, from every request. Pass header_name="x-myapp-loading-router" to lazy_middleware_factory to rename it, and response_header=True to also set it on the response — off by default, as it exposes internal module names to the client.

The default extractor (route_infos_extractor("myapp"), no cache) is the simplest wiring, but it imports every module at startup to read its routes — it mounts on demand without deferring imports. Eager loading, deployment filtering, and the variant/version-aware extractor ([variants]) are covered in the documentation.

Routers computing their routes

The cache checksums each router file. A router that computes its routes from data declared elsewhere — a loop over an imported constant, a registry filled by other modules, enum values — must list those modules in a module-level __route_sources__, so that changing any of them re-extracts the router:

from fastapi import APIRouter

__route_sources__ = ("myapp.config.meta", "myapp.dal.ids")

router = APIRouter()

The cache reads the declaration from the source, without importing the router. A declaration that is not a tuple or list of module names, or that names a module that cannot be found, is reported by a warning and its router is re-extracted on every start (outside strict mode).

The declaration is the developer's responsibility: nothing detects a router that should declare route sources and does not. verify_route_infos_cache extracts routers again and returns the routes on which the cache disagrees; run it in a test suite, on every router module, to catch a missing or incomplete declaration:

from fastapi_router_lazy import (
    CachedRouteInfosExtractor,
    route_infos_extractor,
    verify_route_infos_cache,
)


def test_route_infos_cache_is_up_to_date() -> None:
    extractor = route_infos_extractor("myapp", cache=True)
    assert isinstance(extractor, CachedRouteInfosExtractor)
    modules = list(extractor.scan_router_modules())
    assert verify_route_infos_cache(extractor, modules) == []

Limitations

Lazy loading trades some runtime dynamism for startup speed:

  • lazily-declared routes are absent from /openapi.json and /docs until their first matching request mounts them (mount eagerly if you need a complete schema up front);
  • no prefix= is applied at include_router time — bake prefixes into the APIRouter itself;
  • a stub and a real route sharing a path are resolved by Starlette match order.

See the Limitations section of the docs for details.

Documentation

Full documentation is available at toilal.github.io/fastapi-router-lazy. A preview of the in-development develop branch is published at toilal.github.io/fastapi-router-lazy/dev/.

Requirements

Python 3.11+ and FastAPI.

License

MIT

CHANGELOG

v0.4.0 (2026-11-01)

Features

  • cached-extractor: Checksum routers with their declared route sources (dd2013b)

v0.3.0 (2026-10-25)

Bug Fixes

  • cached-extractor: Write the route infos cache atomically and survive a corrupted one (533ab0b)

  • router-loader: Keep the streamed item type and deduplicate tags on flattened routes (5387fa4)

  • variants: Extract variant route infos in a subprocess and resolve subclassed routers (108336e)

Features

  • middleware: Set the lazy-loading header on the request, with a configurable name (98c2a6f)

v0.2.4 (2026-10-08)

Bug Fixes

  • variants: Leave no child residue on parent RouterWrappers (#25, 5ffdcb9)

v0.2.3 (2026-10-08)

Bug Fixes

  • Rebuild loaded routes from their effective include context (#23, 11f1c6a)

  • extractor: Register routes reached through nested includes (#23, 11f1c6a)

  • router-loader: Keep effective contexts off the caller's wrappers (#23, 11f1c6a)

  • router-loader: Rebuild loaded routes from their effective include context (#23, 11f1c6a)

v0.2.2 (2026-07-25)

Bug Fixes

  • router-loader: Isolate reparented routes (#20, b31933a)

  • router-loader: Preserve dependency overrides (#20, b31933a)

v0.2.1 (2026-07-20)

Bug Fixes

  • Flatten _IncludedRouter wrappers into serving routes (#18, 62b6788)

v0.2.0 (2026-07-05)

Features

v0.1.0 (2026-07-05)

  • Initial Release

Changelog entries are generated automatically by python-semantic-release from Conventional Commits on release.

Metadata

Release files for fastapi-router-lazy 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-router-lazy 0.4.0
File Size Uploaded Provenance Actions
fastapi_router_lazy-0.4.0.tar.gz 169.2 kB Without provenance Details for fastapi_router_lazy-0.4.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-router-lazy 0.4.0
File Interpreter ABI Platform Actions
fastapi_router_lazy-0.4.0-py3-none-any.whl Python 3 none any Details for fastapi_router_lazy-0.4.0-py3-none-any.whl

Total release size: 201.9 kB

Release files / fastapi_router_lazy-0.4.0.tar.gz

Download URL fastapi_router_lazy-0.4.0.tar.gz
Size 169.2 kB
Tags Source
SHA-256 checksum
How to use checksums
85633ef074ea77a03b991cccc8751ecf1f1b2d02410fc099e318fa8172a23097
BLAKE2b-256 checksum
How to use checksums
1ebfab2bc630007baffdfc76e36f612d0474442e31108ed7b443112410dde764
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fastapi_router_lazy-0.4.0-py3-none-any.whl

Download URL fastapi_router_lazy-0.4.0-py3-none-any.whl
Size 32.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
650cb0ab2eb724af2649615b4c9ace4215372207c103e1f2ef5b3a446848b96b
BLAKE2b-256 checksum
How to use checksums
6bbfb8959ea47c3e9493bc67a8a918f26aedfd4ed0b7d0206bfd20930164f7df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for fastapi-router-lazy 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page