How-To: Use FastAPI-Restly in an Existing Project#
FastAPI-Restly registers ordinary FastAPI path operations. You can mount Restly
views on the same FastAPI app or APIRouter as your existing routes, adopt it
one resource at a time, and move individual endpoints back to plain FastAPI when
that is the clearer shape.
Add Restly Next to Existing Routes#
Use fr.include_view(...) wherever you already compose routes. Existing routers
and Restly views can share the same parent app or router:
from fastapi import APIRouter, FastAPI
import fastapi_restly as fr
from .orders import router as orders_router
app = FastAPI()
api = APIRouter(prefix="/api")
api.include_router(orders_router, prefix="/orders") # existing FastAPI routes
class UserView(fr.AsyncRestView):
prefix = "/users"
model = User
schema = UserRead
fr.include_view(api, UserView) # generated /api/users routes
app.include_router(api)
Adoption is per resource. In the example above, orders stay hand-written while
users use Restly. Adding a ProductView later does not require changing the
orders router.
You can also keep plain FastAPI routes beside a Restly resource for endpoints that are not part of the CRUD surface:
@api.get("/users/{id}/export")
async def export_user(id: int):
...
Step Out for One Endpoint#
If one generated endpoint should be hand-written, exclude only that route and add the FastAPI route yourself:
class UserView(fr.AsyncRestView):
prefix = "/users"
model = User
schema = UserRead
exclude_routes = (fr.ViewRoute.DELETE,)
fr.include_view(api, UserView)
@api.delete("/users/{id}", status_code=204)
async def delete_user(id: int):
...
That leaves Restly responsible for list, create, read, and update while DELETE
uses your ordinary FastAPI implementation. For smaller changes that keep the
same HTTP contract, prefer overriding a perform_* handler; for a different
status code, response shape, or query interface, see
Override CRUD Behavior and Add Custom Endpoints.
Step Out for a Whole Resource#
There is no global Restly router to unwind. A resource is included only where
you call fr.include_view(...). To move a resource back to plain FastAPI,
remove that include call and register an APIRouter with the same prefix and
path operations.
Your models, Pydantic schemas, dependencies, and database session wiring can stay in ordinary application modules. Restly does not need to own the whole app.
Provide Your Own Session Generator#
If your project already manages its own database sessions, configure FastAPI-Restly to use them instead of its built-in session factory.
If you provide custom sessionmakers or generators, make sure their lifecycle and
session options match the behavior your views rely on. Restly’s built-in
factories intentionally use different autoflush defaults for sync and async
sessions and keep expire_on_commit=False for both; see
Session Factory Defaults.
Custom generators also own transaction handling: Restly does not add its
commit_session_on_response behavior around them.
For async views (AsyncRestView), pass an async generator to
fr.configure():
from typing import AsyncIterator
from sqlalchemy.ext.asyncio import AsyncSession
import fastapi_restly as fr
async def my_get_db() -> AsyncIterator[AsyncSession]:
...
yield MyAsyncSession()
fr.configure(session_generator=my_get_db)
For sync views (RestView), pass a sync generator:
from typing import Iterator
from sqlalchemy.orm import Session
import fastapi_restly as fr
def my_get_db() -> Iterator[Session]:
...
yield MySession()
fr.configure(sync_session_generator=my_get_db)
Use a Custom Session Dependency on One View#
Use fr.configure(...) when one session source should be the default for the
application. If only one view should use a different session source, override
the view’s session dependency instead.
from collections.abc import AsyncIterator
from typing import Annotated
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
import fastapi_restly as fr
reporting_engine = create_async_engine("postgresql+asyncpg://user:pass@reports/db")
ReportingSession = async_sessionmaker(
bind=reporting_engine,
autoflush=False,
expire_on_commit=False,
)
async def get_reporting_db() -> AsyncIterator[AsyncSession]:
async with ReportingSession() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
ReportingSessionDep = Annotated[AsyncSession, Depends(get_reporting_db)]
@fr.include_view(app)
class ReportView(fr.AsyncRestView):
prefix = "/reports"
model = Report
schema = ReportRead
session: ReportingSessionDep
The custom dependency owns its own lifecycle, including commit/rollback policy. This is the recommended escape hatch for read replicas, reporting databases, or other per-view session wiring. Restly does not currently provide named engines or named Restly contexts.
You can also use FastAPI-Restly’s configured session proxy directly in your own code (for example in background tasks):
import fastapi_restly as fr
async with fr.open_async_session() as session:
result = await session.execute(...)
Use Your Own DeclarativeBase Models#
If your project already has SQLAlchemy models on a custom DeclarativeBase,
you can use those models directly in FastAPI-Restly views:
import fastapi_restly as fr
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class AppBase(DeclarativeBase):
pass
class World(AppBase):
__tablename__ = "world"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
message: Mapped[str]
@fr.include_view(app)
class WorldView(fr.AsyncRestView):
prefix = "/world"
model = World
FastAPI-Restly supports these models for generated CRUD routes and
auto-generated schemas. When creating tables, use your own base metadata
(for example AppBase.metadata.create_all(...)).