cerberusとは

CERBERUS, n. The watch-dog of Hades, whose duty it was to guard the entrance; everybody, sooner or later, had to go there, and nobody wanted to carry off the entrance. - Ambrose Bierce, The Devil’s Dictionary

ケルベロスはシンプルで軽量なデータ検証機能を提供するパッケージです。
公式曰く、拡張も容易なため、カスタム検証も可能です。

(以下、9割公式のドキュメントの翻訳です)


概要

検証用のスキーマを定義し、それを Validator クラスのインスタンスに渡します。

schma = {'name': {'type': 'string'}}
v = Validator(schema)

次に、validate()を呼び出すことで、簡単にスキーマと辞書を検証できます。
検証が成功すると True が返されます。

document = {'name': 'john doe'}
v.validate(document)  # True

インストール

安定バージョンはpipから取得できます。
pip install cerberus


使い方

基本的な使い方

# 概要の通り。スキーマを定義し、Validatorクラスに渡して検証する。
>>> from cerberus import Validator
>>> schema = {'name': {'type': 'string'}}
>>> v = Validator(schema)
>>> document = {'name': 'john doe'}
>>> v.validate(document)
True

# スキーマと辞書を同時に渡すこともできる。
>>> v = Validator()
>>> v.validate(document, schema)
True

# Validatorクラスとそのインスタンスは呼び出し可能なので、以下の簡略構文が使える。
>>> v(document)
True

ケルベロスは、検証に問題があった時点(最初のバリデーションエラーが起きた時点)では停止しません(!)。
文書全体が常に処理されてからFalseを返します。その後、エラープロパティにアクセスすることで、起きた問題のリストを取得できます。
つまり検証したデータ内で起きたバリデーションエラーは全て取得できます

バリデーションエラーはValidator.errorsに格納されます。

>>> schema = {'name': {'type': 'string'}, 'age': {'type': 'integer', 'min': 10}}
>>> document = {'name': 400, 'age': 5}
>>> v.validate(document, schema)
False
>>> v.errors
{'age': ['min value is 10'], 'name': ['must be of string type']}

未知のものを許可

デフォルトでは、スキーマで定義された以外のキーは許可されません。
ただし、Validator でallow_unknown = Trueを設定することにより、スキーマに記述されていないキーを許可したり、検証をかけたりすることができます。

# デフォルトでは、スキーマで定義されたキーのみが許可される。
>>> schema = {'name': {'type': 'string', 'maxlength': 10}}
>>> v.validate({'name': 'john', 'sex': 'M'}, schema)
False
>>> v.errors
{'sex': ['unknown field']}

# allow_unknownをTrueに設定することで、不明なキーを許可できる。(検証はしない)
>>> v.schema = {}
>>> v.allow_unknown = True
>>> v.validate({'name': 'john', 'sex': 'M'})
True

# もしくは、検証スキーマに設定できる。この場合は未知のキーが検証される。
>>> v.schema = {}
>>> v.allow_unknown = {'type': 'string'}
>>> v.validate({'an_unknown_field': 1})
False
>>> v.errors
{'an_unknown_field': ['must be of string type']}

# 初期化時に設定することも可能。
>>> v = Validator({}, allow_unknown=True)
>>> v.validate({'name': 'john', 'sex': 'M'})
True

# スキーマのルールとして設定することもできる。ネストした先の辞書等にも使用可能。
>>> v = Validator()
>>> v.allow_unknown
False

>>> schema = {
...   'name': {'type': 'string'},
...   'a_dict': {
...     'type': 'dict',
...     'allow_unknown': True,  #a_dict辞書の未知のキーを許可
...     'schema': {
...       'address': {'type': 'string'}
...     }
...   }
... }

>>> v.validate({'name': 'john',
...             'a_dict': {'an_unknown_field': 'is allowed'}},
...            schema)
True

>>> v.validate({'name': 'john',
...             'an_unknown_field': 'is not allowed',  # 親の辞書はv.allow_unknown == False
...             'a_dict':{'an_unknown_field': 'is allowed'}},
...            schema)
False
>>> v.errors
{'an_unknown_field': ['unknown field']}

全てを要求する

デフォルトでは、スキーマで定義されたキーは不要です。
ただし、Validator でrequire_all = Trueを設定することにより、スキーマに記述した全てのキーを要求できます。

>>> v = Validator()
>>> v.require_all
False

>>> schema = {
...   'name': {'type': 'string'},
...   'a_dict': {
...     'type': 'dict',
...     'require_all': True,
...     'schema': {
...       'address': {'type': 'string'}
...     }
...   }
... }

>>> v.validate({'name': 'foo', 'a_dict': {}}, schema)
False
>>> v.errors
{'a_dict': [{'address': ['required field']}]}

>>> v.validate({'a_dict': {'address': 'foobar'}}, schema)
True

処理済みドキュメントの取得

# 検証されたドキュメントはドキュメントプロパティに格納される
>>> v.schema = {'amount': {'type': 'integer', 'coerce': int}}
>>> v.validate({'amount': '1'})
True
>>> v.document
{'amount': 1}

# validated()
# 検証済みの値のみを返す
>>> schema = {'name': {'type': 'string', 'maxlength': 10}}
>>> v = Validator(schema)
>>> v.validated({'name': 'hoge'})
{'name': 'hoge'}
>>> v.validated({'name': 200})
# None

# normalized()
# 検証せずにDocumentの正規化されたコピーを返す
>>> n = v.normalized({'name': 200})
>>> type(n['name'])
# <class 'int'>
>>> n = v.normalized({'name': 'hoge'})
>>> type(n['name'])
# <class 'str'>
>>> n
{'name': 'hoge'}

検証スキーマ

前述の通りですが、デフォルトの設定では、スキーマに書かれている全てのキーはオプションとして扱われます。
極端な話、デフォルトではどんなスキーマ相手でもdocument = {}で検証が通ります。
設定を変えるか、Validator.require_allを True にして全てのスキーマキーを要求することが望ましいです。

2つのレジスタ

# schema_registry
>>> from cerberus import schema_registry
>>> schema_registry.add('non-system user',
...                     {'uid': {'min': 1000, 'max': 0xffff}})
>>> schema = {'sender': {'schema': 'non-system user',
...                      'allow_unknown': True},
...           'receiver': {'schema': 'non-system user',
...                        'allow_unknown': True}}

# rules_set_registry
>>> from cerberus import rules_set_registry
>>> rules_set_registry.extend((('boolean', {'type': 'boolean'}),
...                            ('booleans', {'valuesrules': 'boolean'})))
>>> schema = {'foo': 'booleans'}

レジストリを設定、及びダンプするには、extend()及びall()を使用します。

検証

スキーマ自体の検証は、決まったタイミングのみ行われます。
(when passed to the validator or a new set of rules is set for a document’s field)

>>> v = Validator({'foo': {'allowed': []}})
>>> v.schema['foo'] = {'allowed': 1}
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "cerberus/schema.py", line 99, in __setitem__
    self.validate({key: value})
  File "cerberus/schema.py", line 126, in validate
    self._validate(schema)
  File "cerberus/schema.py", line 141, in _validate
    raise SchemaError(self.schema_validator.errors)
SchemaError: {'foo': {'allowed': 'must be of container type'}}

>>> v.schema['foo']['allowed'] = 'strings are no valid constraint for allowed'
>>> v.schema.validate()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "cerberus/schema.py", line 126, in validate
    self._validate(schema)
  File "cerberus/schema.py", line 141, in _validate
    raise SchemaError(self.schema_validator.errors)
SchemaError: {'foo': {'allowed': 'must be of container type'}}

Serialization

Cerberus スキーマは、Python の型(dict, list, stringなど)で構成されています。
ユーザ定義の検証ルールも、スキーマ内ではstring型としてその名前が呼び出されます。
これによって、PyYAML など、様々な方法でスキーマを定義することができます。

>>> import yaml
>>> schema_text = '''
... name:
...   type: string
... age:
...   type: integer
...   min: 10
... '''
>>> schema = yaml.load(schema_text)
>>> document = {'name': 'Little Joe', 'age': 5}
>>> v.validate(document, schema)
False
>>> v.errors
{'age': ['min value is 10']}

YAML 以外にも、ネストされた辞書を生成できるデコーダーがあれば、どんなシリアライザーも利用できます。
JSON とか。


検証ルール

基本は 👇 にまとまってます
Validation Rules — Cerberus is a lightweight and extensible data validation library for Python

valuesrules

検証される全てのデータに適応されるルールを定義します。

>>> schema = {'numbers':
...              {'type': 'dict',
...               'valuesrules': {'type': 'integer', 'min': 10}}
... }
>>> document = {'numbers': {'an integer': 10, 'another integer': 100}}
>>> v.validate(document, schema)
True

>>> document = {'numbers': {'an integer': 9}}
>>> v.validate(document, schema)
False

>>> v.errors
{'numbers': [{'an integer': ['min value is 10']}]}

エラー

ドキュメントが見つからない時、または無効な値の時

cerberus.validator.DocumentError

>>> document = ""
>>> v = Validator(schema)
>>> v(document)

cerberus.validator.DocumentError: '' is not a document, must be a dict

無効な検証スキーマを検出した場合

cerberus.schema.SchemaError

>>> schema = {"name": {"type": "hoge"}}
>>> v = Validator(schema)

cerberus.schema.SchemaError: {'name': [{'type': ['Unsupported types: hoge']}]}


参考

Python のバリデーションライブラリ「Cerberus」のよく使うバリデーションルールをまとめてみた | Developers.IO
Cerberus 入門!ばりばりバリデーションしよう! - hogehoge diary