GitHubじゃ!Pythonじゃ!

GitHubからPython関係の優良リポジトリを探したかったのじゃー、でも英語は出来ないから日本語で読むのじゃー、英語社会世知辛いのじゃー

keleshev

schema – スキーマの検証はちょうどPythonicを得た

投稿日:

スキーマの検証はちょうどPythonicを得た

スキーマの検証はちょうど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呼び出しで変換されます、以下を参照)。

データが無効な場合、 SchemaSchemaError例外を発生させます。

インストール

pipまたはeasy_installを使用してください:

pip install schema

また、プロジェクトにschema.pyファイルをドロップすることもできます。これは自己完結型です。

Schemaデータの検証方法

タイプ

Schema(...)が型( intstrobjectなど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(...)listtuplesetまたは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ます。

ユーザーフレンドリーなエラー報告

有効なクラス( SchemaAndOrRegexUse )にキーワード引数errorを渡して、組み込みのクラスではなくこのエラーを報告することができます。

>>> Schema(Use(int, error='Invalid year')).validate('XVII')
Traceback (most recent call last):
...
SchemaError: Invalid year

自動生成されたエラーメッセージの例外のexc.autosアクセスし、 errorテキストが渡されたerrorexc.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変換しました。







-keleshev

執筆者: