Skip to content

Conversion Rules

The converter applies deterministic, source-grounded mappings from each source framework to Lilya.

Rule Model

  • Rules are adapter-specific and explicitly registered.
  • Each rule has a stable ID (map rules).
  • Applied rules are reported per conversion (map applied).
  • Unsupported or partial mappings emit diagnostics with file and line context.

FastAPI Rules

Imports and constructors

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

Routing and dependencies

  • include_router(...) -> include(path=..., app=...).
  • api_route(...) and trace(...) normalize to Lilya route methods.
  • Router prefix= is merged into route paths where deterministic.
  • Depends(...) and Annotated[..., Depends(...)] are normalized to Lilya dependency maps.

Middleware and handlers

  • FastAPI exception-handler decorators become add_exception_handler(...) calls.
  • Unsupported decorator middleware patterns are removed with diagnostics.

Flask Rules

  • Flask/Blueprint imports map to Lilya app/router imports.
  • Blueprint url_prefix is merged into route decorators where deterministic.
  • register_blueprint(...) -> include(path=..., app=...) with non-empty path normalization.
  • Unsupported route and constructor kwargs are removed with diagnostics.

Django Rules

  • django.urls.path(...) and re_path(...) -> Lilya Path(...).
  • django.urls.include(...) -> Lilya Include(path=..., app=...).
  • urlpatterns modules are materialized as app = Lilya(routes=urlpatterns).
  • Converter syntax (<int:id>) is normalized to Lilya syntax ({id:int}).
  • Project path mapping:
  • management/commands/* -> directives/operations/*.

Litestar Rules

  • Litestar imports map to Lilya app/routing imports.
  • Module-level HTTP decorators (@get, @post, etc.) are converted to explicit Path(...) routes.
  • route_handlers in Litestar(...) and Router(...) are normalized to Lilya routes.
  • Router(path=...) is materialized via Include(path=..., app=router) at app level.

Starlette Rules

  • Starlette imports map to Lilya import aliases:
  • Starlette -> Lilya,
  • Route -> Path,
  • Mount -> Include,
  • WebSocketRoute -> WebSocketPath.
  • Route(...), Mount(...), and WebSocketRoute(...) call signatures are normalized.
  • mount(...) calls normalize to Lilya include semantics.
  • add_route(route=...) calls normalize to add_route(handler=...).

Path Normalization

For frameworks with route/include path values, converter output enforces Lilya path expectations:

  • path values are never empty,
  • path values are normalized to start with / where deterministically possible.

Worked Examples

FastAPI dependencies

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}
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}

FastAPI complex conversion

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"])
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

  • Run map rules --source SOURCE_KEY before conversion.
  • Run map applied REPORT.json after conversion to audit triggered rules.
  • Treat diagnostics as migration checklist items.