Use 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 the business verb (get_many, get_one,
create, update, delete) or its handle_<verb> request 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, schemas, dependencies, and session wiring can stay in ordinary app modules.
Replace an Existing Hand-Written Router#
Going the other direction — retiring a hand-written CRUD router in favor of a generated view — is a mapping exercise:
Map your routes to the generated table.
GET /+POST /+GET/PATCH/DELETE /{id}are covered (the exact contract). Anything else on the router (exports, actions) stays as custom routes on the view or as plain FastAPI routes beside it.Keep custom semantics out of the swap. A route whose contract differs (e.g.
PUTupdates, a non-204 delete) can be excluded viaexclude_routesand kept hand-written until you adapt it.Pin the wire contract with tests first. Write
RestlyTestClienttests against the old router’s responses, then swap in the view and run them unchanged — payload or status drift shows up immediately.
class ProductView(fr.AsyncRestView):
prefix = "/products"
model = Product
schema = ProductRead # match your old response shape exactly
exclude_routes = (fr.ViewRoute.DELETE,) # old DELETE returns the object
fr.include_view(api, ProductView)
api.include_router(legacy_delete_router) # until the contract is adapted
Reuse Your Existing Engine#
The common integration: your app already builds an engine (or sessionmaker)
with the pool settings and URL handling you trust. Hand exactly that object to
fr.configure() — Restly does not need to own it:
import fastapi_restly as fr
# the engine your app already creates somewhere central
fr.configure(async_engine=existing_async_engine)
# sync apps: fr.configure(engine=existing_engine)
# or hand over a sessionmaker instead:
# fr.configure(async_make_session=ExistingAsyncSession)
Restly builds its session factory on top and owns the commit on its views; nothing about the engine changes hands. Reach for a session generator (next section) only when sessions must be constructed in a custom way — scoped sessions, multi-tenant routing, instrumentation.
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.
A custom generator constructs sessions your way but does not own the
commit: it should construct, yield, and clean up (close / roll back on the way
out); Restly commits. Customizing how a session is built never takes the commit
away from Restly.
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]:
# Construct, yield, and clean up -- do NOT commit. Restly owns the commit;
# the ``async with`` rolls back and closes the session on the way out.
async with ReportingSession() as session:
yield session
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 session construction and cleanup. Restly still owns the commit. Use this for read replicas, reporting databases, or other per-view session wiring.
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(...)
# sync counterpart:
with fr.open_session() as session:
result = session.execute(...)
Off-request code owns its commit — these helpers do not commit for you.
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(...)).
See also#
Test APIs with RestlyTestClient and Fixtures — pin the wire contract while migrating; savepoint-isolated tests against your DB.
Deploying — engine configuration from environment values, Alembic, and a production
main.py.Patterns — the idiomatic answers for nested resources, webhooks, and other shapes your existing app probably has.