py-pydantic-patterns
Pydantic v2 patterns for validation and serialization. Use when creating schemas, validating data, or working with request/response models.
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
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
$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. 下の青いボタンを押して
py-pydantic-patterns.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
py-pydantic-patternsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
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"