Skip to content

symfonic.platform.routes

routes

Enumerating what an app actually serves, across FastAPI versions.

Up to FastAPI 0.136, include_router flattened a router's routes into app.routes, so counting them was a list comprehension and every tool in this repository wrote one.

From 0.137 it appends a single _IncludedRouter that holds the router and delegates at request time. The app serves exactly the same URLs -- measured, with a request -- but app.routes now reports one object with no path where it used to report a route per endpoint.

Every route-counting check in this repository read that number, so on 0.137 they all agreed a fully working app mounted nothing: the adopter gate, the artifact matrix, the route-parity gate, and the pin that was added to pyproject on the strength of them. The bug was in the counting, not in the product, and the pin has been lifted.

This is the one enumerator they now share. It is public because an adopter introspecting their own app hits the same wall, and the framework is what handed them the router.

MountedRoute

Bases: tuple

One served endpoint: (path, methods), comparable and printable.

api_routes

api_routes(app: Any, prefix: str = '/api/v1') -> list[MountedRoute]

The subset under prefix. The question most callers are asking.

Source code in src/symfonic/platform/routes.py
def api_routes(app: Any, prefix: str = "/api/v1") -> list[MountedRoute]:
    """The subset under ``prefix``. The question most callers are asking."""
    return [route for route in mounted_routes(app) if route.path.startswith(prefix)]

mounted_routes

mounted_routes(app: Any) -> list[MountedRoute]

Every endpoint app serves, whichever FastAPI version built it.

Walks included routers rather than trusting app.routes to be flat. A route that carries no HTTP method -- a mount, a static files app -- is skipped: this answers "what can be called", and those cannot.

Source code in src/symfonic/platform/routes.py
def mounted_routes(app: Any) -> list[MountedRoute]:
    """Every endpoint ``app`` serves, whichever FastAPI version built it.

    Walks included routers rather than trusting ``app.routes`` to be flat. A
    route that carries no HTTP method -- a mount, a static files app -- is
    skipped: this answers "what can be called", and those cannot.
    """
    return sorted(set(_walk(getattr(app, "routes", ()) or ())))