バリデーションチェック、めんどくさいですよね。
最初はFormEncodeでやろうとしたのですが、「もうちょっと今っぽいやつないのかな……」と探したところ、とてもいい感じのライブラリがあったので、使い方をメモしておきます。ご参考になれば幸いです。
Cerberusとは
英語読みで「サーベラス」でいいんでしょうか? いわゆる「ケルベロス」のことで、「冥界の番犬」のようにデータの入り口を守る、というのが名前の由来のようです。厨ニっぽい かっこいいですね。
導入
$ pip install cerberus
使い方
サンプル見て頂くのが早いです。こんな感じに今っぽく書けます。
(Python 3.5.2 で動作確認しています)
# -*- 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による「ケロちゃんチェック」で、幸せになりましょう!
あの決定的な敗戦から数十年――