diff -Nru python-schema-0.6.6/debian/changelog python-schema-0.6.7/debian/changelog --- python-schema-0.6.6/debian/changelog 2017-07-10 09:50:16.000000000 +0000 +++ python-schema-0.6.7/debian/changelog 2018-02-22 09:41:28.000000000 +0000 @@ -1,3 +1,20 @@ +python-schema (0.6.7-1) unstable; urgency=medium + + [ Ondřej Nový ] + * d/control: Set Vcs-* to salsa.debian.org + + [ Ghislain Antony Vaillant ] + * Update the gbp configuration + * New upstream version 0.6.7 + * Fixup whitespacing in rules file + * Support the nocheck build profile + * Filter egg-info with extend-diff-ignore + * Bump the debhelper version to 11 + * Bump the standards version to 4.1.3 + * Update the copyright years + + -- Ghislain Antony Vaillant Thu, 22 Feb 2018 09:41:28 +0000 + python-schema (0.6.6-2) unstable; urgency=medium * Upgrade watch file to version 4 diff -Nru python-schema-0.6.6/debian/compat python-schema-0.6.7/debian/compat --- python-schema-0.6.6/debian/compat 2017-07-10 09:50:16.000000000 +0000 +++ python-schema-0.6.7/debian/compat 2018-02-22 09:41:28.000000000 +0000 @@ -1 +1 @@ -10 +11 diff -Nru python-schema-0.6.6/debian/control python-schema-0.6.7/debian/control --- python-schema-0.6.6/debian/control 2017-07-10 09:50:16.000000000 +0000 +++ python-schema-0.6.7/debian/control 2018-02-22 09:41:28.000000000 +0000 @@ -3,20 +3,21 @@ Uploaders: Ghislain Antony Vaillant Section: python Priority: optional -Build-Depends: debhelper (>= 10), +Build-Depends: debhelper (>= 11), dh-python, + dpkg-dev (>= 1.17.14), pypy, - pypy-pytest, + pypy-pytest , pypy-setuptools, python-all, - python-pytest, + python-pytest , python-setuptools, python3-all, - python3-pytest, + python3-pytest , python3-setuptools -Standards-Version: 4.0.0 -Vcs-Browser: https://anonscm.debian.org/git/python-modules/packages/python-schema.git -Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/python-schema.git +Standards-Version: 4.1.3 +Vcs-Browser: https://salsa.debian.org/python-team/modules/python-schema +Vcs-Git: https://salsa.debian.org/python-team/modules/python-schema.git Homepage: https://github.com/keleshev/schema X-Python-Version: >= 2.7 X-Python3-Version: >= 3.2 diff -Nru python-schema-0.6.6/debian/copyright python-schema-0.6.7/debian/copyright --- python-schema-0.6.6/debian/copyright 2017-07-10 09:50:16.000000000 +0000 +++ python-schema-0.6.7/debian/copyright 2018-02-22 09:41:28.000000000 +0000 @@ -8,7 +8,7 @@ License: Expat Files: debian/* -Copyright: 2016 Ghislain Antony Vaillant +Copyright: 2016-2018 Ghislain Antony Vaillant License: Expat License: Expat diff -Nru python-schema-0.6.6/debian/gbp.conf python-schema-0.6.7/debian/gbp.conf --- python-schema-0.6.6/debian/gbp.conf 2017-07-10 09:50:16.000000000 +0000 +++ python-schema-0.6.7/debian/gbp.conf 2018-02-22 09:41:28.000000000 +0000 @@ -3,7 +3,5 @@ debian-branch = debian/master upstream-tag = upstream/%(version)s debian-tag = debian/%(version)s +sign-tags = True pristine-tar = True - -[pq] -patch-numbers = False diff -Nru python-schema-0.6.6/debian/rules python-schema-0.6.7/debian/rules --- python-schema-0.6.6/debian/rules 2017-07-10 09:50:16.000000000 +0000 +++ python-schema-0.6.7/debian/rules 2018-02-22 09:41:28.000000000 +0000 @@ -1,11 +1,12 @@ #! /usr/bin/make -f # Uncomment this to turn on verbose mode. -#export DH_VERBOSE = 1 +#export DH_VERBOSE=1 -export PYBUILD_NAME = schema -export PYBUILD_BEFORE_TEST = cp {dir}/test_schema.py {dir}/LICENSE-MIT {build_dir} -export PYBUILD_AFTER_TEST = rm {build_dir}/test_schema.py {build_dir}/LICENSE-MIT +export PYBUILD_NAME=schema + +export PYBUILD_BEFORE_TEST=cp {dir}/test_schema.py {dir}/LICENSE-MIT {build_dir} +export PYBUILD_AFTER_TEST=rm {build_dir}/test_schema.py {build_dir}/LICENSE-MIT %: dh $@ --with python2,python3,pypy --buildsystem=pybuild diff -Nru python-schema-0.6.6/debian/source/options python-schema-0.6.7/debian/source/options --- python-schema-0.6.6/debian/source/options 1970-01-01 00:00:00.000000000 +0000 +++ python-schema-0.6.7/debian/source/options 2018-02-22 09:41:28.000000000 +0000 @@ -0,0 +1 @@ +extend-diff-ignore="^[^/]+\.egg-info/" diff -Nru python-schema-0.6.6/PKG-INFO python-schema-0.6.7/PKG-INFO --- python-schema-0.6.6/PKG-INFO 2017-04-26 09:27:42.000000000 +0000 +++ python-schema-0.6.7/PKG-INFO 2018-01-09 02:04:34.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: schema -Version: 0.6.6 +Version: 0.6.7 Summary: Simple data validation library Home-page: https://github.com/keleshev/schema Author: Vladimir Keleshev @@ -32,18 +32,18 @@ >>> schema = Schema([{'name': And(str, len), ... 'age': And(Use(int), lambda n: 18 <= n <= 99), - ... Optional('sex'): And(str, Use(str.lower), - ... lambda s: s in ('male', 'female'))}]) + ... Optional('gender'): And(str, Use(str.lower), + ... lambda s: s in ('squid', 'kid'))}]) - >>> data = [{'name': 'Sue', 'age': '28', 'sex': 'FEMALE'}, + >>> data = [{'name': 'Sue', 'age': '28', 'gender': 'Squid'}, ... {'name': 'Sam', 'age': '42'}, - ... {'name': 'Sacha', 'age': '20', 'sex': 'Male'}] + ... {'name': 'Sacha', 'age': '20', 'gender': 'KID'}] >>> validated = schema.validate(data) - >>> assert validated == [{'name': 'Sue', 'age': 28, 'sex': 'female'}, + >>> assert validated == [{'name': 'Sue', 'age': 28, 'gender': 'squid'}, ... {'name': 'Sam', 'age': 42}, - ... {'name': 'Sacha', 'age' : 20, 'sex': 'male'}] + ... {'name': 'Sacha', 'age' : 20, 'gender': 'kid'}] If data is valid, ``Schema.validate`` will return the validated data @@ -62,7 +62,7 @@ Alternatively, you can just drop ``schema.py`` file into your project—it is self-contained. - - **schema** is tested with Python 2.6, 2.7, 3.2, 3.3 and PyPy. + - **schema** is tested with Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 and PyPy. - **schema** follows `semantic versioning `_. How ``Schema`` validates data @@ -170,6 +170,23 @@ except Exception as e: raise SchemaError('%r raised %r' % (self._callable.__name__, e)) + + Sometimes you need to transform and validate part of data, but keep original data unchanged. + ``Const`` helps to keep your data safe: + + .. code:: python + + >> 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} + Now you can write your own validation-aware classes and data types. Lists, similar containers @@ -257,6 +274,37 @@ Defaults are used verbatim, not passed through any validators specified in the value. + You can mark a key as forbidden as follows: + + .. code:: python + + >>> from schema import Forbidden + >>> Schema({Forbidden('age'): object}).validate({'age': 50}) + Traceback (most recent call last): + ... + SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50} + + A few things are worth noting. First, the value paired with the forbidden + key determines whether it will be rejected: + + .. code:: python + + >>> Schema({Forbidden('age'): str, 'age': int}).validate({'age': 50}) + {'age': 50} + + Note: if we hadn't supplied the 'age' key here, the call would have failed too, but with + SchemaWrongKeyError, not SchemaForbiddenKeyError. + + Second, Forbidden has a higher priority than standard keys, and consequently than Optional. + This means we can do that: + + .. code:: python + + >>> Schema({Forbidden('age'): object, Optional(str): object}).validate({'age': 50}) + Traceback (most recent call last): + ... + SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50} + **schema** has classes ``And`` and ``Or`` that help validating several schemas for the same data: diff -Nru python-schema-0.6.6/README.rst python-schema-0.6.7/README.rst --- python-schema-0.6.6/README.rst 2017-04-26 09:27:25.000000000 +0000 +++ python-schema-0.6.7/README.rst 2018-01-09 02:03:54.000000000 +0000 @@ -24,18 +24,18 @@ >>> schema = Schema([{'name': And(str, len), ... 'age': And(Use(int), lambda n: 18 <= n <= 99), - ... Optional('sex'): And(str, Use(str.lower), - ... lambda s: s in ('male', 'female'))}]) + ... Optional('gender'): And(str, Use(str.lower), + ... lambda s: s in ('squid', 'kid'))}]) - >>> data = [{'name': 'Sue', 'age': '28', 'sex': 'FEMALE'}, + >>> data = [{'name': 'Sue', 'age': '28', 'gender': 'Squid'}, ... {'name': 'Sam', 'age': '42'}, - ... {'name': 'Sacha', 'age': '20', 'sex': 'Male'}] + ... {'name': 'Sacha', 'age': '20', 'gender': 'KID'}] >>> validated = schema.validate(data) - >>> assert validated == [{'name': 'Sue', 'age': 28, 'sex': 'female'}, + >>> assert validated == [{'name': 'Sue', 'age': 28, 'gender': 'squid'}, ... {'name': 'Sam', 'age': 42}, - ... {'name': 'Sacha', 'age' : 20, 'sex': 'male'}] + ... {'name': 'Sacha', 'age' : 20, 'gender': 'kid'}] If data is valid, ``Schema.validate`` will return the validated data @@ -54,7 +54,7 @@ Alternatively, you can just drop ``schema.py`` file into your project—it is self-contained. -- **schema** is tested with Python 2.6, 2.7, 3.2, 3.3 and PyPy. +- **schema** is tested with Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 and PyPy. - **schema** follows `semantic versioning `_. How ``Schema`` validates data @@ -162,6 +162,23 @@ except Exception as e: raise SchemaError('%r raised %r' % (self._callable.__name__, e)) + +Sometimes you need to transform and validate part of data, but keep original data unchanged. +``Const`` helps to keep your data safe: + +.. code:: python + + >> 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} + Now you can write your own validation-aware classes and data types. Lists, similar containers @@ -249,6 +266,37 @@ Defaults are used verbatim, not passed through any validators specified in the value. +You can mark a key as forbidden as follows: + +.. code:: python + + >>> from schema import Forbidden + >>> Schema({Forbidden('age'): object}).validate({'age': 50}) + Traceback (most recent call last): + ... + SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50} + +A few things are worth noting. First, the value paired with the forbidden +key determines whether it will be rejected: + +.. code:: python + + >>> Schema({Forbidden('age'): str, 'age': int}).validate({'age': 50}) + {'age': 50} + +Note: if we hadn't supplied the 'age' key here, the call would have failed too, but with +SchemaWrongKeyError, not SchemaForbiddenKeyError. + +Second, Forbidden has a higher priority than standard keys, and consequently than Optional. +This means we can do that: + +.. code:: python + + >>> Schema({Forbidden('age'): object, Optional(str): object}).validate({'age': 50}) + Traceback (most recent call last): + ... + SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50} + **schema** has classes ``And`` and ``Or`` that help validating several schemas for the same data: diff -Nru python-schema-0.6.6/schema.egg-info/PKG-INFO python-schema-0.6.7/schema.egg-info/PKG-INFO --- python-schema-0.6.6/schema.egg-info/PKG-INFO 2017-04-26 09:27:42.000000000 +0000 +++ python-schema-0.6.7/schema.egg-info/PKG-INFO 2018-01-09 02:04:34.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: schema -Version: 0.6.6 +Version: 0.6.7 Summary: Simple data validation library Home-page: https://github.com/keleshev/schema Author: Vladimir Keleshev @@ -32,18 +32,18 @@ >>> schema = Schema([{'name': And(str, len), ... 'age': And(Use(int), lambda n: 18 <= n <= 99), - ... Optional('sex'): And(str, Use(str.lower), - ... lambda s: s in ('male', 'female'))}]) + ... Optional('gender'): And(str, Use(str.lower), + ... lambda s: s in ('squid', 'kid'))}]) - >>> data = [{'name': 'Sue', 'age': '28', 'sex': 'FEMALE'}, + >>> data = [{'name': 'Sue', 'age': '28', 'gender': 'Squid'}, ... {'name': 'Sam', 'age': '42'}, - ... {'name': 'Sacha', 'age': '20', 'sex': 'Male'}] + ... {'name': 'Sacha', 'age': '20', 'gender': 'KID'}] >>> validated = schema.validate(data) - >>> assert validated == [{'name': 'Sue', 'age': 28, 'sex': 'female'}, + >>> assert validated == [{'name': 'Sue', 'age': 28, 'gender': 'squid'}, ... {'name': 'Sam', 'age': 42}, - ... {'name': 'Sacha', 'age' : 20, 'sex': 'male'}] + ... {'name': 'Sacha', 'age' : 20, 'gender': 'kid'}] If data is valid, ``Schema.validate`` will return the validated data @@ -62,7 +62,7 @@ Alternatively, you can just drop ``schema.py`` file into your project—it is self-contained. - - **schema** is tested with Python 2.6, 2.7, 3.2, 3.3 and PyPy. + - **schema** is tested with Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 and PyPy. - **schema** follows `semantic versioning `_. How ``Schema`` validates data @@ -170,6 +170,23 @@ except Exception as e: raise SchemaError('%r raised %r' % (self._callable.__name__, e)) + + Sometimes you need to transform and validate part of data, but keep original data unchanged. + ``Const`` helps to keep your data safe: + + .. code:: python + + >> 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} + Now you can write your own validation-aware classes and data types. Lists, similar containers @@ -257,6 +274,37 @@ Defaults are used verbatim, not passed through any validators specified in the value. + You can mark a key as forbidden as follows: + + .. code:: python + + >>> from schema import Forbidden + >>> Schema({Forbidden('age'): object}).validate({'age': 50}) + Traceback (most recent call last): + ... + SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50} + + A few things are worth noting. First, the value paired with the forbidden + key determines whether it will be rejected: + + .. code:: python + + >>> Schema({Forbidden('age'): str, 'age': int}).validate({'age': 50}) + {'age': 50} + + Note: if we hadn't supplied the 'age' key here, the call would have failed too, but with + SchemaWrongKeyError, not SchemaForbiddenKeyError. + + Second, Forbidden has a higher priority than standard keys, and consequently than Optional. + This means we can do that: + + .. code:: python + + >>> Schema({Forbidden('age'): object, Optional(str): object}).validate({'age': 50}) + Traceback (most recent call last): + ... + SchemaForbiddenKeyError: Forbidden key encountered: 'age' in {'age': 50} + **schema** has classes ``And`` and ``Or`` that help validating several schemas for the same data: diff -Nru python-schema-0.6.6/schema.py python-schema-0.6.7/schema.py --- python-schema-0.6.6/schema.py 2017-04-26 09:27:36.000000000 +0000 +++ python-schema-0.6.7/schema.py 2018-01-09 02:04:27.000000000 +0000 @@ -4,12 +4,13 @@ import re -__version__ = '0.6.6' +__version__ = '0.6.7' __all__ = ['Schema', - 'And', 'Or', 'Regex', 'Optional', 'Use', + 'And', 'Or', 'Regex', 'Optional', 'Use', 'Forbidden', 'Const', 'SchemaError', 'SchemaWrongKeyError', 'SchemaMissingKeyError', + 'SchemaForbiddenKeyError', 'SchemaUnexpectedTypeError'] @@ -54,6 +55,12 @@ pass +class SchemaForbiddenKeyError(SchemaError): + """Error should be raised when a forbidden key is found within the + data set being validated, and its value matches the value that was specified""" + pass + + class SchemaUnexpectedTypeError(SchemaError): """Error should be raised when a type mismatch is detected within the data set being validated.""" @@ -218,6 +225,8 @@ @staticmethod def _dict_key_priority(s): """Return priority for a given key object.""" + if isinstance(s, Forbidden): + return _priority(s._schema) - 0.5 if isinstance(s, Optional): return _priority(s._schema) + 0.5 return _priority(s) @@ -246,17 +255,33 @@ except SchemaError: pass else: - try: - nvalue = Schema(svalue, error=e, - ignore_extra_keys=i).validate(value) - except SchemaError as x: - k = "Key '%s' error:" % nkey - raise SchemaError([k] + x.autos, [e] + x.errors) + if isinstance(skey, Forbidden): + # As the content of the value makes little sense for + # forbidden keys, we reverse its meaning: + # we will only raise the SchemaErrorForbiddenKey + # exception if the value does match, allowing for + # excluding a key only if its value has a certain type, + # and allowing Forbidden to work well in combination + # with Optional. + try: + nvalue = Schema(svalue, error=e).validate(value) + except SchemaError: + continue + raise SchemaForbiddenKeyError( + 'Forbidden key encountered: %r in %r' % + (nkey, data), e) else: - new[nkey] = nvalue - coverage.add(skey) - break - required = set(k for k in s if type(k) is not Optional) + try: + nvalue = Schema(svalue, error=e, + ignore_extra_keys=i).validate(value) + except SchemaError as x: + k = "Key '%s' error:" % nkey + raise SchemaError([k] + x.autos, [e] + x.errors) + else: + new[nkey] = nvalue + coverage.add(skey) + break + required = set(k for k in s if type(k) not in [Optional, Forbidden]) if not required.issubset(coverage): missing_keys = required - coverage s_missing_keys = \ @@ -331,6 +356,27 @@ self.default = default self.key = self._schema + def __hash__(self): + return hash(self._schema) + + def __eq__(self, other): + return (self.__class__ is other.__class__ and + getattr(self, 'default', self._MARKER) == + getattr(other, 'default', self._MARKER) and + self._schema == other._schema) + + +class Forbidden(Schema): + def __init__(self, *args, **kwargs): + super(Forbidden, self).__init__(*args, **kwargs) + self.key = self._schema + + +class Const(Schema): + def validate(self, data): + super(Const, self).validate(data) + return data + def _callable_str(callable_): if hasattr(callable_, '__name__'): diff -Nru python-schema-0.6.6/test_schema.py python-schema-0.6.7/test_schema.py --- python-schema-0.6.6/test_schema.py 2017-04-26 09:27:25.000000000 +0000 +++ python-schema-0.6.7/test_schema.py 2018-01-09 02:03:54.000000000 +0000 @@ -1,5 +1,6 @@ from __future__ import with_statement from collections import defaultdict, namedtuple +from functools import partial from operator import methodcaller import os import re @@ -8,9 +9,10 @@ from pytest import raises -from schema import (Schema, Use, And, Or, Regex, Optional, +from schema import (Schema, Use, And, Or, Regex, Optional, Const, SchemaError, SchemaWrongKeyError, - SchemaMissingKeyError, SchemaUnexpectedTypeError) + SchemaMissingKeyError, SchemaUnexpectedTypeError, + SchemaForbiddenKeyError, Forbidden) if sys.version_info[0] == 3: basestring = str # Python 3 does not have basestring @@ -75,6 +77,28 @@ with SE: Or().validate(2) +def test_test(): + def unique_list(_list): + return len(_list) == len(set(_list)) + + def dict_keys(key, _list): + return list(map(lambda d: d[key], _list)) + + schema = ( + Schema( + Const( + And(Use(partial(dict_keys, "index")), unique_list)))) + data = [ + {"index": 1, "value": "foo"}, + {"index": 2, "value": "bar"}] + assert schema.validate(data) == data + + bad_data = [ + {"index": 1, "value": "foo"}, + {"index": 1, "value": "bar"}] + with SE: schema.validate(bad_data) + + def test_regex(): # Simple case: validate string assert Regex(r'foo').validate('afoot') == 'afoot' @@ -218,6 +242,18 @@ {'key': 5, 'dk': {'a': 'a', 'bad': 'b'}} +def test_dict_forbidden_keys(): + with raises(SchemaForbiddenKeyError): + Schema({Forbidden('b'): object}).validate({'b': 'bye'}) + with raises(SchemaWrongKeyError): + Schema({Forbidden('b'): int}).validate({'b': 'bye'}) + assert (Schema({Forbidden('b'): int, + Optional('b'): object}).validate({'b': 'bye'}) == + {'b': 'bye'}) + with raises(SchemaForbiddenKeyError): + Schema({Forbidden('b'): object, Optional('b'): object}).validate({'b': 'bye'}) + + def test_dict_optional_keys(): with SE: Schema({'a': 1, 'b': 2}).validate({'a': 1}) assert Schema({'a': 1, Optional('b'): 2}).validate({'a': 1}) == {'a': 1} @@ -226,6 +262,8 @@ # Make sure Optionals are favored over types: assert Schema({basestring: 1, Optional('b'): 2}).validate({'a': 1, 'b': 2}) == {'a': 1, 'b': 2} + # Make sure Optionals hash based on their key: + assert len({Optional('a'): 1, Optional('a'): 1, Optional('b'): 2}) == 2 def test_dict_optional_defaults():