Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 0品質スコア 50/100

pydantic

PydanticのRust製コアによる高速バリデーション機能を活用し、型ヒントとランタイム型チェックを用いたPythonデータ検証を行います。FastAPI・Djangoとの連携やコンフィグ管理など、型安全が求められる場面で活躍します。

description の原文を見る

Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation in FastAPI, Django, and configuration management.

SKILL.md 本文

Pydantic 検証スキル

概要

型ヒントと実行時型チェックを使用した Python データ検証。Pydantic v2 の Rust 駆動コアにより高性能検証を実現します。

使用場面

  • API リクエスト/レスポンス検証 (FastAPI、Django)
  • 設定と構成管理 (環境変数、設定ファイル)
  • ORM モデル検証 (SQLAlchemy 統合)
  • データ解析とシリアライゼーション (JSON、辞書、カスタム形式)
  • 自動検証機能付きの型安全なデータクラス
  • 型安全性を備えた CLI 引数解析

クイックスタート

from pydantic import BaseModel, Field, EmailStr
from datetime import datetime

class User(BaseModel):
    id: int
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    created_at: datetime = Field(default_factory=datetime.now)
    is_active: bool = True

# データの検証
user = User(id=1, name="Alice", email="alice@example.com")
print(user.model_dump())  # {'id': 1, 'name': 'Alice', ...}

# 自動型変換
user2 = User(id="2", name="Bob", email="bob@example.com")
assert user2.id == 2  # 文字列 "2" が int に変換される

# 検証エラー
try:
    User(id=3, name="", email="invalid")
except ValidationError as e:
    print(e.errors())

コア概念

BaseModel の基礎

from pydantic import BaseModel, ConfigDict

class Product(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        use_enum_values=True,
        arbitrary_types_allowed=False
    )

    name: str
    price: float
    quantity: int = 0

# 使用例
product = Product(name="  Widget  ", price=19.99)
assert product.name == "Widget"  # 空白が削除される

# 割り当て時に検証
product.price = "29.99"  # 自動的に float に変換

フィールド設定

from pydantic import Field, field_validator
from typing import Annotated

class Item(BaseModel):
    # フィールド制約
    sku: str = Field(pattern=r'^[A-Z]{3}-\d{4}$')
    price: float = Field(gt=0, le=10000)
    stock: int = Field(ge=0, default=0)

    # Annotated 型 (Pydantic v2)
    quantity: Annotated[int, Field(ge=1, le=100)]

    # 説明と例
    description: str = Field(
        ...,
        description="Product description",
        examples=["High-quality widget"]
    )

    # 廃止予定フィールド
    old_field: str | None = Field(None, deprecated=True)

    @field_validator('sku')
    @classmethod
    def validate_sku(cls, v: str) -> str:
        if not v.startswith('ABC'):
            raise ValueError('SKU must start with ABC')
        return v

Pydantic v2 の改善点

v1 からのマイグレーション

# Pydantic v1
class OldModel(BaseModel):
    class Config:
        validate_assignment = True
        json_encoders = {datetime: lambda v: v.isoformat()}

# Pydantic v2
class NewModel(BaseModel):
    model_config = ConfigDict(
        validate_assignment=True,
        # json_encoders はシリアライザーに置き換わり
    )

    @model_serializer
    def ser_model(self) -> dict:
        return {...}

# 主な変更:
# - .dict() → .model_dump()
# - .json() → .model_dump_json()
# - .parse_obj() → .model_validate()
# - .parse_raw() → .model_validate_json()
# - @validator → @field_validator
# - @root_validator → @model_validator

パフォーマンスの改善

# v2 は Rust コア (pydantic-core) を使用して 5~50 倍高速化
from pydantic import BaseModel
import time

class Data(BaseModel):
    values: list[int]
    names: list[str]

# ベンチマーク
data = {'values': list(range(10000)), 'names': ['item'] * 10000}
start = time.perf_counter()
for _ in range(1000):
    Data.model_validate(data)
elapsed = time.perf_counter() - start
print(f"Validated 1000 iterations in {elapsed:.2f}s")

フィールド型

組み込み型

from pydantic import (
    BaseModel, EmailStr, HttpUrl, UUID4,
    FilePath, DirectoryPath, Json, SecretStr,
    PositiveInt, NegativeFloat, conint, constr
)
from typing import Literal
from pathlib import Path

class Example(BaseModel):
    # メール検証
    email: EmailStr

    # URL 検証
    website: HttpUrl

    # UUID
    id: UUID4

    # ファイルシステムパス
    config_file: FilePath
    data_dir: DirectoryPath

    # JSON 文字列 → パースされたオブジェクト
    metadata: Json[dict[str, str]]

    # シークレット (ログに出力されない)
    api_key: SecretStr

    # 制約型
    age: PositiveInt
    balance: NegativeFloat
    username: constr(min_length=3, max_length=20, pattern=r'^[a-z]+$')
    code: conint(ge=1000, le=9999)

    # リテラル型
    status: Literal['pending', 'approved', 'rejected']

カスタム型

from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import core_schema
from typing import Any

class Color:
    def __init__(self, r: int, g: int, b: int):
        self.r, self.g, self.b = r, g, b

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.no_info_after_validator_function(
            cls.validate,
            core_schema.str_schema()
        )

    @classmethod
    def validate(cls, v: str) -> 'Color':
        if not v.startswith('#') or len(v) != 7:
            raise ValueError('Invalid hex color')
        r = int(v[1:3], 16)
        g = int(v[3:5], 16)
        b = int(v[5:7], 16)
        return cls(r, g, b)

class Design(BaseModel):
    primary_color: Color

# 使用例
design = Design(primary_color='#FF5733')
assert design.primary_color.r == 255

バリデーター

フィールドバリデーター

from pydantic import field_validator, model_validator

class Account(BaseModel):
    username: str
    password: str
    password_confirm: str

    @field_validator('username')
    @classmethod
    def username_alphanumeric(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError('must be alphanumeric')
        return v

    @field_validator('password')
    @classmethod
    def password_strong(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('must be at least 8 characters')
        if not any(c.isupper() for c in v):
            raise ValueError('must contain uppercase letter')
        return v

    # 複数フィールドの検証
    @field_validator('username', 'password')
    @classmethod
    def not_empty(cls, v: str) -> str:
        if not v or not v.strip():
            raise ValueError('must not be empty')
        return v.strip()

モデルバリデーター

from pydantic import model_validator
from typing import Self

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

    @model_validator(mode='after')
    def check_dates(self) -> Self:
        if self.end_date < self.start_date:
            raise ValueError('end_date must be after start_date')
        return self

class Order(BaseModel):
    items: list[str]
    total: float
    discount: float = 0

    @model_validator(mode='before')
    @classmethod
    def calculate_total(cls, data: dict) -> dict:
        # 検証前の前処理
        if isinstance(data, dict) and 'total' not in data:
            data['total'] = len(data.get('items', [])) * 10.0
        return data

ルートバリデーター (Wrap)

from pydantic import model_validator, ValidationInfo

class Config(BaseModel):
    env: Literal['dev', 'prod']
    debug: bool = False

    @model_validator(mode='wrap')
    @classmethod
    def validate_config(cls, values: Any, handler, info: ValidationInfo):
        # デフォルト検証を実行
        result = handler(values)

        # 検証後のロジック
        if result.env == 'prod' and result.debug:
            raise ValueError('debug cannot be True in production')

        return result

型変換と Strict モード

from pydantic import BaseModel, ConfigDict, ValidationError

# 型変換モード (デフォルト)
class CoerciveModel(BaseModel):
    count: int
    price: float

data = CoerciveModel(count="42", price="19.99")
assert data.count == 42  # 文字列 → int
assert data.price == 19.99  # 文字列 → float

# Strict モード
class StrictModel(BaseModel):
    model_config = ConfigDict(strict=True)

    count: int
    price: float

try:
    StrictModel(count="42", price="19.99")  # ValidationError が発生
except ValidationError as e:
    print("Strict mode: no coercion allowed")

# フィールド単位の strict モード
class MixedModel(BaseModel):
    flexible: int  # 型変換を許可
    strict: Annotated[int, Field(strict=True)]  # 型変換を不許可

MixedModel(flexible="1", strict=2)  # OK
# MixedModel(flexible="1", strict="2")  # ValidationError

ネストされたモデルと再帰的型

from pydantic import BaseModel
from typing import ForwardRef

# ネストされたモデル
class Address(BaseModel):
    street: str
    city: str
    country: str

class Company(BaseModel):
    name: str
    address: Address

company = Company(
    name="ACME Corp",
    address={'street': '123 Main St', 'city': 'NYC', 'country': 'USA'}
)

# 再帰的型 (ツリー構造)
class TreeNode(BaseModel):
    value: int
    children: list['TreeNode'] = []

TreeNode.model_rebuild()  # 前方参照に必要

tree = TreeNode(
    value=1,
    children=[
        TreeNode(value=2, children=[TreeNode(value=4)]),
        TreeNode(value=3)
    ]
)

# Self 参照を持つ ForwardRef
class Category(BaseModel):
    name: str
    parent: 'Category | None' = None
    subcategories: list['Category'] = []

Category.model_rebuild()

ジェネリックモデル

from pydantic import BaseModel
from typing import Generic, TypeVar

T = TypeVar('T')

class Response(BaseModel, Generic[T]):
    success: bool
    data: T
    message: str = ''

class User(BaseModel):
    id: int
    name: str

# 具体的な型での使用
user_response = Response[User](
    success=True,
    data=User(id=1, name='Alice')
)

# リスト応答
list_response = Response[list[User]](
    success=True,
    data=[User(id=1, name='Alice'), User(id=2, name='Bob')]
)

# ジェネリックリポジトリパターン
class Repository(BaseModel, Generic[T]):
    items: list[T]

    def add(self, item: T) -> None:
        self.items.append(item)

user_repo = Repository[User](items=[])
user_repo.add(User(id=1, name='Alice'))

シリアライゼーション

Model Dump

from pydantic import BaseModel, Field, field_serializer

class Article(BaseModel):
    title: str
    content: str
    tags: list[str]
    metadata: dict[str, Any] = {}

    # シリアライゼーションのカスタマイズ
    @field_serializer('tags')
    def serialize_tags(self, tags: list[str]) -> str:
        return ','.join(tags)

article = Article(
    title='Pydantic Guide',
    content='...',
    tags=['python', 'validation']
)

# 辞書にダンプ
data = article.model_dump()
# {'title': 'Pydantic Guide', 'tags': 'python,validation', ...}

# フィールドを除外
data = article.model_dump(exclude={'metadata'})

# 特定フィールドのみ含める
data = article.model_dump(include={'title', 'tags'})

# 未設定フィールドを除外
article2 = Article(title='Test', content='...', tags=[])
data = article2.model_dump(exclude_unset=True)  # metadata は除外される

# エイリアスで出力
class AliasModel(BaseModel):
    internal_name: str = Field(alias='externalName')

model = AliasModel(externalName='value')
model.model_dump(by_alias=True)  # {'externalName': 'value'}

JSON シリアライゼーション

from datetime import datetime
from pydantic import BaseModel, field_serializer

class Event(BaseModel):
    name: str
    timestamp: datetime

    @field_serializer('timestamp')
    def serialize_dt(self, dt: datetime) -> str:
        return dt.isoformat()

event = Event(name='Deploy', timestamp=datetime.now())

# JSON 文字列にダンプ
json_str = event.model_dump_json()
# '{"name":"Deploy","timestamp":"2025-11-30T..."}'

# 見栄え良く出力
json_str = event.model_dump_json(indent=2)

# JSON からパース
event2 = Event.model_validate_json(json_str)

カスタムシリアライザー

from pydantic import model_serializer

class User(BaseModel):
    id: int
    username: str
    password: SecretStr

    @model_serializer
    def ser_model(self) -> dict[str, Any]:
        return {
            'id': self.id,
            'username': self.username,
            # パスワードは絶対にシリアライズしない
        }

user = User(id=1, username='alice', password='secret123')
assert 'password' not in user.model_dump()

設定管理

BaseSettings

from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field

class AppSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file='.env',
        env_file_encoding='utf-8',
        env_prefix='APP_',
        case_sensitive=False
    )

    # 環境変数
    database_url: str
    redis_url: str = 'redis://localhost:6379'
    secret_key: SecretStr
    debug: bool = False

    # ネストされた設定
    class SMTPSettings(BaseModel):
        host: str
        port: int = 587
        username: str
        password: SecretStr

    smtp: SMTPSettings

# 環境変数から読み込み:
# APP_DATABASE_URL, APP_REDIS_URL, APP_SECRET_KEY, APP_DEBUG
# APP_SMTP__HOST, APP_SMTP__PORT, など

settings = AppSettings()

マルチ環境設定

from functools import lru_cache

class Settings(BaseSettings):
    environment: Literal['dev', 'staging', 'prod'] = 'dev'
    database_url: str
    api_key: SecretStr

    model_config = SettingsConfigDict(
        env_file='.env',
        extra='ignore'
    )

    @property
    def is_production(self) -> bool:
        return self.environment == 'prod'

@lru_cache
def get_settings() -> Settings:
    return Settings()

# FastAPI での使用
from fastapi import Depends

@app.get('/config')
def get_config(settings: Settings = Depends(get_settings)):
    return {'env': settings.environment}

FastAPI 統合

リクエスト/レスポンスモデル

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=50)
    email: EmailStr
    password: str = Field(min_length=8)

class UserResponse(BaseModel):
    id: int
    username: str
    email: EmailStr

    model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse)
def create_user(user: UserCreate):
    # FastAPI がリクエストボディを自動検証
    # レスポンスは UserResponse のフィールドのみを返す (password は除外)
    return UserResponse(
        id=1,
        username=user.username,
        email=user.email
    )

クエリパラメータ

from pydantic import BaseModel, Field
from fastapi import Query

class PaginationParams(BaseModel):
    skip: int = Field(0, ge=0)
    limit: int = Field(10, ge=1, le=100)

class SearchParams(BaseModel):
    q: str = Field(..., min_length=1)
    category: str | None = None
    sort_by: Literal['date', 'relevance'] = 'relevance'

@app.get('/search')
def search(params: SearchParams = Query()):
    return {'query': params.q, 'sort': params.sort_by}

レスポンスモデルのカスタマイズ

class DetailedUser(BaseModel):
    id: int
    username: str
    email: EmailStr
    created_at: datetime
    last_login: datetime | None

@app.get('/users/{user_id}', response_model=DetailedUser)
def get_user(user_id: int, include_dates: bool = False):
    user = DetailedUser(
        id=user_id,
        username='alice',
        email='alice@example.com',
        created_at=datetime.now(),
        last_login=None
    )

    if not include_dates:
        return user.model_dump(exclude={'created_at', 'last_login'})
    return user

SQLAlchemy 統合

ORM モデルと Pydantic

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import DeclarativeBase
from pydantic import BaseModel, ConfigDict

class Base(DeclarativeBase):
    pass

# SQLAlchemy ORM モデル
class UserDB(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(50), unique=True)
    email = Column(String(100))
    created_at = Column(DateTime, default=datetime.utcnow)

# 検証用 Pydantic モデル
class UserSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    username: str
    email: EmailStr
    created_at: datetime

# 使用例
from sqlalchemy.orm import Session

def get_user(db: Session, user_id: int) -> UserSchema:
    user = db.query(UserDB).filter(UserDB.id == user_id).first()
    return UserSchema.model_validate(user)  # ORM → Pydantic

ハイブリッドアプローチ

from pydantic import BaseModel

class UserBase(BaseModel):
    username: str
    email: EmailStr

class UserCreate(UserBase):
    password: str

class UserUpdate(BaseModel):
    username: str | None = None
    email: EmailStr | None = None
    password: str | None = None

class UserInDB(UserBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    created_at: datetime
    password_hash: str

# CRUD 操作
def create_user(db: Session, user: UserCreate) -> UserInDB:
    db_user = UserDB(
        username=user.username,
        email=user.email,
        password_hash=hash_password(user.password)
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return UserInDB.model_validate(db_user)

Django 統合

Django モデルの検証

from django.db import models
from pydantic import BaseModel, field_validator

# Django モデル
class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published = models.BooleanField(default=False)

# Pydantic スキーマ
class ArticleSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    title: str = Field(max_length=200)
    content: str
    published: bool = False

    @field_validator('content')
    @classmethod
    def validate_content(cls, v: str) -> str:
        if len(v) < 100:
            raise ValueError('Content too short')
        return v

# Django ビューでの使用
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods

@require_http_methods(['POST'])
def create_article(request):
    try:
        data = ArticleSchema.model_validate_json(request.body)
        article = Article.objects.create(**data.model_dump())
        return JsonResponse({'id': article.id})
    except ValidationError as e:
        return JsonResponse({'errors': e.errors()}, status=400)

計算フィールド

from pydantic import computed_field

class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        return self.width * self.height

    @computed_field
    @property
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

rect = Rectangle(width=10, height=5)
assert rect.area == 50
assert rect.perimeter == 30

# シリアライゼーション時の計算フィールド
data = rect.model_dump()
# {'width': 10.0, 'height': 5.0, 'area': 50.0, 'perimeter': 30.0}

カスタムエラー

from pydantic import BaseModel, field_validator, ValidationError
from pydantic_core import PydanticCustomError

class StrictUser(BaseModel):
    username: str
    age: int

    @field_validator('username')
    @classmethod
    def validate_username(cls, v: str) -> str:
        if len(v) < 3:
            raise PydanticCustomError(
                'username_too_short',
                'Username must be at least 3 characters',
                {'min_length': 3, 'actual_length': len(v)}
            )
        return v

    @field_validator('age')
    @classmethod
    def validate_age(cls, v: int) -> int:
        if v < 18:
            raise PydanticCustomError(
                'underage',
                'User must be at least 18 years old',
                {'age': v, 'minimum_age': 18}
            )
        return v

# カスタムエラー処理
try:
    StrictUser(username='ab', age=16)
except ValidationError as e:
    for error in e.errors():
        print(f"{error['type']}: {error['msg']}")
        print(f"Context: {error.get('ctx')}")

パフォーマンス最適化

v2 Rust コアの利点

# Pydantic v2 は pydantic-core (Rust) を使用:
# - 5~50 倍高速な検証
# - メモリ使用量の削減
# - 改善されたエラーメッセージ
# - 改善された JSON パース

import timeit
from pydantic import BaseModel

class Data(BaseModel):
    values: list[int]
    names: list[str]
    metadata: dict[str, Any]

# ベンチマーク
data_dict = {
    'values': list(range(1000)),
    'names': ['item'] * 1000,
    'metadata': {'key': 'value'}
}

def validate():
    Data.model_validate(data_dict)

time_taken = timeit.timeit(validate, number=10000)
print(f"10000 validations: {time_taken:.2f}s")

最適化テクニック

from pydantic import BaseModel, ConfigDict

class OptimizedModel(BaseModel):
    model_config = ConfigDict(
        # 必要な場合のみ割り当て時に検証
        validate_assignment=False,

        # 内部使用時は検証を無効化
        validate_default=False,

        # メモリ効率のためのスロット使用
        # (Pydantic v2 BaseModel では直接利用不可)
    )

    data: list[int]

# バリデーターの再利用
from functools import lru_cache

@lru_cache(maxsize=128)
def get_validator(model_class):
    return model_class.model_validate

# 一括検証
def validate_bulk(items: list[dict]) -> list[Data]:
    validator = get_validator(Data)
    return [validator(item) for item in items]

JSON Schema の生成

from pydantic import BaseModel, Field

class Product(BaseModel):
    """Product model for catalog"""

    id: int = Field(description="Unique product identifier")
    name: str = Field(description="Product name", examples=["Widget"])
    price: float = Field(gt=0, description="Price in USD")
    tags: list[str] = Field(default=[], description="Product tags")

# JSON Schema の生成
schema = Product.model_json_schema()
print(json.dumps(schema, indent=2))
# {
#   "title": "Product",
#   "description": "Product model for catalog",
#   "type": "object",
#   "properties": {
#     "id": {"type": "integer", "description": "Unique product identifier"},
#     "name": {"type": "string", "description": "Product name"},
#     ...
#   },
#   "required": ["id", "name", "price"]
# }

# OpenAPI 互換
from fastapi import FastAPI

app = FastAPI()

@app.post('/products')
def create_product(product: Product):
    return product

# FastAPI は Pydantic モデルから自動的に OpenAPI schema を生成

データクラス統合

from pydantic.dataclasses import dataclass
from pydantic import Field

@dataclass
class User:
    id: int
    name: str = Field(min_length=1)
    email: str = Field(pattern=r'.+@.+\..+')

# Pydantic BaseModel のように検証機能を備えて動作
user = User(id=1, name='Alice', email='alice@example.com')

# 構築時の検証
try:
    User(id=2, name='', email='invalid')
except ValidationError as e:
    print(e.errors())

# Pydantic BaseModel に変換
from pydantic import BaseModel

class UserModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    name: str
    email: str

user_model = UserModel.model_validate(user)

テスト戦略

ユニットテストモデル

import pytest
from pydantic import ValidationError

def test_user_validation():
    # 有効なデータ
    user = User(id=1, name='Alice', email='alice@example.com')
    assert user.name == 'Alice'

    # 無効なデータ
    with pytest.raises(ValidationError) as exc_info:
        User(id='invalid', name='Bob', email='bob@example.com')

    errors = exc_info.value.errors()
    assert errors[0]['type'] == 'int_parsing'

def test_user_serialization():
    user = User(id=1, name='Alice', email='alice@example.com')
    data = user.model_dump()

    assert data == {
        'id': 1,
        'name': 'Alice',
        'email': 'alice@example.com'
    }

def test_nested_validation():
    company = Company(
        name='ACME',
        address={'street': '123 Main', 'city': 'NYC', 'country': 'USA'}
    )
    assert company.address.city == 'NYC'

フィクスチャを使用したテスト

@pytest.fixture
def sample_user_data():
    return {
        'id': 1,
        'name': 'Alice',
        'email': 'alice@example.com'
    }

@pytest.fixture
def sample_user(sample_user_data):
    return User(**sample_user_data)

def test_with_fixtures(sample_user):
    assert sample_user.name == 'Alice'

def test_invalid_email(sample_user_data):
    sample_user_data['email'] = 'invalid'
    with pytest.raises(ValidationError):
        User(**sample_user_data)

プロパティベースのテスト

from hypothesis import given, strategies as st

@given(
    id=st.integers(min_value=1),
    name=st.text(min_size=1, max_size=100),
    email=st.emails()
)
def test_user_always_valid(id, name, email):
    user = User(id=id, name=name, email=email)
    assert user.id == id
    assert user.name == name
    assert user.email == email

マイグレーションガイド (v1 → v2)

主な変更点

# v1
from pydantic import BaseModel

class OldModel(BaseModel):
    class Config:
        validate_assignment = True
        arbitrary_types_allowed = True

    # バリデーター
    @validator('field')
    def validate_field(cls, v):
        return v

    @root_validator
    def validate_model(cls, values):
        return values

    # シリアライゼーション
    data = model.dict()
    json_str = model.json()

    # パース
    model = OldModel.parse_obj(data)
    model = OldModel.parse_raw(json_str)

# v2
from pydantic import BaseModel, ConfigDict, field_validator, model_validator

class NewModel(BaseModel):
    model_config = ConfigDict(
        validate_assignment=True,
        arbitrary_types_allowed=True
    )

    # フィールドバリデーター
    @field_validator('field')
    @classmethod
    def validate_field(cls, v):
        return v

    # モデルバリデーター
    @model_validator(mode='after')
    def validate_model(self):
        return self

    # シリアライゼーション
    data = model.model_dump()
    json_str = model.model_dump_json()

    # パース
    model = NewModel.model_validate(data)
    model = NewModel.model_validate_json(json_str)

マイグレーションチェックリスト

  • class Configmodel_config = ConfigDict() に置き換える
  • .dict().model_dump() に更新
  • .json().model_dump_json() に更新
  • .parse_obj().model_validate() に更新
  • .parse_raw().model_validate_json() に更新
  • @validator@field_validator@classmethod に更新
  • @root_validator@model_validator(mode='after') に更新
  • json_encoders@field_serializer に置き換える
  • strict モード動作の変更を確認
  • カスタム型を __get_pydantic_core_schema__ を使用するよう更新

ベストプラクティス

モデル構成

# 用途別にスキーマを分離
class UserBase(BaseModel):
    """共有フィールド"""
    username: str
    email: EmailStr

class UserCreate(UserBase):
    """ユーザー作成用 API リクエスト"""
    password: str

class UserUpdate(BaseModel):
    """ユーザー更新用 API リクエスト (すべてオプション)"""
    username: str | None = None
    email: EmailStr | None = None
    password: str | None = None

class UserInDB(UserBase):
    """データベース表現"""
    model_config = ConfigDict(from_attributes=True)

    id: int
    password_hash: str
    created_at: datetime

class UserResponse(UserBase):
    """API レスポンス (機密データを除外)"""
    id: int
    created_at: datetime

検証のベストプラクティス

# バリデーターではなく Field で制約を使用
class Good(BaseModel):
    age: int = Field(ge=0, le=150)
    email: EmailStr

class Bad(BaseModel):
    age: int
    email: str

    @field_validator('age')
    @classmethod
    def validate_age(cls, v):
        if v < 0 or v > 150:
            raise ValueError('invalid age')
        return v

# 継承ではなく構成を優先
class TimestampMixin(BaseModel):
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)

class User(TimestampMixin):
    username: str
    email: EmailStr

エラー処理

from pydantic import ValidationError

def safe_validate(data: dict) -> User | None:
    try:
        return User.model_validate(data)
    except ValidationError as e:
        # 検証エラーをログに記録
        logger.error(f"Validation failed: {e.errors()}")
        return None

def validate_with_details(data: dict):
    try:
        return User.model_validate(data)
    except ValidationError as e:
        # ユーザーフレンドリーなエラーを返す
        return {
            'success': False,
            'errors': [
                {
                    'field': '.'.join(str(loc) for loc in err['loc']),
                    'message': err['msg'],
                    'type': err['type']
                }
                for err in e.errors()
            ]
        }

一般的なパターン

API レスポンスラッパー

from typing import Generic, TypeVar

T = TypeVar('T')

class APIResponse(BaseModel, Generic[T]):
    success: bool
    data: T | None = None
    error: str | None = None
    metadata: dict[str, Any] = {}

# 使用例
user_response = APIResponse[User](
    success=True,
    data=User(id=1, name='Alice', email='alice@example.com')
)

error_response = APIResponse[User](
    success=False,
    error='User not found'
)

ページネーション

class PaginatedResponse(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    page_size: int

    @computed_field
    @property
    def total_pages(self) -> int:
        return (self.total + self.page_size - 1) // self.page_size

users = PaginatedResponse[User](
    items=[...],
    total=100,
    page=1,
    page_size=10
)
assert users.total_pages == 10

監査フィールド

class AuditMixin(BaseModel):
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)
    created_by: int | None = None
    updated_by: int | None = None

class Document(AuditMixin):
    title: str
    content: str

    @model_validator(mode='before')
    @classmethod
    def update_timestamp(cls, data: dict) -> dict:
        if isinstance(data, dict):
            data['updated_at'] = datetime.utcnow()
        return data

関連スキル

Pydantic を使用する際は、以下の補完的なスキルを検討してください:

  • fastapi-local-dev: Pydantic 統合を備えた FastAPI 開発サーバーパターン
  • sqlalchemy: Pydantic 検証を備えたデータベースモデルの SQLAlchemy ORM パターン
  • django: Pydantic スキーマを備えた Django フレームワーク統合
  • pytest: Pydantic モデルと検証のテスト戦略

FastAPI 統合クイックリファレンス (スタンドアロン使用用)

# FastAPI と Pydantic (基本パターン)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str

class UserResponse(BaseModel):
    id: int
    username: str
    email: EmailStr

    model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse)
def create_user(user: UserCreate):
    # FastAPI は Pydantic を使用して自動検証
    # response_model はパスワードをフィルタリング
    return UserResponse(id=1, username=user.username, email=user.email)

SQLAlchemy 統合クイックリファレンス (スタンドアロン使用用)

# SQLAlchemy 2.0 と Pydantic 検証
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import DeclarativeBase
from pydantic import BaseModel, ConfigDict

class Base(DeclarativeBase):
    pass

class UserDB(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    username = Column(String(50))
    email = Column(String(100))

class UserSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    username: str
    email: str

# ORM を Pydantic に変換
user_orm = db.query(UserDB).first()
user_validated = UserSchema.model_validate(user_orm)

Pytest テストクイックリファレンス (スタンドアロン使用用)

# pytest を使用した Pydantic モデルのテスト
import pytest
from pydantic import ValidationError

def test_user_validation():
    user = User(id=1, name='Alice', email='alice@example.com')
    assert user.name == 'Alice'

def test_validation_error():
    with pytest.raises(ValidationError) as exc_info:
        User(id='invalid', name='Bob', email='bob@example.com')
    errors = exc_info.value.errors()
    assert errors[0]['type'] == 'int_parsing'

@pytest.fixture
def sample_user():
    return User(id=1, name='Alice', email='alice@example.com')

[他のスキルが一緒にデプロイされている場合、それぞれのスキルで完全な統合パターンが利用可能です]

その他のリソース

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
bobmatnyc
リポジトリ
bobmatnyc/claude-mpm-skills
ライセンス
MIT
最終更新
不明

Source: https://github.com/bobmatnyc/claude-mpm-skills / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: bobmatnyc · bobmatnyc/claude-mpm-skills · ライセンス: MIT