Github: https://github.com/keleshev/schema
スキーマの検証はちょうどPythonicを得た
スキーマは、JSON / YAML(またはその他のもの)からPythonデータ型に変換された、設定ファイル、フォーム、外部サービスまたはコマンドライン解析から取得されたものなど、Pythonデータ構造を検証するためのライブラリです。
例
ここでは、 スキーマ感を得るための簡単な例を挙げ、個人情報を含むエントリのリストを検証します。
>>> from schema import Schema, And, Use, Optional
>>> schema = Schema([{'name': And(str, len),
... 'age': And(Use(int), lambda n: 18 <= n <= 99),
... Optional('gender'): And(str, Use(str.lower),
... lambda s: s in ('squid', 'kid'))}])
>>> data = [{'name': 'Sue', 'age': '28', 'gender': 'Squid'},
... {'name': 'Sam', 'age': '42'},
... {'name': 'Sacha', 'age': '20', 'gender': 'KID'}]
>>> validated = schema.validate(data)
>>> assert validated == [{'name': 'Sue', 'age': 28, 'gender': 'squid'},
... {'name': 'Sam', 'age': 42},
... {'name': 'Sacha', 'age' : 20, 'gender': 'kid'}]
データが有効な場合、 Schema.validate
は検証されたデータを返します(オプションでUse呼び出しで変換されます、以下を参照)。
データが無効な場合、 Schema
はSchemaError
例外を発生させます。
インストール
pipまたはeasy_installを使用してください:
pip install schema
また、プロジェクトにschema.py
ファイルをドロップすることもできます。これは自己完結型です。
- スキーマは、Python 2.6,2.7,3.2,3.3,3.4,3.5、およびPyPyでテストされています 。
- スキーマはセマンティックバージョニングに従います 。
Schema
データの検証方法
タイプ
Schema(...)
が型( int
、 str
、 object
などSchema(...)
遭遇すると、対応するデータがその型のインスタンスであるかどうかをチェックし、そうでなければSchemaError
ます。
>>> from schema import Schema
>>> Schema(int).validate(123)
123
>>> Schema(int).validate('123')
Traceback (most recent call last):
...
SchemaUnexpectedTypeError: '123' should be instance of 'int'
>>> Schema(object).validate('hai')
'hai'
呼び出し可能なもの
Schema(...)
がcallable(関数、クラス、または__call__
メソッドを持つオブジェクトSchema(...)
遭遇すると、それを呼び出し、戻り値がTrue
であれば検証を継続し、そうでなければSchemaError
ます。
>>> import os
>>> Schema(os.path.exists).validate('./')
'./'
>>> Schema(os.path.exists).validate('./non-existent/')
Traceback (most recent call last):
...
SchemaError: exists('./non-existent/') should evaluate to True
>>> Schema(lambda n: n > 0).validate(123)
123
>>> Schema(lambda n: n > 0).validate(-12)
Traceback (most recent call last):
...
SchemaError: <lambda>(-12) should evaluate to True
“Validatables”
Schema(...)
がvalidate
メソッドを持つオブジェクトに遭遇すると、 data = obj.validate(data)
として対応するデータに対してこのメソッドを実行しdata = obj.validate(data)
。 このメソッドは、 SchemaError
例外を発生させる可能性があります。この例外は、そのデータが無効であることをSchema
に通知します。そうでなければ、検証を続けます。
“validatable”の例は、文字列またはバッファを指定された正規表現(文字列、バッファまたはコンパイルされた正規表現SRE_Pattern
)と照合しようとするRegex
です:
>>> from schema import Regex
>>> import re
>>> Regex(r'^foo').validate('foobar')
'foobar'
>>> Regex(r'^[A-Z]+$', flags=re.I).validate('those-dashes-dont-match')
Traceback (most recent call last):
...
SchemaError: Regex('^[A-Z]+$', flags=re.IGNORECASE) does not match 'those-dashes-dont-match'
より一般的なケースでは、そのようなオブジェクトを作成Use
ためにUse
を使用Use
ことができます。 関数や型を使用して値を変換して検証します。
>>> from schema import Use
>>> Schema(Use(int)).validate('123')
123
>>> Schema(Use(lambda f: open(f, 'a'))).validate('LICENSE-MIT')
<open file 'LICENSE-MIT', mode 'a' at 0x...>
詳細をドロップUse
と、基本的には:
class Use(object):
def __init__(self, callable_):
self._callable = callable_
def validate(self, data):
try:
return self._callable(data)
except Exception as e:
raise SchemaError('%r raised %r' % (self._callable.__name__, e))
場合によっては、データの一部を変換して検証する必要がありますが、元のデータは変更しないでください。 Const
はデータを安全に保つのに役立ちます:
>> from schema import Use, Const, And, Schema
>> from datetime import datetime
>> is_future = lambda date: datetime.now() > date
>> to_json = lambda v: {"timestamp": v}
>> Schema(And(Const(And(Use(datetime.fromtimestamp), is_future)), Use(to_json))).validate(1234567890)
{"timestamp": 1234567890}
これで、独自の検証対応のクラスとデータ型を記述できます。
リスト、類似のコンテナ
Schema(...)
がlist
、 tuple
、 set
またはfrozenset
インスタンスを検出すると、対応するデータコンテナの内容がそのコンテナ内にリストされているスキーマに対して検証されます。
>>> Schema([1, 0]).validate([1, 1, 0, 1])
[1, 1, 0, 1]
>>> Schema((int, float)).validate((5, 7, 8, 'not int or float here'))
Traceback (most recent call last):
...
SchemaError: Or(<type 'int'>, <type 'float'>) did not validate 'not int or float here'
'not int or float here' should be instance of 'float'
辞書
Schema(...)
がdict
インスタンスに遭遇すると、データのキーと値のペアが検証されます。
>>> d = Schema({'name': str,
... 'age': lambda n: 18 <= n <= 99}).validate({'name': 'Sue', 'age': 28})
>>> assert d == {'name': 'Sue', 'age': 28}
スキーマとしてキーを指定することもできます:
>>> schema = Schema({str: int, # string keys should have integer values
... int: None}) # int keys should be always None
>>> data = schema.validate({'key1': 1, 'key2': 2,
... 10: None, 20: None})
>>> schema.validate({'key1': 1,
... 10: 'not None here'})
Traceback (most recent call last):
...
SchemaError: Key '10' error:
None does not match 'not None here'
特定のキー値をチェックしたいが、他のキー値は気にしない場合に便利です:
>>> schema = Schema({'<id>': int,
... '<file>': Use(open),
... str: object}) # don't care about other str keys
>>> data = schema.validate({'<id>': 10,
... '<file>': 'README.rst',
... '--verbose': True})
次のようにキーをオプションとしてマークすることができます。
>>> from schema import Optional
>>> Schema({'name': str,
... Optional('occupation'): str}).validate({'name': 'Sam'})
{'name': 'Sam'}
Optional
キーは、データ内のキーが一致しない場合に使用されるdefault
保持することもできdefault
。
>>> from schema import Optional
>>> Schema({Optional('color', default='blue'): str,
... str: str}).validate({'texture': 'furry'}
... ) == {'color': 'blue', 'texture': 'furry'}
True
デフォルトはそのまま使用され、値に指定されているバリデーターは渡されません。
次のように、キーを禁止としてマークすることができます。
>>> from schema import Forbidden
>>> Schema({Forbidden('age'): object}).validate({'age': 50})
Traceback (most recent call last):
...
SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50}
いくつか注意する価値があります。 まず、禁止キーとペアになった値が拒否されるかどうかが決まります。
>>> Schema({Forbidden('age'): str, 'age': int}).validate({'age': 50})
{'age': 50}
注:ここで「年齢」キーを指定しなかった場合も、呼び出しは失敗しますが、SchemaWrongKeyErrorではなくSchemaForbiddenKeyErrorで失敗します。
第2に、禁止は標準キーよりも優先順位が高く、したがってオプションです。 つまり、これを行うことができます:
>>> Schema({Forbidden('age'): object, Optional(str): object}).validate({'age': 50})
Traceback (most recent call last):
...
SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50}
スキーマにはクラスAnd
およびOr
があり、同じデータに対して複数のスキーマを検証するのに役立ちます。
>>> from schema import And, Or
>>> Schema({'age': And(int, lambda n: 0 < n < 99)}).validate({'age': 7})
{'age': 7}
>>> Schema({'password': And(str, lambda s: len(s) > 6)}).validate({'password': 'hai'})
Traceback (most recent call last):
...
SchemaError: Key 'password' error:
<lambda>('hai') should evaluate to True
>>> Schema(And(Or(int, float), lambda x: x > 0)).validate(3.1415)
3.1415
余分なキー
Schema(...)
パラメータignore_extra_keys
は、検証で辞書内の余分なキーを無視し、検証後に戻りません。
>>> schema = Schema({'name': str}, ignore_extra_keys=True)
>>> schema.validate({'name': 'Sam', 'age': '42'})
{'name': 'Sam'}
余分なキーが返されるようにするには、 object: object
をキーと値のペアの1つとして使用しobject: object
これは、任意のキーと任意の値に一致します。 それ以外の場合は、余分なキーがSchemaError
ます。
ユーザーフレンドリーなエラー報告
有効なクラス( Schema
、 And
、 Or
、 Regex
、 Use
)にキーワード引数error
を渡して、組み込みのクラスではなくこのエラーを報告することができます。
>>> Schema(Use(int, error='Invalid year')).validate('XVII')
Traceback (most recent call last):
...
SchemaError: Invalid year
自動生成されたエラーメッセージの例外のexc.autos
アクセスし、 error
テキストが渡されたerror
のexc.autos
アクセスすることによって発生したすべてのエラーを表示できます。
トレースバックなしでユーザーにメッセージを表示する場合は、 sys.exit(exc.code)
終了できます。 error
メッセージが優先されます。
JSON APIの例
簡単な例です:github APIから要点リクエストを作成する検証。
>>> gist = '''{"description": "the description for this gist",
... "public": true,
... "files": {
... "file1.txt": {"content": "String file contents"},
... "other.txt": {"content": "Another file contents"}}}'''
>>> from schema import Schema, And, Use, Optional
>>> import json
>>> gist_schema = Schema(And(Use(json.loads), # first convert from JSON
... # use basestring since json returns unicode
... {Optional('description'): basestring,
... 'public': bool,
... 'files': {basestring: {'content': basestring}}}))
>>> gist = gist_schema.validate(gist)
# gist:
{u'description': u'the description for this gist',
u'files': {u'file1.txt': {u'content': u'String file contents'},
u'other.txt': {u'content': u'Another file contents'}},
u'public': True}
docoptで スキーマを使用する
次の使用パターンでdocoptを使用しているとします。
使用法:my_program.py [–count = N] <パス> <ファイル> …
<files>
が読み取り可能であり、 <path>
が存在し、– --count
が0から5の整数かNone
ことを検証したいとします。
docoptが次のdictを返すと仮定します:
>>> args = {'<files>': ['LICENSE-MIT', 'setup.py'],
... '<path>': '../',
... '--count': '3'}
これは、 schema
を使用して検証する方法です。
>>> from schema import Schema, And, Or, Use
>>> import os
>>> s = Schema({'<files>': [Use(open)],
... '<path>': os.path.exists,
... '--count': Or(None, And(Use(int), lambda n: 0 < n < 5))})
>>> args = s.validate(args)
>>> args['<files>']
[<open file 'LICENSE-MIT', mode 'r' at 0x...>, <open file 'setup.py', mode 'r' at 0x...>]
>>> args['<path>']
'../'
>>> args['--count']
3
あなたが見ることができるように、 スキーマで検証されたデータは成功し、ファイルを開き、 '3'
をint
変換しました。