Skip to content

Conversion Rules

This converter applies only source-grounded mappings found in local FastAPI/Lilya/Sayer repositories.

Rule Categories

Import and Constructor Rules

  • FastAPI app/router imports map to Lilya app/router imports.
  • Response/middleware import modules are mapped only when Lilya has direct equivalents.
  • Unsupported constructor kwargs are removed and reported.

Routing Rules

  • include_router(...) becomes include(path=..., app=...).
  • api_route(...) becomes route(..., methods=[...]).
  • trace(...) becomes route(..., methods=["TRACE"]).
  • Router prefix= is extracted and merged into route paths where deterministic.

Dependency Rules

  • Signature defaults Depends(dep) become Provide(dep) route dependencies + Provides() defaults.
  • Annotated[..., Depends(dep)] metadata is normalized to Lilya dependency maps.
  • Decorator/constructor dependency lists are converted to dependency dictionaries.

Error and Middleware Rules

  • @app.exception_handler(ExceptionType) becomes app.add_exception_handler(ExceptionType, handler).
  • FastAPI function middleware decorators are removed with explicit diagnostics.
  • Class middleware imports/calls are preserved where direct Lilya mapping exists.

Complex Conversion Behaviors

Router Prefix Handling

FastAPI commonly combines router prefixes and include prefixes.

Converter behavior: - route paths receive extracted router prefixes, - include paths preserve explicit include prefixes, - dynamic combinations that cannot be resolved deterministically are reported.

Dependency Layering

Dependencies can exist simultaneously at: - app/router constructors, - include-router call sites, - route decorators, - route handler signatures.

Converter behavior: - merges dependency layers into Lilya route/include-compatible dependency dictionaries, - generates deterministic synthetic names where needed, - reports unsupported shapes.

Response Metadata Gaps

FastAPI metadata like response_model does not map 1:1 to Lilya route decorator arguments.

Converter behavior: - removes unsupported kwargs, - preserves executable handler code, - emits diagnostics for manual review.

Worked Dependency Example

FastAPI:

from typing import Annotated

from fastapi import Depends, FastAPI


def global_dep() -> str:
    return "global"


app = FastAPI(dependencies=[Depends(global_dep)])


@app.get("/items")
async def items(
    user: str = Depends(global_dep),
    value: Annotated[int, Depends(global_dep)] = 1,
):
    return {"user": user, "value": value}

Lilya:

from lilya.apps import Lilya as FastAPI
from lilya.dependencies import Provide, Provides


def global_dep() -> str:
    return "global"


app = FastAPI(dependencies={"_global_dep": Provide(global_dep)})


@app.get(
    "/items",
    dependencies={"_global_dep": Provide(global_dep), "user": Provide(global_dep), "value": Provide(global_dep)},
)
async def items(user: str = Provides(), value: int = 1, *, _global_dep=Provides()):
    return {"user": user, "value": value}

Worked Complex Example

FastAPI:

from typing import Annotated

from fastapi import APIRouter, Depends, FastAPI


def app_dep() -> str:
    return "app"


def include_dep() -> str:
    return "include"


def router_dep() -> str:
    return "router"


def route_dep() -> str:
    return "route"


app = FastAPI(openapi_url="/openapi.json", dependencies=[Depends(app_dep)])
router = APIRouter(prefix="/api", dependencies=[Depends(router_dep)], tags=["ignored"])


@router.api_route(
    "/items/{item_id}",
    methods=["GET", "POST"],
    dependencies=[Depends(route_dep)],
    response_model=dict,
)
async def item(
    item_id: int,
    token: Annotated[str, Depends(route_dep)],
    extra: str = Depends(router_dep),
):
    return {"id": item_id, "token": token, "extra": extra}


app.include_router(router, prefix="/v1", dependencies=[Depends(include_dep)], tags=["ignored"])

Lilya:

from lilya.apps import Lilya as FastAPI
from lilya.dependencies import Provide, Provides
from lilya.routing import Router as APIRouter
from typing import Annotated


def app_dep() -> str:
    return "app"


def include_dep() -> str:
    return "include"


def router_dep() -> str:
    return "router"


def route_dep() -> str:
    return "route"


app = FastAPI(dependencies={"_app_dep": Provide(app_dep)}, enable_openapi=True)
router = APIRouter()


@router.route(
    "/api/items/{item_id}",
    methods=["GET", "POST"],
    dependencies={
        "_route_dep": Provide(route_dep),
        "_router_dep": Provide(router_dep),
        "token": Provide(route_dep),
        "extra": Provide(router_dep),
    },
)
async def item(
    item_id: int,
    token: str,
    extra: str = Provides(),
    *,
    _router_dep=Provides(),
    _route_dep=Provides(),
):
    return {"id": item_id, "token": token, "extra": extra}


app.include(path="/v1", app=router, dependencies={"_include_dep": Provide(include_dep)})

Operational Guidance

  • Use map rules before conversion to inspect available mappings.
  • Use map applied after conversion to audit which rules were triggered.
  • Treat diagnostics as required migration checklist items.