FastAPI + SQLAlchemy¶
Setup¶
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
integration = FastAPISQLAlchemyIntegration()
The integration composes:
- the FastAPI adapter, which parses
filter,sort,page, andsize - the SQLAlchemy backend, which lowers compiled criteria into
Selectstatements
It does not execute database I/O. It only builds SQLAlchemy statements.
The integration works with:
- synchronous SQLAlchemy execution through
Session - asynchronous SQLAlchemy execution through
AsyncSession
Route-ready dependencies¶
from typing import Annotated, Any
from fastapi import Depends, FastAPI
app = FastAPI()
@app.get("/users")
def list_users(
stmt: Annotated[Any, Depends(integration.select_dependency(User))],
):
return {"sql": str(stmt)}
@app.get("/users/count")
def count_users(
stmt: Annotated[Any, Depends(integration.count_select_dependency(User))],
):
...
@app.get("/users/paginated")
def paginated_users(
bundle: Annotated[
Any,
Depends(integration.paginated_select_dependency(User)),
],
):
# bundle.statement -> filtered + sorted + paged SELECT
# bundle.count_statement -> filtered count SELECT
...
paginated_select_dependency(...) returns a
SQLAlchemyPaginatedSelect bundle with:
statementcount_statement
Declarative resources¶
users = integration.resource(
User,
filterable_fields={"id", "name"},
sortable_fields={"name"},
default_sort="name,desc",
max_page_size=50,
filter_examples={
"by_name": {"summary": "By name", "value": "name==demo"},
},
)
@app.get("/users")
def list_users(
stmt: Annotated[Any, Depends(users.select_dependency())],
):
...
Resources are useful when one model/route pair has a stable public contract:
- allowed filter fields
- allowed sort fields
- default sort
- OpenAPI examples
- page-size limits
Also available:
users.count_select_dependency()users.paginated_select_dependency()users.applier_dependency()
Custom base statement¶
from sqlalchemy import select
users = integration.resource(
User,
statement_factory=lambda: select(User).where(User.status == "active"),
default_sort="-name",
)
Use statement_factory when the public route should always start from one
restricted base query.
Applying criteria directly¶
stmt = integration.apply(select(User), User, request_criteria)
stmt = integration.select(User, request_criteria)
stmt = integration.count_select(User, request_criteria)
bundle = integration.paginated_select(User, request_criteria)
These helpers are useful outside route dependencies, for example in service layers or tests.
Error behavior¶
Adapter-layer failures keep the same status behavior as the request adapter:
400for parse and page-validation failures422for semantic failures
Backend-layer failures are also normalized to 422 in the integration layer.
Examples:
- filtering on an unmapped field
- sorting on an unmapped field
- backend-specific lowering errors
This avoids leaking raw SQLAlchemy exceptions as 500 responses.
Free-threaded behavior¶
The integration caches dependency callables and base select(model) statements
per model. Those shared caches are published with explicit synchronization for
free-threaded execution.
Async note¶
This page shows the synchronous route shape. The integration is also compatible
with async def routes and AsyncSession; see Async Flows.