diff options
author | Julian Berman <Julian@GrayVines.com> | 2013-05-20 10:18:22 -0400 |
---|---|---|
committer | Julian Berman <Julian@GrayVines.com> | 2013-05-20 10:30:47 -0400 |
commit | 4d750d6845381092afca86e3d667671398e37943 (patch) | |
tree | 0007496bc2953afc71a66579ccf2f7c630a0d831 | |
parent | 7221a3229b378a3cdb08f9bd5c4a94688af8d559 (diff) | |
download | jsonschema-4d750d6845381092afca86e3d667671398e37943.tar.gz |
Make everything 5x slower.
Add jsonschema.validators.create, and reimplement ValidatorMixin in terms of
it. This makes all of the validators much much slower, but stay tuned for
magic.
-rw-r--r-- | jsonschema/tests/test_validators.py | 42 | ||||
-rw-r--r-- | jsonschema/validators.py | 177 |
2 files changed, 135 insertions, 84 deletions
diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 2fd1d91..219404a 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -9,10 +9,50 @@ from jsonschema.compat import PY3 from jsonschema.tests.compat import mock, unittest from jsonschema.validators import ( RefResolutionError, UnknownType, ValidationError, ErrorTree, - Draft3Validator, Draft4Validator, RefResolver, ValidatorMixin, validate, + Draft3Validator, Draft4Validator, RefResolver, ValidatorMixin, + create, validate, ) +class TestCreate(unittest.TestCase): + def setUp(self): + self.meta_schema = {"properties" : {"smelly" : {}}} + self.smelly = mock.MagicMock() + self.validators = {"smelly" : self.smelly} + self.types = {"dict" : dict} + self.Validator = create( + meta_schema=self.meta_schema, + validators=self.validators, + default_types=self.types, + ) + + self.validator_value = 12 + self.schema = {"smelly" : self.validator_value} + self.validator = self.Validator(self.schema) + + def test_attrs(self): + self.assertEqual(self.Validator.VALIDATORS, self.validators) + self.assertEqual(self.Validator.META_SCHEMA, self.meta_schema) + self.assertEqual(self.Validator.DEFAULT_TYPES, self.types) + + def test_init(self): + self.assertEqual(self.validator.schema, self.schema) + + def test_iter_errors(self): + instance = "hello" + + self.smelly.return_value = [] + self.assertEqual(list(self.validator.iter_errors(instance)), []) + + error = mock.Mock() + self.smelly.return_value = [error] + self.assertEqual(list(self.validator.iter_errors(instance)), [error]) + + self.smelly.assert_called_with( + self.validator_value, instance, self.schema, + ) + + class TestIterErrors(unittest.TestCase): def setUp(self): self.validator = Draft3Validator({}) diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 36601d5..e513aa3 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -136,99 +136,110 @@ def validates(version): return _validates -class ValidatorMixin(object): - """ - Concrete implementation of :class:`IValidator`. - - Provides default implementations of each method. Validation of schema - properties is dispatched to ``validate_property`` methods. E.g., to - implement a validator for a ``maximum`` property, create a - ``validate_maximum`` method. Validator methods should yield zero or more - :exc:`ValidationError``\s to signal failed validation. +def create(meta_schema, validators=(), default_types=None): # noqa + if default_types is None: + default_types = { + "array" : list, "boolean" : bool, "integer" : int_types, + "null" : type(None), "number" : numbers.Number, "object" : dict, + "string" : str_types, + } + + class Validator(object): + VALIDATORS = dict(validators) + META_SCHEMA = dict(meta_schema) + DEFAULT_TYPES = dict(default_types) + + def __init__( + self, schema, types=(), resolver=None, format_checker=None, + ): + self._types = dict(self.DEFAULT_TYPES) + self._types.update(types) + + if resolver is None: + resolver = RefResolver.from_schema(schema) + + self.resolver = resolver + self.format_checker = format_checker + self.schema = schema + + @classmethod + def check_schema(cls, schema): + for error in cls(cls.META_SCHEMA).iter_errors(schema): + raise SchemaError.create_from(error) + + def iter_errors(self, instance, _schema=None): + if _schema is None: + _schema = self.schema + + with self.resolver.in_scope(_schema.get("id", "")): + ref = _schema.get("$ref") + if ref is not None: + validators = [("$ref", ref)] + else: + validators = iteritems(_schema) + + for k, v in validators: + validator = self.VALIDATORS.get(k) + if validator is None: + continue + + errors = validator(v, instance, _schema) or () + for error in errors: + # set details if not already set by the called fn + error._set( + validator=k, + validator_value=v, + instance=instance, + schema=_schema, + ) + if k != "$ref": + error.schema_path.appendleft(k) + yield error - """ + def descend(self, instance, schema, path=None, schema_path=None): + for error in self.iter_errors(instance, schema): + if path is not None: + error.path.appendleft(path) + if schema_path is not None: + error.schema_path.appendleft(schema_path) + yield error - DEFAULT_TYPES = { - "array" : list, "boolean" : bool, "integer" : int_types, - "null" : type(None), "number" : numbers.Number, "object" : dict, - "string" : str_types, - } + def validate(self, *args, **kwargs): + for error in self.iter_errors(*args, **kwargs): + raise error - def __init__(self, schema, types=(), resolver=None, format_checker=None): - self._types = dict(self.DEFAULT_TYPES) - self._types.update(types) + def is_type(self, instance, type): + if type not in self._types: + raise UnknownType(type) + pytypes = self._types[type] - if resolver is None: - resolver = RefResolver.from_schema(schema) + # bool inherits from int, so ensure bools aren't reported as ints + if isinstance(instance, bool): + pytypes = _utils.flatten(pytypes) + is_number = any( + issubclass(pytype, numbers.Number) for pytype in pytypes + ) + if is_number and bool not in pytypes: + return False + return isinstance(instance, pytypes) - self.resolver = resolver - self.format_checker = format_checker - self.schema = schema + def is_valid(self, instance, _schema=None): + error = next(self.iter_errors(instance, _schema), None) + return error is None - def is_type(self, instance, type): - if type not in self._types: - raise UnknownType(type) - pytypes = self._types[type] + return Validator - # bool inherits from int, so ensure bools aren't reported as integers - if isinstance(instance, bool): - pytypes = _utils.flatten(pytypes) - num = any(issubclass(pytype, numbers.Number) for pytype in pytypes) - if num and bool not in pytypes: - return False - return isinstance(instance, pytypes) - def is_valid(self, instance, _schema=None): - error = next(self.iter_errors(instance, _schema), None) - return error is None +class ValidatorMixin(create(meta_schema={})): + def __init__(self, *args, **kwargs): + super(ValidatorMixin, self).__init__(*args, **kwargs) - @classmethod - def check_schema(cls, schema): - for error in cls(cls.META_SCHEMA).iter_errors(schema): - raise SchemaError.create_from(error) - - def iter_errors(self, instance, _schema=None): - if _schema is None: - _schema = self.schema - - with self.resolver.in_scope(_schema.get("id", "")): - ref = _schema.get("$ref") - if ref is not None: - validators = [("$ref", ref)] - else: - validators = iteritems(_schema) - - for k, v in validators: - validator_attr = "validate_%s" % (k.lstrip("$"),) - validator = getattr(self, validator_attr, None) - - if validator is None: - continue - - errors = validator(v, instance, _schema) or () - for error in errors: - # set details if they weren't already set by the called fn - error._set( - validator=k, - validator_value=v, - instance=instance, - schema=_schema, - ) - if k != "$ref": - error.schema_path.appendleft(k) - yield error + class _VALIDATORS(dict): + def __missing__(this, key, dflt=None): + return getattr(self, "validate_" + str(key).lstrip("$"), dflt) + get = __missing__ - def descend(self, instance, schema, path=None, schema_path=None): - for error in self.iter_errors(instance, schema): - if path is not None: - error.path.appendleft(path) - if schema_path is not None: - error.schema_path.appendleft(schema_path) - yield error - - def validate(self, *args, **kwargs): - for error in self.iter_errors(*args, **kwargs): - raise error + self.VALIDATORS = _VALIDATORS() class _Draft34CommonMixin(object): |