LoginSignup
94
68

More than 5 years have passed since last update.

Pythonの汎用データバリデーションライブラリ「Cerberus」を使う

Last updated at Posted at 2016-11-28

バリデーションチェック、めんどくさいですよね。

最初はFormEncodeでやろうとしたのですが、「もうちょっと今っぽいやつないのかな……」と探したところ、とてもいい感じのライブラリがあったので、使い方をメモしておきます。ご参考になれば幸いです。

Cerberusとは

英語読みで「サーベラス」でいいんでしょうか? いわゆる「ケルベロス」のことで、「冥界の番犬」のようにデータの入り口を守る、というのが名前の由来のようです。厨ニっぽい かっこいいですね。

導入

$ pip install cerberus

使い方

サンプル見て頂くのが早いです。こんな感じに今っぽく書けます。
(Python 3.5.2 で動作確認しています)

sample_cerberus.py
# -*- coding: utf-8 -*-

from cerberus import Validator

import re
from datetime import datetime, date
from pprint import pprint

# バリデーション定義
schema = {
    'name': {
        'type': 'string',
        'required': True,
        'empty': False,
    },
    'email': {
        'type': 'string',
        'required': True,
        'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$',
    },
    'age': {
        'type': 'integer',
        'min': 0,
    },
    'phones': {
        'type': 'list',
        'schema': {
            'type': 'string',
            'regex': '^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$',
        }
    },
    'address': {
        'type': 'string',
        'empty': True,
    },
    'birthday': {
        'type': 'date',
    }
}

# バリデータを作成
v = Validator(schema)

# 入力値1(バリデーションOK)
data_ok = {
    'name': '田中一郎',
    'email': 'tanaka@test.co.jp',
    'age': 30,
    'phones': [
        '012-3456-7890',
        '0120-444-444',
    ],
    'address': '',
    'birthday': date(1990, 7, 7),
}

# 入力値2(バリデーションNG)
data_ng = {
    'name': '',
#    'email': 'tanaka@test.co.jp',
    'age': -300,
    'phones': [
        '01234567890',
        'skype',
    ],
    'address': None,
    'birthday': '1990-07-07',
    'sex': 'male',
}

# バリデーション実施、結果表示
print("-----------------------------------------")
print("◯OK")
print("-----------------------------------------")
pprint(v.validate(data_ok))
pprint(v.errors)

print("-----------------------------------------")
print("×NG")
print("-----------------------------------------")
pprint(v.validate(data_ng))
pprint(v.errors)

実行結果

-----------------------------------------
OK
-----------------------------------------
True
{}
-----------------------------------------
×NG
-----------------------------------------
False
{'age': ['min value is 0'],
 'birthday': ['must be of date type'],
 'email': ['required field'],
 'name': ['empty values not allowed'],
 'phones': [{0: ['value does not match regex '
                 "'^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$'"],
             1: ['value does not match regex '
                 "'^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$'"]}],
 'sex': ['unknown field']}

サンプルではよく使いそうなバリデーションルールを中心に取り上げましたが、他にも色々あるので、詳しくは以下をご覧ください。

Validation Rules — Cerberus is a lightweight and extensible data validation library for Python

バリデーションエラーメッセージの日本語化

このままでも使えますが、おおかた「メッセージは日本語にできないの?」って話になりますよね。たぶん。

残念ながら2016/11時点の最新バージョン(v1.0.1)では、リソースファイルなどを使った多言語対応はされていません。ですが、BasicErrorHandlerのメッセージ定義を上書きすることで、バリデーションエラーメッセージを日本語化することが可能です。

# -*- coding: utf-8 -*-

from pprint import pprint

from cerberus import Validator
from cerberus.errors import BasicErrorHandler

class CustomErrorHandler(BasicErrorHandler):
    """ BasicErrorHandler.message を上書きして日本語化 """
    def __init__(self, tree=None):
        super(CustomErrorHandler, self).__init__(tree)
        # 文言を適宜日本語化してください
        self.messages = {
            0x00: "{0}",
            0x01: "document is missing",
            0x02: "required field",
            0x03: "知らない項目 '{field}' です",
#            ... (略) ...
            0x93: "no definitions validate",
            0x94: "one or more definitions don't validate"
        }


schema = {
    'mail_address' : {'type' : 'string'},
}
data = {
    'name': '田中',     # 0x03: バリデーションエラー
    'mail_address' : 'tanaka_at_test.com',
}

# 拡張したBasicErrorHandlerを指定
v = Validator(schema, error_handler=CustomErrorHandler())

pprint(v.validate(data))
pprint(v.errors)

実行結果

False
{'name': ["知らない項目 'name' です"]}

こちらにIssueが上がっているので、そのうち、こんなことする必要はなくなるかもしれません。なるといいなあ……

バリデーションルールのカスタマイズ

上記サンプルを見ると、「メールアドレスをregexでチェックしているところ、カスタムバリデーションにできない?」って話になりますよね。たぶん。

ここにドキュメントがあるのですが、どういうときにどれを使うべきなのかがよくわからなかったので、簡単にまとめておきます。

1. カスタムデータ型を追加する (Custom input_data Types)

'type'として独自のデータ型を指定する場合です。

schema = {
    'mail_address' : {
        'type' : 'email',
    },
}

{'type': 'integer'} とか {'type': 'string'} とか、バリデーションに使えるデータタイプはいくつかありますが、ここに新しく{'type' : 'email'} を追加する、というイメージです。

Validatorを拡張したクラス(CustomValidator)を作成、そこにデータタイプを追加します。

# -*- coding: utf-8 -*-

import re
from pprint import pprint

from cerberus import Validator

REGEX_EMAIL = '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'

class CustomValidator(Validator):
    # [Custom input_data Types]
    # http://docs.python-cerberus.org/en/stable/customize.html#custom-input_data-types
    def _validate_type_email(self, value):
        if re.match(REGEX_EMAIL, value):
            return True

schema = {
    'mail_address' : {
        'type' : 'email',
    },
}
data = {
    'mail_address' : 'tanaka_at_test.com',
}

v = CustomValidator(schema)
pprint(v.validate(data))
pprint(v.errors)

実行結果

False
{'mail_address': ['must be of email type']}

エラーメッセージ 0x24: "must be of {constraint} type", の文言を個別に変更することはできませんが、これで用が足りる場合も多いでしょう。

2. バリデーションスキーマにバリデーション用メソッドを指定する (Custom Validator)

スキーマ定義にバリデーション用のメソッド名を指定し、メソッドの実行結果を受け取るイメージです。

schema = {
    'mail_address' : {
        'validator': check_mail,   # ←check_mail() メソッド
    },
}

これはソースコード見ていただいた方が早いですね。

# -*- coding: utf-8 -*-

import re
from pprint import pprint

from cerberus import Validator

REGEX_EMAIL = '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'

# [Custom Validator]
# @see http://docs.python-cerberus.org/en/stable/validation-rules.html#validator]
def check_mail(field, value, error):
    if not re.match(REGEX_EMAIL, value):
        msg = "指定された'%s'は、メールアドレスとして不正です。" % value
        error(field, msg)

schema = {
    'mail_address' : {
        'validator': check_mail,   # ←check_mail() メソッド
    },
}
data = {
    'mail_address' : 'tanaka_at_test.com',
}

v = Validator(schema)
pprint(v.validate(data))
pprint(v.errors)

実行結果

False
{'mail_address': ["指定された'tanaka_at_test.com'は、メールアドレスとして不正です。"]}

毎回これでやるのは大変そうですが、Validatorを拡張しなくていいのは非常に楽です。エラーメッセージも個別に指定できますし。

3. バリデーションルールを追加する (Custom Rules)

スキーマに独自のkey, valueを指定したい場合に使います。

schema = {
    'mail_address' : {
        'exists_keyword' : 'SUZUKI',
    },
}

上記2つと大きく違うのは、「スキーマで指定された値を取得できる」部分です。ここでは 「スキーマで指定されたキーワード(SUZUKI)が含まれていない場合、バリデーションを失敗させる」ようにします。

# -*- coding: utf-8 -*-

import re
from pprint import pprint

from cerberus import Validator

class CustomValidator(Validator):
    # [Custom Rules]
    # http://docs.python-cerberus.org/en/stable/customize.html#custom-rules
    def _validate_exists_keyword(self, exists_keyword, field, value):
        """
        ※以下のdocstringを書かないとwaringが出る

        The rule's arguments are validated against this schema: {'type': 'string'}
        """
        if value.find(exists_keyword) < 0:
            self._error(field, "与えられた文字列'%s'の中に、キーワード文字列'%s'が存在しませんでした。" % (value, exists_keyword))


schema = {
    'mail_address' : {
        'exists_keyword' : 'SUZUKI',
    },
}
data = {
    'mail_address' : 'tanaka_at_test.com',
}

v = CustomValidator(schema)
pprint(v.validate(data))
pprint(v.errors)

実行結果

False
{'mail_address': ["与えられた文字列'tanaka_at_test.com'の中に、キーワード文字列'SUZUKI'が存在しませんでした。"]}

docstringを書かないといけない部分がわかりにくいです。今回は {'exists_keyword' : 'SUZUKI'} とスキーマに指定したいので、'SUZUKI'に対応したデータ型 {'type': 'string'} をdocstringに記述しています。ちなみにThe rule's arguments are...の文字列は固定です。

記述しない場合、以下のようなwarningが発生します。
UserWarning: No validation schema is defined for the arguments of rule 'exists_keyword'

こちらは複雑な割に、どういう場合に使えばいいのかよくわからなかったのですが、「blacklistをスキーマに渡し、その中にマッチするものがあれば、バリデーションエラーにする」みたいなケースで使えるかもしれません。

なお、スキーマはdict形式でないといけないので、以下のようなカスタムルールを作ってもエラーになります。ご注意ください。

# ×NG
schema = {
    'mail_address' : {
        'has_bad_word',           # dict形式になっていない
    },
}

# ◯OK
schema = {
    'mail_address' : {
        'has_bad_word' : True,    # docstringで{'type': 'boolean'} 指定
    },
}

最後に

めんどくさいバリデーションですが、Cerberusによる「ケロちゃんチェック」で、幸せになりましょう!

あの決定的な敗戦から数十年――

94
68
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
94
68