Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Create Pydantic v2 base schemas for request validation and response serialization in FastAPI
FastAPI Core Schemas
Overview
This skill covers creating base Pydantic v2 schemas for consistent request/response handling across the application.
Create core/schemas.py
Create src/app/core/schemas.py:
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, ConfigDict
classBaseSchema(BaseModel):
"""
Base schema for all Pydantic models.
Configured with:
- from_attributes: Enables ORM mode (read from SQLAlchemy models)
- populate_by_name: Allow using field names or aliases
- str_strip_whitespace: Strip whitespace from string fields
- validate_default: Validate default values
"""
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True,
str_strip_whitespace=True,
validate_default=True,
)
classBaseCreateSchema(BaseSchema):
"""
Base schema for create operations.
Does NOT include id, timestamps, or deleted_at.
Only fields that the client provides when creating a resource.
"""passclass ():
():
: UUID
created_at: datetime
updated_at: datetime
():
deleted_at: datetime | =
BaseUpdateSchema
BaseSchema
"""
Base schema for update operations.
All fields should be Optional to support partial updates (PATCH).
Does NOT include id, timestamps, or deleted_at.
"""
pass
class
BaseResponseSchema
BaseSchema
"""
Base schema for response serialization.
Includes:
- id: UUID primary key
- created_at: Creation timestamp
- updated_at: Last update timestamp
"""
id
class
BaseResponseWithDeletedSchema
BaseResponseSchema
"""
Response schema that includes soft delete information.
Use when the API needs to return deleted_at field,
such as admin endpoints or trash/archive views.
"""
None
None
Usage Example
When creating entity schemas, inherit from the base schemas:
# src/app/items/schemas.pyfrom uuid import UUID
from pydantic import Field
from app.core.schemas import (
BaseCreateSchema,
BaseUpdateSchema,
BaseResponseSchema,
)
classItemCreate(BaseCreateSchema):
"""Schema for creating an item."""
name: str = Field(..., min_length=1, max_length=255)
description: str | None = Field(default=None, max_length=5000)
category_id: UUID
classItemUpdate(BaseUpdateSchema):
"""Schema for updating an item. All fields optional for PATCH."""
name: str | None = Field(default=None, min_length=1, max_length=255)
description: str | None = Field(default=None, max_length=5000)
category_id: UUID | None = NoneclassItemResponse(BaseResponseSchema):
"""Schema for item responses."""
name: str
description: str | None
category_id: UUID
Pydantic v2 Configuration Options
Option
Purpose
Default
from_attributes
Read data from ORM model attributes
False
populate_by_name
Allow field name or alias
False
str_strip_whitespace
Strip whitespace from strings
False
validate_default
Validate default values
False
strict
Strict type coercion
False
extra
Handle extra fields: "ignore", "forbid", "allow"
"ignore"
Field Validation
from pydantic import Field, field_validator, model_validator
classItemCreate(BaseCreateSchema):
name: str = Field(
..., # Required
min_length=1,
max_length=255,
description="Item name",
examples=["My Item"],
)
price: float = Field(
...,
gt=0, # Greater than 0
le=1000000, # Less than or equal to
description="Item price in USD",
)
tags: list[str] = Field(
default_factory=list,
max_length=10, # Max 10 tags
)
@field_validator("name") @classmethoddefvalidate_name(cls, v: str) -> str:
"""Custom name validation."""if v.lower() == "test":
raise ValueError("Name cannot be 'test'")
return v.title()
@model_validator(mode="after")defvalidate_model(self) -> "ItemCreate":
"""Cross-field validation."""ifself.price > 100andnotself.tags:
raise ValueError("Expensive items must have tags")
returnself
from typing importGeneric, TypeVar
from pydantic import BaseModel
T = TypeVar("T")
classListResponse(BaseModel, Generic[T]):
"""Generic list response with metadata."""
items: list[T]
total: int
model_config = ConfigDict(from_attributes=True)
# Usage:# ListResponse[ItemResponse]
Integration with fastapi-pagination
When using fastapi-pagination, you don't need custom list schemas. The library provides Page[T]:
from pydantic import computed_field
classItemResponse(BaseResponseSchema):
name: str
price: float
quantity: int @computed_field @propertydeftotal_value(self) -> float:
"""Computed field for total value."""returnself.price * self.quantity
Serialization Aliases
from pydantic import Field
classItemResponse(BaseResponseSchema):
internal_id: str = Field(serialization_alias="id")
item_name: str = Field(serialization_alias="name")