Skip to content

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_none=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 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 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]