FastAPI Integration¶
Advanced Alchemy provides integration with FastAPI through its repository and service patterns.
Basic Setup¶
Configure SQLAlchemy with FastAPI:
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from fastapi import FastAPI
from advanced_alchemy.config import AsyncSessionConfig, SQLAlchemyAsyncConfig
from advanced_alchemy.base import metadata_registry
from advanced_alchemy.extensions.starlette import StarletteAdvancedAlchemy
session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
connection_string="sqlite+aiosqlite:///test.sqlite",
session_config=session_config
)
@asynccontextmanager
async def on_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
"""Initializes the database."""
metadata = metadata_registry.get(sqlalchemy_config.bind_key)
if sqlalchemy_config.create_all:
async with sqlalchemy_config.get_engine().begin() as conn:
await conn.run_sync(metadata.create_all)
yield
app = FastAPI(lifespan=on_lifespan)
alchemy = StarletteAdvancedAlchemy(config=sqlalchemy_config, app=app)
from contextlib import asynccontextmanager
from collections.abc import AsyncGenerator
from fastapi import FastAPI
from advanced_alchemy.config import AsyncSessionConfig, SQLAlchemyAsyncConfig
from advanced_alchemy.base import metadata_registry
from advanced_alchemy.extensions.starlette import StarletteAdvancedAlchemy
session_config = AsyncSessionConfig(expire_on_commit=False)
sqlalchemy_config = SQLAlchemyAsyncConfig(
connection_string="sqlite+aiosqlite:///test.sqlite",
session_config=session_config
)
@asynccontextmanager
async def on_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
"""Initializes the database."""
metadata = metadata_registry.get(sqlalchemy_config.bind_key)
if sqlalchemy_config.create_all:
async with sqlalchemy_config.get_engine().begin() as conn:
await conn.run_sync(metadata.create_all)
yield
app = FastAPI(lifespan=on_lifespan)
alchemy = StarletteAdvancedAlchemy(config=sqlalchemy_config, app=app)
Models and Schemas¶
Define your SQLAlchemy models and Pydantic schemas:
from datetime import date
from uuid import UUID
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from pydantic import BaseModel as _BaseModel
from advanced_alchemy.base import UUIDAuditBase, UUIDBase
class BaseModel(_BaseModel):
"""Extend Pydantic's BaseModel to enable ORM mode"""
model_config = {"from_attributes": True}
class AuthorModel(UUIDBase):
__tablename__ = "author"
name: Mapped[str]
dob: Mapped[date | None]
books: Mapped[list[BookModel]] = relationship(back_populates="author", lazy="noload")
class BookModel(UUIDAuditBase):
__tablename__ = "book"
title: Mapped[str]
author_id: Mapped[UUID] = mapped_column(ForeignKey("author.id"))
author: Mapped[AuthorModel] = relationship(lazy="joined", innerjoin=True, viewonly=True)
class Author(BaseModel):
id: UUID | None
name: str
dob: date | None = None
class AuthorCreate(BaseModel):
name: str
dob: date | None = None
class AuthorUpdate(BaseModel):
name: str | None = None
dob: date | None = None
Repository and Service¶
Create repository and service classes:
from sqlalchemy.ext.asyncio import AsyncSession
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService
from typing import AsyncGenerator
class AuthorRepository(SQLAlchemyAsyncRepository[AuthorModel]):
"""Author repository."""
model_type = AuthorModel
class AuthorService(SQLAlchemyAsyncRepositoryService[AuthorModel]):
"""Author service."""
repository_type = AuthorRepository
async def provide_authors_service(
db_session: Annotated[AsyncSession, Depends(provide_db_session)],
) -> AsyncGenerator[AuthorService, None]:
"""This provides the default Authors repository."""
async with AuthorService.new(session=db_session) as service:
yield service
from sqlalchemy.ext.asyncio import AsyncSession
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService
from collections.abc import AsyncGenerator
class AuthorRepository(SQLAlchemyAsyncRepository[AuthorModel]):
"""Author repository."""
model_type = AuthorModel
class AuthorService(SQLAlchemyAsyncRepositoryService[AuthorModel]):
"""Author service."""
repository_type = AuthorRepository
async def provide_authors_service(
db_session: Annotated[AsyncSession, Depends(provide_db_session)],
) -> AsyncGenerator[AuthorService, None]:
"""This provides the default Authors repository."""
async with AuthorService.new(session=db_session) as service:
yield service
Dependency Injection¶
Set up dependency injection for the database session:
from fastapi import Request
async def provide_db_session(request: Request) -> AsyncSession:
"""Provide a DB session."""
return alchemy.get_session(request) # this is the `StarletteAdvancedAlchemy` object
Controllers¶
Create controllers using the service:
from fastapi import APIRouter, Depends
from uuid import UUID
from advanced_alchemy.filters import LimitOffset
from advanced_alchemy.service import OffsetPagination
author_router = APIRouter()
@author_router.get(path="/authors", response_model=OffsetPagination[Author])
async def list_authors(
authors_service: Annotated[AuthorService, Depends(provide_authors_service)],
limit_offset: Annotated[LimitOffset, Depends(provide_limit_offset_pagination)],
) -> OffsetPagination[AuthorModel]:
"""List authors."""
results, total = await authors_service.list_and_count(limit_offset)
return authors_service.to_schema(results, total, filters=[limit_offset])
@author_router.post(path="/authors", response_model=Author)
async def create_author(
authors_service: Annotated[AuthorService, Depends(provide_authors_service)],
data: AuthorCreate,
) -> AuthorModel:
"""Create a new author."""
obj = await authors_service.create(data.model_dump(exclude_unset=True, exclude_none=True), auto_commit=True)
return authors_service.to_schema(obj)
@author_router.get(path="/authors/{author_id}", response_model=Author)
async def get_author(
authors_service: Annotated[AuthorService, Depends(provide_authors_service)],
author_id: UUID,
) -> AuthorModel:
"""Get an existing author."""
obj = await authors_service.get(author_id)
return authors_service.to_schema(obj)
@author_router.patch(path="/authors/{author_id}", response_model=Author)
async def update_author(
authors_service: Annotated[AuthorService, Depends(provide_authors_service)],
data: AuthorUpdate,
author_id: UUID,
) -> AuthorModel:
"""Update an author."""
obj = await authors_service.update(
data.model_dump(exclude_unset=True, exclude_none=True),
item_id=author_id,
auto_commit=True,
)
return authors_service.to_schema(obj)
@author_router.delete(path="/authors/{author_id}")
async def delete_author(
authors_service: Annotated[AuthorService, Depends(provide_authors_service)],
author_id: UUID,
) -> None:
"""Delete an author from the system."""
_ = await authors_service.delete(author_id, auto_commit=True)
Application Configuration¶
Finally, configure your FastAPI application with the router:
app.include_router(author_router)