원클릭으로
fastapi-core-models
Create SQLAlchemy base model with UUID, timestamp, and soft delete mixins for FastAPI
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Create SQLAlchemy base model with UUID, timestamp, and soft delete mixins for FastAPI
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Configure Alembic for async SQLAlchemy migrations with PostgreSQL
Create FastAPI application factory with lifespan, middleware, pagination, and router configuration
Implement session-based authentication in FastAPI applications. Use when building login/logout flows, protecting endpoints with auth dependencies, creating user models with password hashing, managing sessions in database, or implementing auth exceptions. Covers HTTPBearer token validation, Argon2 password hashing, session repository/service patterns, and route protection with dependency injection.
Overview and guidelines for FastAPI 3-layer architecture with async SQLAlchemy, Pydantic v2, and best practices
Create abstract base repository interface with CRUD, pagination, filtering, bulk operations, and soft delete
Create Pydantic v2 base schemas for request validation and response serialization in FastAPI
| name | fastapi-core-models |
| description | Create SQLAlchemy base model with UUID, timestamp, and soft delete mixins for FastAPI |
This skill covers creating the base SQLAlchemy model and reusable mixins for UUID primary keys, timestamps, and soft delete functionality.
Create src/app/core/models.py:
from datetime import datetime
from uuid import UUID, uuid4
from sqlalchemy import DateTime, func
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column
from sqlalchemy.ext.asyncio import AsyncAttrs
class Base(AsyncAttrs, DeclarativeBase):
"""
Base class for all SQLAlchemy models.
Includes AsyncAttrs for proper async lazy loading support.
All models should inherit from this class.
"""
@declared_attr.directive
def __tablename__(cls) -> str:
"""
Generate table name from class name.
Converts CamelCase to snake_case and pluralizes.
Example: UserProfile -> user_profiles
"""
import re
name = re.sub(r"(?<!^)(?=[A-Z])", "_", cls.__name__).lower()
return f"{name}s"
class UUIDMixin:
"""
Mixin that adds a UUID primary key.
Uses PostgreSQL's native UUID type for optimal storage and indexing.
Generates UUID4 by default.
"""
id: Mapped[UUID] = mapped_column(
PG_UUID(as_uuid=True),
primary_key=True,
default=uuid4,
sort_order=-100, # Ensure id appears first in table
)
class TimestampMixin:
"""
Mixin that adds created_at and updated_at timestamps.
- created_at: Set once when record is created (server-side default)
- updated_at: Updated automatically on every modification
All timestamps are timezone-aware UTC.
"""
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
nullable=False,
sort_order=100,
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
nullable=False,
sort_order=101,
)
class SoftDeleteMixin:
"""
Mixin that adds soft delete functionality.
- deleted_at: NULL means not deleted, timestamp means deleted
- Records are never physically deleted, only marked
Repositories should filter out soft-deleted records by default.
"""
deleted_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True),
nullable=True,
default=None,
index=True, # Index for efficient filtering
sort_order=102,
)
@property
def is_deleted(self) -> bool:
"""Check if the record is soft deleted."""
return self.deleted_at is not None
When creating entity models, combine the mixins:
# src/app/items/models.py
from sqlalchemy import String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.models import Base, UUIDMixin, TimestampMixin, SoftDeleteMixin
class Item(UUIDMixin, TimestampMixin, SoftDeleteMixin, Base):
"""Item model with UUID, timestamps, and soft delete."""
__tablename__ = "items" # Explicit table name (optional)
name: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
The above model generates:
CREATE TABLE items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX ix_items_deleted_at ON items (deleted_at);
Always use this order when inheriting:
class MyModel(UUIDMixin, TimestampMixin, SoftDeleteMixin, Base):
This ensures:
id appears first (sort_order=-100)SQLAlchemy 2.0 uses Mapped[] for all column definitions:
# Required field
name: Mapped[str] = mapped_column(String(255))
# Optional field
description: Mapped[str | None] = mapped_column(Text, nullable=True)
# With default
is_active: Mapped[bool] = mapped_column(default=True)
# Foreign key
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id"))
from sqlalchemy import (
String, # VARCHAR(n)
Text, # TEXT (unlimited)
Integer, # INTEGER
BigInteger, # BIGINT
Float, # FLOAT
Numeric, # DECIMAL(precision, scale)
Boolean, # BOOLEAN
DateTime, # TIMESTAMP
Date, # DATE
JSON, # JSONB (PostgreSQL)
Enum, # ENUM type
)
from sqlalchemy.dialects.postgresql import (
UUID, # UUID
ARRAY, # ARRAY type
JSONB, # JSONB (explicit)
)
from sqlalchemy.orm import relationship
class User(UUIDMixin, TimestampMixin, Base):
__tablename__ = "users"
name: Mapped[str] = mapped_column(String(255))
# One-to-many relationship
items: Mapped[list["Item"]] = relationship(
back_populates="user",
lazy="selectin", # Async-safe loading
)
class Item(UUIDMixin, TimestampMixin, Base):
__tablename__ = "items"
name: Mapped[str] = mapped_column(String(255))
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id"))
# Many-to-one relationship
user: Mapped["User"] = relationship(back_populates="items")
For async contexts, use these loading strategies:
| Strategy | Use Case |
|---|---|
lazy="selectin" | Load related objects in separate SELECT |
lazy="joined" | Load with JOIN (use sparingly) |
lazy="raise" | Raise error if accessed (explicit loading only) |
Avoid lazy="select" (default) - it triggers implicit I/O in async context.
from sqlalchemy import Index
class Item(UUIDMixin, TimestampMixin, Base):
__tablename__ = "items"
name: Mapped[str] = mapped_column(String(255), index=True)
status: Mapped[str] = mapped_column(String(50))
category: Mapped[str] = mapped_column(String(100))
# Composite index
__table_args__ = (
Index("ix_items_status_category", "status", "category"),
)