Examples¶
FastAPI CRUD¶
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine
from sqlalchemy.orm import Mapped, mapped_column, Session
from schemap import AutoBase
app = FastAPI()
engine = create_engine("sqlite:///./db.sqlite3")
class User(AutoBase):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
email: Mapped[str]
AutoBase.metadata.create_all(engine)
def get_session():
with Session(engine) as session:
yield session
@app.post("/users")
def create_user(data: User.CreateSchema, session: Session = Depends(get_session)):
user = User.from_schema(data)
session.add(user)
session.commit()
session.refresh(user)
return user.to_schema()
@app.get("/users/{user_id}")
def get_user(user_id: int, session: Session = Depends(get_session)):
user = session.get(User, user_id)
if not user:
raise HTTPException(status_code=404)
return user.to_schema(User.PublicSchema)
@app.patch("/users/{user_id}")
def update_user(user_id: int, data: User.UpdateSchema, session: Session = Depends(get_session)):
user = session.get(User, user_id)
if not user:
raise HTTPException(status_code=404)
for key, value in data.model_dump(exclude_unset=True).items():
setattr(user, key, value)
session.commit()
session.refresh(user)
return user.to_schema()
The decorator approach works the same way in FastAPI. Just swap AutoBase for @auto_schema:
from schemap import auto_schema
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
@auto_schema
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
email: Mapped[str]
Base.metadata.create_all(engine)
# Same CRUD code; User.Schema, User.CreateSchema etc. all exist
Hide sensitive fields from API responses with exclude_public:
from schemap import SchemaConfig
class User(AutoBase):
__tablename__ = "users"
__schema_config__ = SchemaConfig(exclude_public=["email", "phone"])
...
Custom validators¶
from sqlalchemy.orm import Mapped, mapped_column
from schemap import AutoBase, SchemaConfig
def must_be_positive(v: float) -> float:
if v <= 0:
raise ValueError("Must be positive")
return v
class Product(AutoBase):
__tablename__ = "products"
__schema_config__ = SchemaConfig(
extra_validators={"price": must_be_positive}
)
id: Mapped[int] = mapped_column(primary_key=True)
price: Mapped[float]
p = Product.CreateSchema(price=10.5) # OK
# Product.CreateSchema(price=-5.0) # Raises pydantic.ValidationError
Inheritance patterns¶
Single-table inheritance¶
class Animal(AutoBase):
__tablename__ = "animals"
id: Mapped[int] = mapped_column(primary_key=True)
type: Mapped[str]
name: Mapped[str]
__mapper_args__ = {"polymorphic_on": type}
class Dog(Animal):
__mapper_args__ = {"polymorphic_identity": "dog"}
breed: Mapped[str] = mapped_column(nullable=True)
class Cat(Animal):
__mapper_args__ = {"polymorphic_identity": "cat"}
color: Mapped[str] = mapped_column(nullable=True)
Each subclass gets its own set of four schemas with its own columns.
Custom base class¶
from datetime import datetime, timezone
from schemap import SchemaMixin
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(SchemaMixin, DeclarativeBase):
__abstract__ = True
id: Mapped[int] = mapped_column(primary_key=True)
created_at: Mapped[datetime] = mapped_column(
default=lambda: datetime.now(timezone.utc)
)
class User(Base):
__tablename__ = "users"
name: Mapped[str]