voluptuous

Pythonバリデーションデータ検証スキーマ型チェックJSONYAML

バリデーションライブラリ

voluptuous

概要

voluptuousは、Pythonのデータバリデーションライブラリです。主にJSON、YAMLなどの外部から入力されるデータの検証を目的として設計されており、シンプルで読みやすい構文と詳細なエラーメッセージが特徴です。複雑なデータ構造の検証をサポートし、Pythonの基本的なデータ型を使用してスキーマを定義できます。

詳細

voluptuousは、以下の設計原則に基づいて開発されています:

主要な特徴

  • シンプルな構文: Pythonの基本的なデータ型(dict、list、int、strなど)を使用してスキーマを定義
  • 詳細なエラーメッセージ: 検証エラーが発生した場合、何が問題かを明確に示す有用なメッセージを提供
  • ネストされた構造のサポート: 複雑なデータ構造も他のデータ型と同様に扱える
  • カスタムバリデータ: 独自の検証ロジックを簡単に追加可能
  • 型強制: データを期待する型に自動変換する機能
  • オプショナルフィールド: 必須フィールドとオプショナルフィールドの柔軟な定義

設計哲学

  1. バリデータはシンプルな呼び出し可能オブジェクト: サブクラス化は不要、単純な関数で実装可能
  2. エラーはシンプルな例外: Invalid(msg)を投げるだけで有用なメッセージを提供
  3. スキーマは基本的なPythonデータ構造: {int: str}のような直感的な記法をサポート
  4. フォーム以外の検証にも対応: APIデータ、設定ファイル、YAMLなど幅広い用途に使用可能

主な用途

  • 設定ファイルの検証
  • API入力データの検証
  • YAML/JSONファイルのバリデーション
  • フォームデータの検証
  • 環境変数の検証

メリット・デメリット

メリット

  • 学習コストが低い: Pythonの基本的なデータ型を使用するため、直感的に理解しやすい
  • 依存関係なし: 外部ライブラリに依存しない軽量な実装
  • 柔軟性が高い: カスタムバリデータやルールを簡単に追加できる
  • エラーメッセージが分かりやすい: デバッグが容易
  • コードの可読性: スキーマ定義が読みやすく、ドキュメントとしても機能
  • Python的な記法: Pythonic なAPIデザイン

デメリット

  • パフォーマンス: Pydanticなどと比較すると処理速度が劣る
  • 型アノテーション非対応: Python 3.6+の型ヒントを活用できない
  • オブジェクト変換なし: バリデーション後もdictのまま(Pydanticのようなモデルオブジェクトは生成されない)
  • IDEサポートが限定的: 型推論やオートコンプリートが効きにくい
  • メンテナンス頻度: 他の主要なバリデーションライブラリと比較して更新頻度が低い

参考ページ

書き方の例

基本的な使い方

from voluptuous import Schema, Required

# 基本的なスキーマの定義
schema = Schema({
    Required('name'): str,
    Required('age'): int,
    'email': str  # オプショナルフィールド
})

# データの検証
data = {
    'name': '山田太郎',
    'age': 30,
    'email': '[email protected]'
}

validated_data = schema(data)
print(validated_data)

型とバリデーション

from voluptuous import Schema, All, Length, Range

# 制約付きスキーマ
schema = Schema({
    Required('username'): All(str, Length(min=3, max=20)),
    Required('age'): All(int, Range(min=0, max=150)),
    Required('score'): All(float, Range(min=0.0, max=100.0))
})

# 正常なデータ
data = {
    'username': 'user123',
    'age': 25,
    'score': 85.5
}

validated = schema(data)

ネストされた構造

from voluptuous import Schema, Required

# ネストされたスキーマ
address_schema = Schema({
    Required('street'): str,
    Required('city'): str,
    Required('zipcode'): str
})

person_schema = Schema({
    Required('name'): str,
    Required('addresses'): [address_schema]  # アドレスのリスト
})

# 検証
data = {
    'name': '田中花子',
    'addresses': [
        {
            'street': '東京都渋谷区1-1-1',
            'city': '東京',
            'zipcode': '150-0001'
        }
    ]
}

validated = person_schema(data)

カスタムバリデータ

from voluptuous import Schema, Invalid
from datetime import datetime

# カスタムバリデータ関数
def valid_date(date_string):
    try:
        date = datetime.strptime(date_string, '%Y-%m-%d')
        if date > datetime.now():
            raise Invalid('日付は未来の日付にできません')
        return date_string
    except ValueError:
        raise Invalid('日付形式が正しくありません (YYYY-MM-DD)')

# カスタムバリデータを使用
schema = Schema({
    Required('name'): str,
    Required('birth_date'): valid_date
})

data = {'name': '鈴木一郎', 'birth_date': '1990-05-15'}
validated = schema(data)

エラーハンドリング

from voluptuous import Schema, Required, Invalid, MultipleInvalid

schema = Schema({
    Required('name'): str,
    Required('age'): int,
    Required('email'): str
})

# エラーが発生するデータ
invalid_data = {
    'name': 123,  # 型が違う
    'age': 'thirty',  # 型が違う
    # emailが欠けている
}

try:
    validated = schema(invalid_data)
except MultipleInvalid as e:
    print("検証エラー:")
    for error in e.errors:
        print(f"- {error.path}: {error.message}")

高度な使用例 - API入力検証

from voluptuous import Schema, Required, Optional, All, Length, Email, In

# APIエンドポイント用のスキーマ
user_registration_schema = Schema({
    Required('username'): All(str, Length(min=3, max=30)),
    Required('password'): All(str, Length(min=8)),
    Required('email'): Email(),
    Required('user_type'): In(['customer', 'vendor', 'admin']),
    Optional('phone'): All(str, Length(min=10, max=15)),
    Optional('preferences'): {
        'newsletter': bool,
        'notifications': bool,
        'language': In(['ja', 'en', 'zh'])
    }
})

# 使用例
def register_user(request_data):
    try:
        # データ検証
        validated_data = user_registration_schema(request_data)
        
        # ここでユーザー登録処理を実行
        return {'status': 'success', 'data': validated_data}
    
    except MultipleInvalid as e:
        return {
            'status': 'error',
            'errors': [{'field': str(err.path[0]), 'message': err.msg} 
                      for err in e.errors]
        }