Skip to content

Defining Entities

Basic Entity Definition

An entity is a class that serves as both a Pydantic model and a SQLAlchemy table definition. Use mapped_column() in Annotated to mark fields that should become database columns:

from typing import Annotated
from uuid import UUID, uuid4
from pydantic import Field
from sqlalchemy.orm import mapped_column
from sqlcrucible import SQLCrucibleBaseModel

class Artist(SQLCrucibleBaseModel):
    __sqlalchemy_params__ = {"__tablename__": "artist"}

    id: Annotated[UUID, mapped_column(primary_key=True)] = Field(default_factory=uuid4)
    name: Annotated[str, mapped_column()]

Fields without mapped_column() are Pydantic-only and won't appear in the database.

Sharing Metadata Across Entities

When defining multiple entities, use a custom base class to share a MetaData instance:

from sqlalchemy import MetaData

class BaseEntity(SQLCrucibleBaseModel):
    __sqlalchemy_params__ = {"__abstract__": True, "metadata": MetaData()}

class Artist(BaseEntity):
    __sqlalchemy_params__ = {"__tablename__": "artist"}
    id: Annotated[UUID, mapped_column(primary_key=True)] = Field(default_factory=uuid4)
    name: str

class Album(BaseEntity):
    __sqlalchemy_params__ = {"__tablename__": "album"}
    id: Annotated[UUID, mapped_column(primary_key=True)] = Field(default_factory=uuid4)
    title: str

Configuring with __sqlalchemy_params__

The __sqlalchemy_params__ dictionary is placed directly into the generated SQLAlchemy model's class namespace. This means you can use it for any class-level attribute SQLAlchemy expects:

Key Purpose
__tablename__ The database table name
__table_args__ Table-level constraints, indexes, etc.
__mapper_args__ Mapper configuration (polymorphism, eager loading, etc.)
__abstract__ Mark as abstract base (no table created)
metadata Custom MetaData instance

Adding SQLAlchemy-Only Columns

You can use __sqlalchemy_params__ to add columns that exist only on the SQLAlchemy model:

from sqlalchemy import Column, String, Index
from sqlalchemy.orm import mapped_column

class User(SQLCrucibleBaseModel):
    __sqlalchemy_params__ = {
        "__tablename__": "user",
        # Add a column only to the SQLAlchemy model
        "legacy_id": Column(String(50), nullable=True),
        # Add table-level constraints
        "__table_args__": (
            Index("ix_user_email", "email"),
        ),
    }

    id: Annotated[UUID, mapped_column(primary_key=True)] = Field(default_factory=uuid4)
    email: str

The legacy_id column exists in the database and on SAType[User], but isn't part of the User Pydantic model — useful for database-only fields, migration artifacts, or columns managed by triggers.