jpskill.com
🛠️ 開発・MCP コミュニティ

py-pydantic-patterns

Pydantic v2 patterns for validation and serialization. Use when creating schemas, validating data, or working with request/response models.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o py-pydantic-patterns.zip https://jpskill.com/download/17846.zip && unzip -o py-pydantic-patterns.zip && rm py-pydantic-patterns.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17846.zip -OutFile "$d\py-pydantic-patterns.zip"; Expand-Archive "$d\py-pydantic-patterns.zip" -DestinationPath $d -Force; ri "$d\py-pydantic-patterns.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して py-pydantic-patterns.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → py-pydantic-patterns フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Pydantic v2 パターン

問題提起

Pydantic v2 は v1 から大幅な API の変更があります。このコードベースは v2 を使用しています。誤ったパターンは、検証の失敗、シリアライゼーションのバグ、およびフロントエンドの統合の問題を引き起こします。


パターン: v1 から v2 への移行

知っておくべき重要な変更点:

# ❌ v1 (OLD - 使用しないでください)
from pydantic import validator
class Model(BaseModel):
    class Config:
        orm_mode = True

    @validator("email")
    def validate_email(cls, v):
        return v.lower()

    def dict(self):
        ...

# ✅ v2 (CURRENT)
from pydantic import field_validator, ConfigDict
class Model(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str) -> str:
        return v.lower()

    def model_dump(self):
        ...

クイックリファレンス:

v1 v2
class Config model_config = ConfigDict(...)
orm_mode = True from_attributes=True
.dict() .model_dump()
.json() .model_dump_json()
@validator @field_validator
@root_validator @model_validator
parse_obj() model_validate()
update_forward_refs() model_rebuild()

パターン: フィールドバリデーター

from pydantic import BaseModel, field_validator, ValidationInfo

class AssessmentCreate(BaseModel):
    title: str
    skill_areas: list[str]
    max_score: int

    # 単一フィールドバリデーター
    @field_validator("title")
    @classmethod
    def title_not_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("Title cannot be empty")
        return v.strip()

    # 他のフィールドへのアクセスを持つバリデーター
    @field_validator("max_score")
    @classmethod
    def validate_max_score(cls, v: int, info: ValidationInfo) -> int:
        if v < 1:
            raise ValueError("Max score must be positive")
        return v

    # 複数のフィールド
    @field_validator("skill_areas")
    @classmethod
    def validate_skill_areas(cls, v: list[str]) -> list[str]:
        valid = {"fundamentals", "advanced", "strategy"}
        for area in v:
            if area not in valid:
                raise ValueError(f"Invalid skill area: {area}")
        return v

パターン: モデルバリデーター

from pydantic import BaseModel, model_validator

class DateRange(BaseModel):
    start_date: datetime
    end_date: datetime

    # 検証前 (生の入力)
    @model_validator(mode="before")
    @classmethod
    def parse_dates(cls, data: dict) -> dict:
        # 文字列の日付を処理する
        if isinstance(data.get("start_date"), str):
            data["start_date"] = datetime.fromisoformat(data["start_date"])
        return data

    # 検証後 (検証済みのモデル)
    @model_validator(mode="after")
    def validate_range(self) -> "DateRange":
        if self.end_date < self.start_date:
            raise ValueError("end_date must be after start_date")
        return self

パターン: モデル構成

from pydantic import BaseModel, ConfigDict

class UserRead(BaseModel):
    # モデルの動作を構成する
    model_config = ConfigDict(
        from_attributes=True,      # ORMオブジェクトからの許可
        str_strip_whitespace=True, # 文字列をトリムする
        str_min_length=1,          # デフォルトで空の文字列は許可しない
        validate_default=True,     # デフォルト値を検証する
        extra="forbid",            # 余分なフィールドでエラーを発生させる
        frozen=False,              # ミューテーションを許可する
    )

    id: UUID
    email: str
    created_at: datetime

# SQLModelオブジェクトでの使用
user_db = await session.get(User, user_id)
user_read = UserRead.model_validate(user_db)  # from_attributesのため動作する

パターン: フィールド定義

from pydantic import BaseModel, Field
from typing import Annotated

class AssessmentCreate(BaseModel):
    # 基本的な制約
    title: str = Field(min_length=1, max_length=200)
    score: int = Field(ge=0, le=100)  # 0 <= score <= 100
    rating: float = Field(gt=0, lt=5.5)  # 0 < rating < 5.5

    # 説明付き (OpenAPIに表示される)
    skill_areas: list[str] = Field(
        min_length=1,
        description="List of skill areas to assess",
        examples=[["fundamentals", "strategy"]],
    )

    # デフォルト値を持つオプション
    notes: str | None = Field(default=None, max_length=1000)

    # 計算されたデフォルト値
    created_at: datetime = Field(default_factory=datetime.utcnow)

# 制約付きの再利用可能な型
PositiveInt = Annotated[int, Field(gt=0)]
Rating = Annotated[float, Field(ge=1.0, le=5.5)]

class Result(BaseModel):
    count: PositiveInt
    rating: Rating

パターン: 判別されたユニオン

問題: 型がフィールドに依存するポリモーフィックなレスポンス。

from pydantic import BaseModel, Field
from typing import Literal, Union
from typing_extensions import Annotated

class TextQuestion(BaseModel):
    type: Literal["text"] = "text"
    prompt: str
    max_length: int

class MultipleChoiceQuestion(BaseModel):
    type: Literal["multiple_choice"] = "multiple_choice"
    prompt: str
    options: list[str]

class RatingQuestion(BaseModel):
    type: Literal["rating"] = "rating"
    prompt: str
    min_value: int
    max_value: int

# 判別されたユニオン - Pydanticは'type'フィールドを使用してクラスを決定する
Question = Annotated[
    Union[TextQuestion, MultipleChoiceQuestion, RatingQuestion],
    Field(discriminator="type"),
]

class Assessment(BaseModel):
    questions: list[Question]

# Pydanticは自動的に正しい型にデシリアライズする
data = {
    "questions": [
        {"type": "text", "prompt": "Describe...", "max_length": 500},
        {"type": "rating", "prompt": "Rate...", "min_value": 1, "max_value": 5},
    ]
}
assessment = Assessment.model_validate(data)
# assessment.questions[0] は TextQuestion
# assessment.questions[1] は RatingQuestion

パターン: カスタム型

from pydantic
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Pydantic v2 Patterns

Problem Statement

Pydantic v2 has significant API changes from v1. This codebase uses v2. Wrong patterns cause validation failures, serialization bugs, and frontend integration issues.


Pattern: v1 to v2 Migration

Critical changes to know:

# ❌ v1 (OLD - don't use)
from pydantic import validator
class Model(BaseModel):
    class Config:
        orm_mode = True

    @validator("email")
    def validate_email(cls, v):
        return v.lower()

    def dict(self):
        ...

# ✅ v2 (CURRENT)
from pydantic import field_validator, ConfigDict
class Model(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str) -> str:
        return v.lower()

    def model_dump(self):
        ...

Quick reference:

v1 v2
class Config model_config = ConfigDict(...)
orm_mode = True from_attributes=True
.dict() .model_dump()
.json() .model_dump_json()
@validator @field_validator
@root_validator @model_validator
parse_obj() model_validate()
update_forward_refs() model_rebuild()

Pattern: Field Validators

from pydantic import BaseModel, field_validator, ValidationInfo

class AssessmentCreate(BaseModel):
    title: str
    skill_areas: list[str]
    max_score: int

    # Single field validator
    @field_validator("title")
    @classmethod
    def title_not_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("Title cannot be empty")
        return v.strip()

    # Validator with access to other fields
    @field_validator("max_score")
    @classmethod
    def validate_max_score(cls, v: int, info: ValidationInfo) -> int:
        if v < 1:
            raise ValueError("Max score must be positive")
        return v

    # Multiple fields
    @field_validator("skill_areas")
    @classmethod
    def validate_skill_areas(cls, v: list[str]) -> list[str]:
        valid = {"fundamentals", "advanced", "strategy"}
        for area in v:
            if area not in valid:
                raise ValueError(f"Invalid skill area: {area}")
        return v

Pattern: Model Validators

from pydantic import BaseModel, model_validator

class DateRange(BaseModel):
    start_date: datetime
    end_date: datetime

    # Before validation (raw input)
    @model_validator(mode="before")
    @classmethod
    def parse_dates(cls, data: dict) -> dict:
        # Handle string dates
        if isinstance(data.get("start_date"), str):
            data["start_date"] = datetime.fromisoformat(data["start_date"])
        return data

    # After validation (validated model)
    @model_validator(mode="after")
    def validate_range(self) -> "DateRange":
        if self.end_date < self.start_date:
            raise ValueError("end_date must be after start_date")
        return self

Pattern: Model Configuration

from pydantic import BaseModel, ConfigDict

class UserRead(BaseModel):
    # Configure model behavior
    model_config = ConfigDict(
        from_attributes=True,      # Allow from ORM objects
        str_strip_whitespace=True, # Strip strings
        str_min_length=1,          # No empty strings by default
        validate_default=True,     # Validate default values
        extra="forbid",            # Error on extra fields
        frozen=False,              # Allow mutation
    )

    id: UUID
    email: str
    created_at: datetime

# Usage with SQLModel objects
user_db = await session.get(User, user_id)
user_read = UserRead.model_validate(user_db)  # Works due to from_attributes

Pattern: Field Definitions

from pydantic import BaseModel, Field
from typing import Annotated

class AssessmentCreate(BaseModel):
    # Basic constraints
    title: str = Field(min_length=1, max_length=200)
    score: int = Field(ge=0, le=100)  # 0 <= score <= 100
    rating: float = Field(gt=0, lt=5.5)  # 0 < rating < 5.5

    # With description (shows in OpenAPI)
    skill_areas: list[str] = Field(
        min_length=1,
        description="List of skill areas to assess",
        examples=[["fundamentals", "strategy"]],
    )

    # Optional with default
    notes: str | None = Field(default=None, max_length=1000)

    # Computed default
    created_at: datetime = Field(default_factory=datetime.utcnow)

# Reusable type with constraints
PositiveInt = Annotated[int, Field(gt=0)]
Rating = Annotated[float, Field(ge=1.0, le=5.5)]

class Result(BaseModel):
    count: PositiveInt
    rating: Rating

Pattern: Discriminated Unions

Problem: Polymorphic responses where type depends on a field.

from pydantic import BaseModel, Field
from typing import Literal, Union
from typing_extensions import Annotated

class TextQuestion(BaseModel):
    type: Literal["text"] = "text"
    prompt: str
    max_length: int

class MultipleChoiceQuestion(BaseModel):
    type: Literal["multiple_choice"] = "multiple_choice"
    prompt: str
    options: list[str]

class RatingQuestion(BaseModel):
    type: Literal["rating"] = "rating"
    prompt: str
    min_value: int
    max_value: int

# Discriminated union - Pydantic uses 'type' field to determine class
Question = Annotated[
    Union[TextQuestion, MultipleChoiceQuestion, RatingQuestion],
    Field(discriminator="type"),
]

class Assessment(BaseModel):
    questions: list[Question]

# Pydantic automatically deserializes to correct type
data = {
    "questions": [
        {"type": "text", "prompt": "Describe...", "max_length": 500},
        {"type": "rating", "prompt": "Rate...", "min_value": 1, "max_value": 5},
    ]
}
assessment = Assessment.model_validate(data)
# assessment.questions[0] is TextQuestion
# assessment.questions[1] is RatingQuestion

Pattern: Custom Types

from pydantic import BaseModel, AfterValidator, BeforeValidator
from typing import Annotated
import re

# Email normalization
def normalize_email(v: str) -> str:
    return v.lower().strip()

Email = Annotated[str, AfterValidator(normalize_email)]

# Phone validation
def validate_phone(v: str) -> str:
    cleaned = re.sub(r"[^\d+]", "", v)
    if not re.match(r"^\+?1?\d{10,14}$", cleaned):
        raise ValueError("Invalid phone number")
    return cleaned

PhoneNumber = Annotated[str, BeforeValidator(validate_phone)]

# UUID from string
def parse_uuid(v: str | UUID) -> UUID:
    if isinstance(v, str):
        return UUID(v)
    return v

UUIDStr = Annotated[UUID, BeforeValidator(parse_uuid)]

class User(BaseModel):
    email: Email
    phone: PhoneNumber | None = None
    id: UUIDStr

Pattern: Serialization Control

from pydantic import BaseModel, field_serializer, computed_field

class User(BaseModel):
    id: UUID
    email: str
    created_at: datetime

    # Custom serialization
    @field_serializer("created_at")
    def serialize_datetime(self, dt: datetime) -> str:
        return dt.isoformat()

    @field_serializer("id")
    def serialize_uuid(self, id: UUID) -> str:
        return str(id)

    # Computed field (included in serialization)
    @computed_field
    @property
    def display_name(self) -> str:
        return self.email.split("@")[0]

# Serialization options
user.model_dump()                          # Full dict
user.model_dump(exclude={"created_at"})    # Exclude fields
user.model_dump(include={"id", "email"})   # Include only
user.model_dump(exclude_none=True)         # Skip None values
user.model_dump(by_alias=True)             # Use field aliases
user.model_dump_json()                     # JSON string

Pattern: Schema Inheritance

class UserBase(BaseModel):
    email: str
    name: str

class UserCreate(UserBase):
    password: str  # Only for creation

class UserRead(UserBase):
    id: UUID
    created_at: datetime

    model_config = ConfigDict(from_attributes=True)

class UserUpdate(BaseModel):
    # All optional for partial updates
    email: str | None = None
    name: str | None = None
    password: str | None = None

Common Issues

Issue Likely Cause Solution
"X is not a valid dict" Using .dict() (v1) Use .model_dump()
"Unable to parse ORM object" Missing from_attributes Add ConfigDict(from_attributes=True)
"@validator not recognized" v1 decorator Use @field_validator with @classmethod
"Extra fields not permitted" extra="forbid" Remove extra fields or change config
Validation not running Default value not validated Add validate_default=True

Detection Commands

# Find v1 patterns
grep -rn "class Config:" --include="*.py"
grep -rn "@validator" --include="*.py"
grep -rn "\.dict()" --include="*.py"
grep -rn "orm_mode" --include="*.py"