Pythonの強力なデータ検証ライブラリ Cerberus 詳細解説 🧐

Python

ソフトウェア開発において、データの整合性を保つことは非常に重要です。特にAPI、設定ファイル、ユーザー入力など、外部から受け取るデータは予期せぬ形式や値を含んでいる可能性があります。このようなデータを適切に処理するためには、データ検証が不可欠です。

Pythonには多くのデータ検証ライブラリが存在しますが、その中でもCerberusは、軽量でありながら強力で、かつ拡張性の高いライブラリとして注目されています。その名前はギリシャ神話に登場する冥界の番犬「ケルベロス」に由来し、まさにデータの門番として機能します。

このブログ記事では、Cerberusの基本的な使い方から、高度な機能、そしてカスタマイズ方法まで、詳細に解説していきます。Cerberusを使えば、あなたのPythonアプリケーションのデータ検証プロセスを、よりシンプルかつ堅牢にすることができるでしょう。さあ、Cerberusの世界を探検しましょう!🚀

第1章: Cerberusとは? – 概要と特徴

Cerberusは、Pythonで辞書(dict)形式のデータを検証するために設計されたライブラリです。主な特徴として以下の点が挙げられます。

  • 軽量性: Cerberusは外部ライブラリへの依存関係がありません。そのため、プロジェクトに簡単に追加でき、環境を複雑にすることがありません。
  • シンプルで直感的なスキーマ定義: 検証ルールはPythonの辞書を使って定義します。これにより、JSONやYAMLなどの形式とも親和性が高く、人間にとっても読み書きしやすいスキーマを作成できます。
  • 拡張性: 標準で提供されているルールセットが豊富なだけでなく、独自のカスタムルールやカスタムデータ型を簡単に追加できます。これにより、プロジェクト固有の複雑な検証要件にも柔軟に対応可能です。
  • 宣言的な検証: 「どのように検証するか」ではなく「データがどうあるべきか」をスキーマで宣言します。これにより、検証ロジックがコード本体から分離され、可読性と保守性が向上します。
  • 網羅的なエラーレポート: 検証に失敗した場合、最初のエラーで停止するのではなく、データ全体を検証し、すべてのエラー箇所をまとめて報告します。これにより、一度の検証で複数の問題を把握できます。
  • データ正規化(Normalization)と型変換(Coercion): 検証プロセス中に、データのクリーニングや型の変換を行う機能も備えています。例えば、文字列を数値に変換したり、不要な空白を除去したりすることが可能です。

これらの特徴により、CerberusはAPIリクエストのペイロード検証、設定ファイルのフォーマットチェック、データベース入力前のデータサニタイズなど、様々な場面で活躍します。

インストール方法

Cerberusのインストールはpipを使って簡単に行えます。

pip install cerberus

特に依存ライブラリはないため、これだけで準備完了です。

第2章: 基本的な使い方 – スキーマ定義と検証

Cerberusの基本的な使い方は非常にシンプルです。以下の3ステップで行います。

  1. スキーマ定義: 検証したいデータの構造とルールをPythonの辞書で定義します。
  2. Validatorインスタンス化: 定義したスキーマを使ってValidatorクラスのインスタンスを作成します。
  3. 検証実行: validate()メソッドを使って、対象のデータを検証します。

スキーマ定義の基本

スキーマは、検証対象の辞書のキーと、そのキーに対応する値が満たすべきルールを定義した辞書です。 キーにはフィールド名を、値にはそのフィールドに対するルールを記述した辞書を指定します。

最も基本的なルールは type で、フィールドの期待されるデータ型を指定します。

from cerberus import Validator

# シンプルなスキーマ定義
schema = {
    'name': {'type': 'string'},                    # nameは文字列であるべき
    'age': {'type': 'integer', 'min': 0},         # ageは0以上の整数であるべき
    'city': {'type': 'string', 'required': True}   # cityは必須の文字列であるべき
}

Validatorインスタンス化と検証実行

定義したスキーマをValidatorクラスのコンストラクタに渡してインスタンスを作成し、validate()メソッドでデータを検証します。

# Validatorのインスタンス化
v = Validator(schema)

# 検証対象のデータ (有効な例)
good_document = {
    'name': 'Alice',
    'age': 30,
    'city': 'Tokyo'
}

# 検証実行
is_valid_good = v.validate(good_document)
print(f"有効なデータの検証結果: {is_valid_good}")  # 出力: 有効なデータの検証結果: True
print(f"エラー内容 (有効なデータ): {v.errors}")   # 出力: エラー内容 (有効なデータ): {}

# 検証対象のデータ (無効な例)
bad_document = {
    'name': 'Bob',
    'age': -5,      # minルール違反
    # 'city' フィールドが欠落 (requiredルール違反)
    'country': 'USA' # スキーマに定義されていないフィールド (デフォルトでは許可される)
}

# 検証実行
is_valid_bad = v.validate(bad_document)
print(f"無効なデータの検証結果: {is_valid_bad}")  # 出力: 無効なデータの検証結果: False
print(f"エラー内容 (無効なデータ): {v.errors}")
# 出力例: エラー内容 (無効なデータ): {'age': ['min value is 0'], 'city': ['required field']}

validate()メソッドは、検証が成功すればTrueを、失敗すればFalseを返します。 検証に失敗した場合、エラーの詳細はValidatorインスタンスのerrors属性(辞書)に格納されます。キーがエラーのあったフィールド名、値がエラーメッセージのリストです。

Cerberusは、デフォルトではスキーマに定義されていないフィールド(上記の例ではcountry)が含まれていてもエラーとはみなしません。これを制御する方法は後述します。

ポイント: Cerberusは検証エラーが見つかっても即座に停止せず、ドキュメント全体を処理します。これにより、一度のvalidate()呼び出しで複数の問題を検出できます。

第3章: 主要な検証ルール 📜

Cerberusは豊富な組み込み検証ルールを提供しています。ここでは特によく使われるルールをいくつか紹介します。

ルール名 説明
type フィールドのデータ型を検証します。'string', 'integer', 'float', 'number' (整数または浮動小数点数), 'boolean', 'dict', 'list', 'set', 'datetime', 'date', 'bytes' などが指定可能です。型のリストを指定すると、いずれかの型に一致すれば有効となります。 {'type': 'integer'}
{'type': ['string', 'list']}
required フィールドが必須かどうかを指定します (デフォルトはFalse)。Trueに設定すると、そのフィールドが存在しない場合にエラーとなります。 {'required': True}
nullable フィールドがNone値(またはnull)を許可するかどうかを指定します (デフォルトはFalse)。Trueに設定すると、None値を受け入れます。 {'nullable': True}
empty 空の値(例: 空文字列'', 空リスト[], 空辞書{})を許可するかどうかを指定します (デフォルトはTrue)。Falseに設定すると、空の値はエラーとなります。requiredTrueでも、emptyTrue(デフォルト)なら空の値は許容される点に注意が必要です。 {'empty': False}
minlength / maxlength 文字列、リスト、セット、バイト列の最小長・最大長を指定します。 {'type': 'string', 'minlength': 5, 'maxlength': 10}
min / max 数値型 (integer, float, number) の最小値・最大値を指定します。 {'type': 'integer', 'min': 0, 'max': 100}
allowed フィールドが取りうる値をリストで指定します。リストに含まれない値はエラーとなります。 {'type': 'string', 'allowed': ['admin', 'user', 'guest']}
regex 値が指定された正規表現にマッチするかどうかを検証します。 {'type': 'string', 'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'} (メールアドレス形式)
readonly Trueに設定すると、そのフィールドが入力データに含まれている場合にエラーとなります。主に更新操作などで、変更を許可しないフィールドを指定する際に使用します。 {'readonly': True}
dependencies フィールド間の依存関係を定義します。特定のフィールドが存在する場合に、他のフィールドも必須とする、といったルールを設定できます。 {'dependencies': 'field_b'} (このフィールドが存在する場合、field_bも必須)
{'dependencies': ['field_c', 'field_d']} (このフィールドが存在する場合、field_cとfield_dも必須)
excludes 指定したフィールドと同時に存在してはいけないフィールドを指定します。 {'excludes': 'field_x'} (このフィールドとfield_xは同時に存在できない)
*of ルール (anyof, allof, noneof, oneof) 複数のルールセットに対する論理条件を指定します。例えばanyofは、指定されたルールセットのいずれか一つ以上に一致すれば有効とします。 {'anyof': [{'type': 'string'}, {'type': 'integer', 'min': 0}]} (文字列か、0以上の整数)

これらのルールを組み合わせることで、非常に柔軟なデータ検証が可能になります。公式ドキュメントには、さらに多くのルールが記載されていますので、必要に応じて参照してください。 (公式ドキュメント: Validation Rules)

from cerberus import Validator

schema = {
    'user_id': {'type': 'integer', 'required': True, 'min': 1},
    'username': {'type': 'string', 'required': True, 'minlength': 3, 'maxlength': 20, 'regex': '^[a-zA-Z0-9_]+$'},
    'role': {'type': 'string', 'allowed': ['member', 'admin'], 'default': 'member'},
    'email': {'type': 'string', 'required': False, 'nullable': True, 'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'},
    'is_active': {'type': 'boolean', 'default': True},
    'profile': {
        'type': 'dict',
        'required': False,
        'schema': { # ネストしたスキーマ
            'bio': {'type': 'string', 'maxlength': 200, 'nullable': True},
            'website': {'type': 'string', 'regex': '^https?://.+$', 'nullable': True}
        }
    },
    'tags': {
        'type': 'list',
        'schema': {'type': 'string', 'maxlength': 50} # リスト内の各要素は最大50文字の文字列
    },
    'admin_level': {'type': 'integer', 'required': True, 'dependencies': {'role': 'admin'}} # roleが'admin'の時だけ必須
}

v = Validator(schema)

valid_data = {
    'user_id': 101,
    'username': 'test_user',
    'role': 'admin', # roleがadminなのでadmin_levelが必須になる
    'email': None,
    'profile': {'bio': 'Hello!', 'website': 'https://example.com'},
    'tags': ['python', 'validation'],
    'admin_level': 5 # dependenciesを満たす
}

invalid_data = {
    'user_id': 0, # min違反
    'username': 'test user', # regex違反 (スペースが含まれる)
    # 'role' が欠落 (allowedのデフォルト 'member' が適用されるが、admin_levelがないため問題ない)
    'is_active': 'yes', # boolean違反
    'profile': {'website': 'invalid-url'}, # regex違反
    'tags': ['long_tag_that_exceeds_fifty_characters_limit_aaaaaaaaaaaa'], # maxlength違反
    'role': 'admin', # roleがadmin
    # admin_level が欠落 (dependencies違反)
}

print("--- Valid Data ---")
print(f"Validation result: {v.validate(valid_data)}")
print(f"Errors: {v.errors}")
print(f"Normalized data: {v.normalized(valid_data)}") # default値が適用されたデータ

print("\n--- Invalid Data ---")
print(f"Validation result: {v.validate(invalid_data)}")
print(f"Errors: {v.errors}")

第4章: 高度な機能 ✨

Cerberusは基本的な検証ルールに加え、より複雑な要件に対応するための高度な機能も提供しています。

データ正規化 (Normalization) と型変換 (Coercion)

検証プロセスの一部として、データの値を変換したり整形したりすることができます。これは主に以下のルールで行います。

  • coerce: 検証前に値に関数を適用します。例えば、文字列で受け取った数値をintfloatに変換したり、文字列の前後の空白を除去したりするのに使えます。関数オブジェクトや、カスタムValidatorクラスのメソッド名を文字列で指定できます。
  • rename / rename_handler: フィールド名を変更します。古いAPIとの互換性維持などに役立ちます。
  • default / default_setter: フィールドが存在しない場合にデフォルト値を設定します。defaultは静的な値を、default_setterは関数(またはメソッド名)を指定して動的にデフォルト値を生成できます。
  • purge_unknown: Validatorのインスタンスプロパティ(またはallow_unknownルール内)でTrueに設定すると、スキーマに定義されていないフィールドを検証後のドキュメントから削除します。デフォルトはFalseです。
from cerberus import Validator
import datetime

def to_uppercase(value):
    if isinstance(value, str):
        return value.upper()
    return value

def parse_date(value):
    try:
        # YYYY-MM-DD形式を期待
        return datetime.datetime.strptime(value, '%Y-%m-%d').date()
    except (ValueError, TypeError):
        return value # パースできない場合は元の値を返す (後続のtype検証でエラーになる)

schema = {
    'item_code': {'type': 'string', 'coerce': to_uppercase, 'required': True},
    'quantity': {'type': 'integer', 'coerce': int, 'min': 1}, # 文字列で来ても整数に変換
    'price': {'type': 'float', 'coerce': float},
    'category': {'type': 'string', 'default': 'uncategorized'},
    'purchase_date': {'type': 'date', 'coerce': parse_date, 'nullable': True}
}

# rename/purge_unknown は Validator インスタンスで設定
# v = Validator(schema, purge_unknown=True)

# または rename_handler を使う
# def custom_rename(field):
#     if field == 'old_name':
#         return 'new_name'
#     return field
# v = Validator(schema, rename_handler=custom_rename)


v = Validator(schema)

data = {
    'item_code': 'abc-123',
    'quantity': '10', # 文字列だがcoerceでintに変換される
    'price': '99.99',
    'extra_field': 'this will be kept by default',
    'purchase_date': '2025-04-01'
}

if v.validate(data):
    print("Validation successful!")
    normalized_data = v.normalized(data)
    print("Normalized data:")
    print(normalized_data)
    # 出力例:
    # Normalized data:
    # {'item_code': 'ABC-123', 'quantity': 10, 'price': 99.99, 'category': 'uncategorized', 'extra_field': 'this will be kept by default', 'purchase_date': datetime.date(2025, 4, 1)}
else:
    print(f"Validation failed: {v.errors}")

# categoryがなくてもdefaultで補完される例
data_no_category = {
    'item_code': 'xyz-789',
    'quantity': 5,
    'price': 150.00
}
if v.validate(data_no_category):
     print("\nValidation successful (no category)!")
     print(f"Normalized data: {v.normalized(data_no_category)}")
     # 出力例: Normalized data: {'item_code': 'XYZ-789', 'quantity': 5, 'price': 150.0, 'category': 'uncategorized'}

coerceは特に、Web APIなどで文字列として受け取ったデータを適切な型に変換する際に非常に便利です。変換後の値に対して検証ルールが適用されます。 検証と正規化が完了したデータは、Validatorインスタンスのnormalized()メソッドまたはdocumentプロパティから取得できます。

ネストした辞書とリストの検証

Cerberusは、辞書の中に辞書やリストが含まれるような、ネストしたデータ構造の検証も得意です。

  • 辞書 (dict) のネスト: typedictにし、schemaルールを使ってネストした辞書のスキーマを定義します。
  • リスト (list) のネスト: typelistにし、schemaルールを使ってリスト内の各要素が満たすべきスキーマを定義します。(リスト内の要素が全て同じ構造の場合)。リスト内に異なる型の要素を許可する場合は、itemsルールを使います。
  • 辞書の値を検証 (valueschema): 辞書のキーに関わらず、全ての値が特定のスキーマに従うことを要求する場合に使います。
  • 辞書のキーを検証 (keyschema): 辞書のキー自体が特定の型やルールに従うことを要求する場合に使います (あまり一般的ではありません)。
  • 辞書のプロパティを検証 (propertyschema): 辞書のキーが特定の正規表現にマッチする場合に、その値に対応するスキーマを適用します。
from cerberus import Validator

complex_schema = {
    'user': {
        'type': 'dict',
        'required': True,
        'schema': { # ネストした辞書のスキーマ
            'id': {'type': 'integer', 'required': True},
            'name': {'type': 'string', 'required': True}
        }
    },
    'permissions': {
        'type': 'list',
        'required': False,
        'schema': {'type': 'string', 'allowed': ['read', 'write', 'admin']} # リスト要素のスキーマ
    },
    'settings': {
        'type': 'dict',
        'valueschema': {'type': 'boolean'} # 全てのsetting値はbooleanであるべき
    },
    'dynamic_fields': {
        'type': 'dict',
        'propertyschema': { # キーが 'prop_' で始まるフィールド
            'startsWith_prop_.*': {'type': 'string', 'maxlength': 10}
        },
        'valueschema': {'type': 'integer'} # それ以外のフィールドの値は整数
    }
}

v = Validator(complex_schema)

data = {
    'user': {'id': 1, 'name': 'Charlie'},
    'permissions': ['read', 'write'],
    'settings': {
        'enable_notifications': True,
        'dark_mode': False
    },
    'dynamic_fields': {
        'startsWith_prop_A': 'valueA', # propertyschemaにマッチ
        'startsWith_prop_B': 'valueB', # propertyschemaにマッチ
        'other_field': 123 # valueschemaにマッチ
    }
}

invalid_data_example = {
    'user': {'id': 'not-an-int'}, # 型エラー
    'permissions': ['read', 'execute'], # allowed違反
    'settings': {'feature_x': 'enabled'}, # valueschema違反 (booleanでない)
    'dynamic_fields': {
        'startsWith_prop_C': 'this string is too long', # propertyschemaのmaxlength違反
        'another_field': 'not-an-int' # valueschema違反
    }
}

print("--- Complex Valid Data ---")
print(f"Validation result: {v.validate(data)}")
print(f"Errors: {v.errors}")

print("\n--- Complex Invalid Data ---")
print(f"Validation result: {v.validate(invalid_data_example)}")
print(f"Errors: {v.errors}")

未知のフィールドの扱い (allow_unknown)

デフォルトでは、Cerberusはスキーマに定義されていないフィールドが存在しても許容します。この挙動を変更するにはallow_unknownルールを使用します。

  • v = Validator(schema, allow_unknown=False): Validator全体で未知のフィールドを禁止します。
  • 'sub_dict': {'type': 'dict', 'allow_unknown': False, 'schema': {...}}: 特定のネストした辞書内でのみ未知のフィールドを禁止します。
  • 'sub_dict': {'type': 'dict', 'allow_unknown': {'type': 'string'}}: 未知のフィールドを許可するが、その値は指定されたスキーマ(この例では文字列)に従う必要がある、という高度な設定も可能です。

purge_unknown=Trueと組み合わせることで、検証プロセスで未知のフィールドを自動的に除去することもできます。

from cerberus import Validator

schema = {
    'known_field': {'type': 'string'}
}

# 1. デフォルト (未知のフィールドを許可)
v_allow = Validator(schema)
data = {'known_field': 'hello', 'unknown_field': 123}
print(f"Default allow_unknown: {v_allow.validate(data)}, Errors: {v_allow.errors}") # True, {}

# 2. 未知のフィールドを禁止
v_disallow = Validator(schema, allow_unknown=False)
print(f"allow_unknown=False: {v_disallow.validate(data)}, Errors: {v_disallow.errors}") # False, {'unknown_field': ['unknown field']}

# 3. 未知のフィールドを許可し、値を検証
v_validate_unknown = Validator(schema, allow_unknown={'type': 'integer'})
data_valid_unknown = {'known_field': 'world', 'another_unknown': 456}
data_invalid_unknown = {'known_field': 'world', 'invalid_unknown': 'abc'}
print(f"allow_unknown=schema (valid): {v_validate_unknown.validate(data_valid_unknown)}, Errors: {v_validate_unknown.errors}") # True, {}
print(f"allow_unknown=schema (invalid): {v_validate_unknown.validate(data_invalid_unknown)}, Errors: {v_validate_unknown.errors}") # False, {'invalid_unknown': ['must be of integer type']}

# 4. purge_unknown=True で未知のフィールドを除去
v_purge = Validator(schema, allow_unknown=True, purge_unknown=True) # allow_unknown=Trueがないとpurgeされない
if v_purge.validate(data):
    print(f"purge_unknown=True: Normalized: {v_purge.normalized(data)}") # {'known_field': 'hello'}

第5章: カスタム検証ルールとカスタム型 🛠️

Cerberusの大きな強みの一つは、その拡張性の高さです。標準のルールや型だけでは不十分な場合、独自の検証ロジックを簡単に追加できます。

カスタム検証ルール (Custom Rules)

特定のドメイン知識に基づいた検証を行いたい場合、カスタムルールを定義できます。これには主に2つの方法があります。

方法1: Validatorクラスのサブクラス化

cerberus.Validatorを継承したクラスを作成し、_validate_<ルール名>という形式のメソッドを実装します。このメソッドは、ルール制約、フィールド名、フィールド値の3つを引数として受け取ります。ルールに違反する場合は、self._error()メソッドを呼び出してエラーを記録します。

from cerberus import Validator

class MyValidator(Validator):
    def _validate_is_odd(self, constraint, field, value):
        """ constraint が True の場合、値が奇数であることを検証します。
        The rule's arguments are validated against this schema:
        {'type': 'boolean'}
        """
        if constraint is True and not isinstance(value, int):
             # 事前に型チェックが必要な場合がある
             # この例では type: integer がスキーマにある前提
             pass
        elif constraint is True and isinstance(value, int) and not bool(value & 1):
            # self._error(フィールド名, エラーメッセージ)
            self._error(field, "Must be an odd number")

    def _validate_divisible_by(self, divisor, field, value):
        """ 値が指定された除数で割り切れることを検証します。
        The rule's arguments are validated against this schema:
        {'type': 'integer', 'min': 1}
        """
        if not isinstance(value, int):
            pass # 型エラーは 'type' ルールに任せる
        elif value % divisor != 0:
            self._error(field, f"Must be divisible by {divisor}")

# カスタムルールを使ったスキーマ
schema = {
    'odd_number': {'type': 'integer', 'is_odd': True}, # _validate_is_odd が呼ばれる
    'multiple_of_5': {'type': 'integer', 'divisible_by': 5} # _validate_divisible_by が呼ばれる
}

v = MyValidator(schema) # カスタムValidatorを使用

data1 = {'odd_number': 9, 'multiple_of_5': 10}
print(f"Data1 valid: {v.validate(data1)}, Errors: {v.errors}") # True, {}

data2 = {'odd_number': 8, 'multiple_of_5': 12}
print(f"Data2 valid: {v.validate(data2)}, Errors: {v.errors}")
# False, {'odd_number': ['Must be an odd number'], 'multiple_of_5': ['Must be divisible by 5']}

# ルール引数の検証 (docstring内のスキーマに基づく)
invalid_schema = {'multiple_of_5': {'divisible_by': 0}} # divisor は 1以上である必要がある
try:
    MyValidator(invalid_schema)
except Exception as e:
    print(f"\nInvalid schema error: {e}")

カスタムルールメソッドのdocstring内に、そのルールが受け取る引数(制約)に対するスキーマを記述することで、スキーマ自体の妥当性も検証できます。

方法2: check_with ルールと関数

より手軽な方法として、検証ロジックを通常のPython関数として定義し、スキーマ内でcheck_withルールを使ってその関数を指定する方法があります。関数は値を受け取り、検証に失敗した場合にFalseを返すか、エラーを発生させる必要があります(ただし、Falseを返すのが一般的です)。check_withは、クラスベースのカスタムルール名(_validate_プレフィックスなし)を文字列で指定することもできます。

from cerberus import Validator, errors

def check_is_palindrome(field, value, error):
    """ 値が回文かどうかをチェックする """
    if isinstance(value, str) and value != value[::-1]:
        # error(フィールド名, エラーメッセージ) を呼び出す
        error(field, "Must be a palindrome")

schema_func = {
    'word': {'type': 'string', 'check_with': check_is_palindrome}
}

# MyValidatorクラス (前の例から) のメソッドを参照することも可能
schema_class_ref = {
     'another_odd': {'type': 'integer', 'check_with': 'is_odd', 'is_odd': True}
}


v_func = Validator(schema_func)
v_class_ref = MyValidator(schema_class_ref) # カスタムValidatorが必要

print(f"Palindrome check ('level'): {v_func.validate({'word': 'level'})}, Errors: {v_func.errors}") # True, {}
print(f"Palindrome check ('hello'): {v_func.validate({'word': 'hello'})}, Errors: {v_func.errors}") # False, {'word': ['Must be a palindrome']}

print(f"Odd check (class ref, 7): {v_class_ref.validate({'another_odd': 7})}, Errors: {v_class_ref.errors}") # True, {}
print(f"Odd check (class ref, 6): {v_class_ref.validate({'another_odd': 6})}, Errors: {v_class_ref.errors}") # False, {'another_odd': ['Must be an odd number']}

クラスベースの方法は再利用性が高く、より複雑な状態管理が可能ですが、関数ベースの方法は特定のケースでシンプルに実装できます。

カスタムデータ型 (Custom Data Types)

標準の型(string, integerなど)以外に、独自のデータ型に対する検証を追加したい場合があります。例えば、特定のフォーマットに従う識別子(UUID、ISBNなど)や、特定のオブジェクトインスタンスであることを検証したい場合です。

カスタム型は、カスタムルールと同様にValidatorのサブクラスを作成し、_validate_type_<型名>というメソッドを実装することで定義します。このメソッドはフィールド名と値を受け取り、型が不正な場合にself._error()を呼び出します。

from cerberus import Validator
import re
from decimal import Decimal, InvalidOperation

class RichValidator(MyValidator): # 前の例のMyValidatorを継承
    def _validate_type_email(self, field, value):
        """ 簡単なメールアドレス形式を検証するカスタム型 """
        if not isinstance(value, str) or not re.match(r"[^@]+@[^@]+\.[^@]+", value):
            self._error(field, "Must be a valid email address (custom type)")

    def _validate_type_decimal(self, field, value):
        """ decimal.Decimal 型を検証するカスタム型 """
        try:
            # 文字列からの変換も試みる(必要に応じて)
            if isinstance(value, str):
                 Decimal(value)
            elif not isinstance(value, Decimal):
                 raise TypeError
        except (InvalidOperation, TypeError):
            self._error(field, "Must be a valid Decimal")


schema_custom_type = {
    'contact_email': {'type': 'email', 'required': True}, # カスタム型 'email'
    'precise_value': {'type': 'decimal', 'required': True} # カスタム型 'decimal'
}

v_rich = RichValidator(schema_custom_type)

data_ok = {'contact_email': 'test@example.com', 'precise_value': Decimal('123.456')}
print(f"\nCustom type (valid): {v_rich.validate(data_ok)}, Errors: {v_rich.errors}") # True, {}

data_ng = {'contact_email': 'invalid-email', 'precise_value': 12.34 } # floatはDecimalではない
print(f"Custom type (invalid): {v_rich.validate(data_ng)}, Errors: {v_rich.errors}")
# False, {'contact_email': ['Must be a valid email address (custom type)'], 'precise_value': ['Must be a valid Decimal']}

このようにカスタムルールとカスタム型を組み合わせることで、Cerberusの検証能力をプロジェクトのニーズに合わせて大幅に拡張できます。

第6章: ユースケースとベストプラクティス 💡

Cerberusはその柔軟性と軽量さから、様々なPythonアプリケーションで活用されています。

主なユースケース

  • Web APIの入力検証: Flask, Django, FastAPIなどのWebフレームワークで、リクエストボディ(JSONペイロードなど)やクエリパラメータが期待される形式・値を持っているかを検証します。これにより、不正なデータによるエラーやセキュリティリスクを防ぎます。
  • 設定ファイルの検証: YAMLやJSON形式の設定ファイルが正しい構造とデータ型を持っているかを確認します。アプリケーション起動時に設定を読み込む際に検証することで、設定ミスによる問題を早期に発見できます。
  • データパイプライン/ETL処理: データ処理の各ステップで、データが期待されるスキーマに準拠しているかを確認します。データの品質を維持し、後続処理でのエラーを防ぎます。
  • フォームデータの検証: Webフォームから送信されたデータをサーバーサイドで検証します。クライアントサイドのバリデーションと併用することで、より堅牢な入力チェックを実現します。
  • コマンドラインインターフェース (CLI) の引数/オプション検証: argparseなどで受け取った引数やオプションの値が適切かを確認します。

ベストプラクティス

  • スキーマは再利用可能な形で定義する: スキーマ定義を別ファイル(例: schemas.py)にまとめたり、設定ファイル(YAMLなど)から読み込むようにしたりすることで、コードの可読性と保守性を高めます。
  • requirednullable/empty を適切に使い分ける: フィールドが必須か、Noneを許容するか、空文字列/リストを許容するかを明確に定義します。
  • coerce を活用して型変換を早期に行う: APIなどから文字列で受け取ることが多いデータは、coerceを使って早い段階で適切な型(数値、日付など)に変換し、その後の処理をシンプルにします。
  • エラーメッセージを分かりやすくする: デフォルトのエラーメッセージでも十分ですが、必要であればカスタムエラーハンドラ(後述はしませんが、公式ドキュメント参照)を作成したり、エラーメッセージをユーザーフレンドリーな言葉に変換したりすることを検討します。errors辞書を解析して、APIのレスポンスなどに整形して返します。
  • 複雑なルールはカスタムルールに切り出す: スキーマが複雑になりすぎる場合は、特定の検証ロジックをカスタムルールとしてValidatorのサブクラスに実装することを検討します。
  • ネストしたスキーマを活用する: 複雑なデータ構造に対しては、schemaルールを使ってネストしたスキーマを定義し、構造を明確にします。
  • バージョン管理: APIなどでスキーマが変更される可能性がある場合は、スキーマのバージョン管理を検討します。
  • テストを記述する: 定義したスキーマと検証ロジックが意図通りに動作するかを確認するために、有効なデータと無効なデータの両方に対する単体テストを記述します。
ヒント: スキーマ定義をYAMLファイルで行うことも可能です。PyYAMLなどのライブラリと組み合わせることで、スキーマをコードから分離できます。
# schema.yaml
name:
  type: string
  required: true
age:
  type: integer
  min: 0
  nullable: true
import yaml
from cerberus import Validator

with open('schema.yaml', 'r') as f:
    schema = yaml.safe_load(f)

v = Validator(schema)
# ... 以降の検証処理

第7章: Cerberusと他のライブラリの比較

PythonにはCerberus以外にも優れたデータ検証・シリアライゼーションライブラリが存在します。ここでは主要なものと比較してみましょう。

ライブラリ 主な特徴 Cerberusとの違い・比較
Pydantic
  • Pythonの型ヒント (Type Hints) を利用したデータ検証と設定管理。
  • データクラスライクなモデル定義。
  • FastAPIなどのモダンなフレームワークで広く採用。
  • データのパース、検証、シリアライズ/デシリアライズ機能が強力。
  • 定義方法: Pydanticは型ヒントベースのクラス定義、Cerberusは辞書ベースのスキーマ定義。
  • 依存関係: Pydanticは型ヒントを活用するため、比較的新しいPythonバージョンが必要になることがあります。Cerberusは依存関係なし。
  • 主目的: Pydanticは型ヒントを活用したデータモデリングと検証に強み。Cerberusは辞書データの検証に特化。
  • エコシステム: FastAPIなどとの連携はPydanticが強力。
Marshmallow
  • オブジェクトのシリアライズ/デシリアライズとデータ検証のためのライブラリ。
  • クラスベースのスキーマ定義。
  • ORM (SQLAlchemyなど) との連携機能が豊富。
  • Webフレームワーク (Flask, Django) の拡張機能が多く存在する。
  • 定義方法: Marshmallowはクラスベース、Cerberusは辞書ベース。
  • 主目的: Marshmallowはオブジェクトと辞書(JSONなど)間の変換(シリアライズ/デシリアライズ)に重点を置きつつ検証も行う。Cerberusは主に辞書データの検証にフォーカス。
  • 複雑さ: Marshmallowは高機能な分、Cerberusよりも学習コストがやや高い可能性があります。
jsonschema
  • JSON Schema仕様に基づいたJSONデータの検証ライブラリ。
  • JSON Schemaは言語非依存の標準仕様。
  • 主にJSONデータの構造と型の検証に特化。
  • スキーマ形式: jsonschemaはJSON Schema仕様の辞書、Cerberusは独自の辞書形式。
  • 機能: Cerberusは型変換(coerce)やカスタムルールなどの点で、jsonschemaよりもPython固有の柔軟性が高い。jsonschemaは標準仕様準拠が強み。
  • 用途: JSONデータの検証に特化するならjsonschemaも有力な選択肢。CerberusはPythonの辞書全般の検証に適している。

どのライブラリを選択するかは、プロジェクトの要件や好みによります。

  • Pythonの型ヒントを積極的に活用し、データモデルを明確に定義したい場合はPydanticが適しています。
  • オブジェクトのシリアライズ/デシリアライズが主目的で、ORM連携などが必要な場合はMarshmallowが強力です。
  • 辞書データの検証に特化し、軽量性シンプルさ拡張性を重視する場合はCerberusが良い選択肢となります。
  • JSON Schema標準に準拠した検証が必要な場合はjsonschemaが適しています。

Cerberusは、特に設定ファイルの検証や、他のライブラリほど高機能なシリアライズ/デシリアライズを必要としないAPIの入力検証などにおいて、そのシンプルさと柔軟性が光ります ✨。

結論: Cerberusで堅牢なデータ検証を! 🎉

このブログ記事では、Pythonのデータ検証ライブラリであるCerberusについて、基本的な使い方から高度な機能、カスタマイズ方法、そして他のライブラリとの比較までを詳しく解説しました。

Cerberusの主な利点を再確認しましょう:

  • シンプルで直感的なスキーマ定義: Pythonの辞書で簡単にルールを記述できます。
  • 軽量で依存関係なし: プロジェクトへの導入が容易です。
  • 高い拡張性: カスタムルールやカスタム型で、複雑な要件にも対応できます。
  • 豊富な検証ルール: 型、必須チェック、長さ、範囲、正規表現など、多彩なルールが組み込まれています。
  • データ正規化機能: coercedefaultなどで、検証と同時にデータを整形できます。
  • 網羅的なエラーレポート: 一度の検証で全てのエラー箇所を把握できます。

APIの入力、設定ファイル、ユーザー入力など、外部から受け取るデータの品質は、アプリケーションの安定性とセキュリティに直結します。Cerberusを活用することで、これらのデータの検証プロセスを効率的かつ確実に行うことができます。

ぜひ、あなたの次のPythonプロジェクトでCerberusを導入し、その「データの番犬」としての信頼性を体験してみてください! 💪

さらに詳しい情報や最新のアップデートについては、Cerberusの公式ドキュメントを参照することをお勧めします。

コメント

タイトルとURLをコピーしました