An initial stab at making jsonschema Python 3.x compatible.

This takes the approach of being Python 2.6, 2.7, 3.1 and 3.2
compatible from an identical code base, i.e. not by requiring an
explicit 2to3 step.  With this approach it is almost impossible to
also support Python 2.5, though that can be investigated if that is a
hard requirement.

The testing framework was changed from Twisted.trial to nosetests,
since Twisted does not yet have Python 3.x support.  Alternatively,
pytest could be used.

Most changes are related to adding "from __future__ import
unicode_literals" and removing all of the "u" prefixes on string
literals.

Since 3.x drops renames dict.iteritems to dict.items, a function
"iteritems" was added to handle either case.

Likewise, itertools.izip was dropped in favor of just using zip.

Comparisions of strings and numbers no longer works, so the string is
forcibly converted to a float before doing a numeric comparison.

Updated "try .. except" to use the new "Exception as e" syntax.

Python 3 changed the way metaclasses are handled.  The metaclass in
tests.py (there are none in the library proper) now uses a crazy
inscrutable syntax that is Python 2.x and 3.x compatible.  See
http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/

There is one doctest failing on Python 3.x that fails due to the fact
that in Python 3 the full path to the Exception object is shown in
tracebacks, i.e. jsonschema.ValidationError vs. ValidationError.  I'm
not sure how to resolve this in a way that is both Python 2 and 3
compatible.  We may just want to skip the doctests on Python 3.
This commit is contained in:
Michael Droettboom
2012-04-20 15:22:23 -04:00
parent f72f335455
commit 267a9152c6
4 changed files with 297 additions and 267 deletions

View File

@@ -46,9 +46,9 @@ Features
>>> try:
... validate([2, 3, 4], schema, stop_on_error=False)
... except ValidationError as e:
... print "Validation failed with errors:"
... print("Validation failed with errors:")
... for error in sorted(e.errors):
... print " * " + error
... print(" * " + error)
Validation failed with errors:
* 4 is not one of [1, 2, 3]
* [2, 3, 4] is too long

View File

@@ -10,13 +10,27 @@ under the JSON Schema specification. See its docstring for details.
"""
from __future__ import division, with_statement
from __future__ import division, with_statement, unicode_literals
import itertools
import re
import types
import sys
import warnings
# For Python 3
try:
unicode
except NameError:
basestring = unicode = str
if sys.version_info[0] >= 3:
def iteritems(d):
return d.items()
else:
def iteritems(d):
return d.iteritems()
from itertools import izip as zip
def _uniq(container):
"""
Check if all of a container's elements are unique.
@@ -33,7 +47,7 @@ def _uniq(container):
try:
sort = sorted(container)
sliced = itertools.islice(container, 1, None)
for i, j in itertools.izip(container, sliced):
for i, j in zip(container, sliced):
if i == j:
return False
except (NotImplementedError, TypeError):
@@ -62,85 +76,85 @@ except NameError: # pragma: no cover
DRAFT_3 = {
u"$schema" : u"http://json-schema.org/draft-03/schema#",
u"id" : u"http://json-schema.org/draft-03/schema#",
u"type" : u"object",
"$schema" : "http://json-schema.org/draft-03/schema#",
"id" : "http://json-schema.org/draft-03/schema#",
"type" : "object",
u"properties" : {
u"type" : {
u"type" : [u"string", u"array"],
u"items" : {u"type" : [u"string", {u"$ref" : u"#"}]},
u"uniqueItems" : True,
u"default" : u"any"
"properties" : {
"type" : {
"type" : ["string", "array"],
"items" : {"type" : ["string", {"$ref" : "#"}]},
"uniqueItems" : True,
"default" : "any"
},
u"properties" : {
u"type" : u"object",
u"additionalProperties" : {u"$ref" : u"#"},
u"default" : {}
"properties" : {
"type" : "object",
"additionalProperties" : {"$ref" : "#"},
"default" : {}
},
u"patternProperties" : {
u"type" : u"object",
u"additionalProperties" : {u"$ref" : u"#"},
u"default" : {}
"patternProperties" : {
"type" : "object",
"additionalProperties" : {"$ref" : "#"},
"default" : {}
},
u"additionalProperties" : {
u"type" : [{u"$ref" : u"#"}, u"boolean"], u"default" : {}
"additionalProperties" : {
"type" : [{"$ref" : "#"}, "boolean"], "default" : {}
},
u"items" : {
u"type" : [{u"$ref" : u"#"}, u"array"],
u"items" : {u"$ref" : u"#"},
u"default" : {}
"items" : {
"type" : [{"$ref" : "#"}, "array"],
"items" : {"$ref" : "#"},
"default" : {}
},
u"additionalItems" : {
u"type" : [{u"$ref" : u"#"}, u"boolean"], u"default" : {}
"additionalItems" : {
"type" : [{"$ref" : "#"}, "boolean"], "default" : {}
},
u"required" : {u"type" : u"boolean", u"default" : False},
u"dependencies" : {
u"type" : u"object",
u"additionalProperties" : {
u"type" : [u"string", u"array", {u"$ref" : u"#"}],
u"items" : {u"type" : "string"}
"required" : {"type" : "boolean", "default" : False},
"dependencies" : {
"type" : "object",
"additionalProperties" : {
"type" : ["string", "array", {"$ref" : "#"}],
"items" : {"type" : "string"}
},
u"default" : {}
"default" : {}
},
u"minimum" : {u"type" : u"number"},
u"maximum" : {u"type" : u"number"},
u"exclusiveMinimum" : {u"type" : u"boolean", u"default" : False},
u"exclusiveMaximum" : {u"type" : u"boolean", u"default" : False},
u"minItems" : {u"type" : u"integer", u"minimum" : 0, u"default" : 0},
u"maxItems" : {u"type" : u"integer", u"minimum" : 0},
u"uniqueItems" : {u"type" : u"boolean", u"default" : False},
u"pattern" : {u"type" : u"string", u"format" : u"regex"},
u"minLength" : {u"type" : u"integer", u"minimum" : 0, u"default" : 0},
u"maxLength" : {u"type" : u"integer"},
u"enum" : {u"type" : u"array", u"minItems" : 1, u"uniqueItems" : True},
u"default" : {u"type" : u"any"},
u"title" : {u"type" : u"string"},
u"description" : {u"type" : u"string"},
u"format" : {u"type" : u"string"},
u"maxDecimal" : {u"type" : u"number", u"minimum" : 0},
u"divisibleBy" : {
u"type" : u"number",
u"minimum" : 0,
u"exclusiveMinimum" : True,
u"default" : 1
"minimum" : {"type" : "number"},
"maximum" : {"type" : "number"},
"exclusiveMinimum" : {"type" : "boolean", "default" : False},
"exclusiveMaximum" : {"type" : "boolean", "default" : False},
"minItems" : {"type" : "integer", "minimum" : 0, "default" : 0},
"maxItems" : {"type" : "integer", "minimum" : 0},
"uniqueItems" : {"type" : "boolean", "default" : False},
"pattern" : {"type" : "string", "format" : "regex"},
"minLength" : {"type" : "integer", "minimum" : 0, "default" : 0},
"maxLength" : {"type" : "integer"},
"enum" : {"type" : "array", "minItems" : 1, "uniqueItems" : True},
"default" : {"type" : "any"},
"title" : {"type" : "string"},
"description" : {"type" : "string"},
"format" : {"type" : "string"},
"maxDecimal" : {"type" : "number", "minimum" : 0},
"divisibleBy" : {
"type" : "number",
"minimum" : 0,
"exclusiveMinimum" : True,
"default" : 1
},
u"disallow" : {
u"type" : [u"string", u"array"],
u"items" : {u"type" : [u"string", {u"$ref" : u"#"}]},
u"uniqueItems" : True
"disallow" : {
"type" : ["string", "array"],
"items" : {"type" : ["string", {"$ref" : "#"}]},
"uniqueItems" : True
},
u"extends" : {
u"type" : [{u"$ref" : u"#"}, u"array"],
u"items" : {u"$ref" : u"#"},
u"default" : {}
"extends" : {
"type" : [{"$ref" : "#"}, "array"],
"items" : {"$ref" : "#"},
"default" : {}
},
u"id" : {u"type" : u"string", u"format" : u"uri"},
u"$ref" : {u"type" : u"string", u"format" : u"uri"},
u"$schema" : {u"type" : u"string", u"format" : u"uri"},
"id" : {"type" : "string", "format" : "uri"},
"$ref" : {"type" : "string", "format" : "uri"},
"$schema" : {"type" : "string", "format" : "uri"},
},
u"dependencies" : {
u"exclusiveMinimum" : u"minimum", u"exclusiveMaximum" : u"maximum"
"dependencies" : {
"exclusiveMinimum" : "minimum", "exclusiveMaximum" : "maximum"
},
}
@@ -172,16 +186,16 @@ class Validator(object):
"""
_SKIPPED = set([ # handled in:
u"dependencies", u"required", # properties
u"exclusiveMinimum", u"exclusiveMaximum", # min*/max*
u"default", u"description", u"id", # no validation needed
u"links", u"name", u"title",
u"$ref", u"$schema", "format", # not yet supported
"dependencies", "required", # properties
"exclusiveMinimum", "exclusiveMaximum", # min*/max*
"default", "description", "id", # no validation needed
"links", "name", "title",
"$ref", "$schema", "format", # not yet supported
])
_TYPES = {
u"array" : list, u"boolean" : bool, u"integer" : int,
u"null" : types.NoneType, u"object" : dict,
"array" : list, "boolean" : bool, "integer" : int,
"null" : type(None), "object" : dict,
}
_meta_validator = None
@@ -241,7 +255,7 @@ class Validator(object):
self._types = dict(
self._TYPES, string=string_types, number=number_types
)
self._types[u"any"] = tuple(self._types.values())
self._types["any"] = tuple(self._types.values())
def is_type(self, instance, type):
"""
@@ -253,7 +267,7 @@ class Validator(object):
if py_type is None:
return self.schema_error(
self._unknown_type, u"%r is not a known type" % (type,)
self._unknown_type, "%r is not a known type" % (type,)
)
# the only thing we're careful about here is evading bool inheriting
@@ -315,16 +329,16 @@ class Validator(object):
self._errors = current_errors
def _validate(self, instance, schema):
for k, v in schema.iteritems():
for k, v in iteritems(schema):
if k in self._SKIPPED:
continue
validator = getattr(self, u"validate_%s" % (k.lstrip("$"),), None)
validator = getattr(self, "validate_%s" % (k.lstrip("$"),), None)
if validator is None:
self.schema_error(
self._unknown_property,
u"%r is not a known schema property" % (k,)
"%r is not a known schema property" % (k,)
)
return
@@ -339,14 +353,14 @@ class Validator(object):
if self._meta_validator is not None:
try:
self._meta_validator.validate(schema, self._version)
except ValidationError, e:
except ValidationError as e:
raise SchemaError(str(e))
self._errors = []
self._validate(instance, schema)
if self._errors:
raise ValidationError(
u"Validation failed with errors (see .errors for details)",
"Validation failed with errors (see .errors for details)",
errors=list(self._errors)
)
@@ -371,12 +385,12 @@ class Validator(object):
)):
return
else:
self.error(u"%r is not of type %r" % (instance, _delist(types)))
self.error("%r is not of type %r" % (instance, _delist(types)))
def validate_properties(self, properties, instance, schema):
for property, subschema in properties.iteritems():
for property, subschema in iteritems(properties):
if property in instance:
dependencies = _list(subschema.get(u"dependencies", []))
dependencies = _list(subschema.get("dependencies", []))
if self.is_type(dependencies, "object"):
self._validate(instance, dependencies)
else:
@@ -384,16 +398,16 @@ class Validator(object):
first = next(missing, None)
if first is not None:
self.error(
u"%r is a dependency of %r" % (first, property)
"%r is a dependency of %r" % (first, property)
)
self._validate(instance[property], subschema)
elif subschema.get(u"required", False):
self.error(u"%r is a required property" % (property,))
elif subschema.get("required", False):
self.error("%r is a required property" % (property,))
def validate_patternProperties(self, patternProperties, instance, schema):
for pattern, subschema in patternProperties.iteritems():
for k, v in instance.iteritems():
for pattern, subschema in iteritems(patternProperties):
for k, v in iteritems(instance):
if re.match(pattern, k):
self._validate(v, subschema)
@@ -402,13 +416,13 @@ class Validator(object):
return
# no viewkeys in <2.7, and pypy seems to fail on vk - vk anyhow, so...
extras = set(instance) - set(schema.get(u"properties", {}))
extras = set(instance) - set(schema.get("properties", {}))
if self.is_type(aP, "object"):
for extra in extras:
self._validate(instance[extra], aP)
elif not aP and extras:
error = u"Additional properties are not allowed (%s %s unexpected)"
error = "Additional properties are not allowed (%s %s unexpected)"
self.error(error % _extras_msg(extras))
def validate_items(self, items, instance, schema):
@@ -427,62 +441,64 @@ class Validator(object):
for item in instance[len(schema):]:
self._validate(item, aI)
elif not aI and len(instance) > len(schema.get("items", [])):
error = u"Additional items are not allowed (%s %s unexpected)"
error = "Additional items are not allowed (%s %s unexpected)"
self.error(error % _extras_msg(instance[len(schema) - 1:]))
def validate_minimum(self, minimum, instance, schema):
if schema.get(u"exclusiveMinimum", False):
instance = float(instance)
if schema.get("exclusiveMinimum", False):
failed = instance <= minimum
cmp = u"less than or equal to"
cmp = "less than or equal to"
else:
failed = instance < minimum
cmp = u"less than"
cmp = "less than"
if failed:
self.error(
u"%r is %s the minimum of %r" % (instance, cmp, minimum)
"%r is %s the minimum of %r" % (instance, cmp, minimum)
)
def validate_maximum(self, maximum, instance, schema):
if schema.get(u"exclusiveMaximum", False):
instance = float(instance)
if schema.get("exclusiveMaximum", False):
failed = instance >= maximum
cmp = u"greater than or equal to"
cmp = "greater than or equal to"
else:
failed = instance > maximum
cmp = u"greater than"
cmp = "greater than"
if failed:
self.error(
u"%r is %s the maximum of %r" % (instance, cmp, maximum)
"%r is %s the maximum of %r" % (instance, cmp, maximum)
)
def validate_minItems(self, mI, instance, schema):
if self.is_type(instance, "array") and len(instance) < mI:
self.error(u"%r is too short" % (instance,))
self.error("%r is too short" % (instance,))
def validate_maxItems(self, mI, instance, schema):
if self.is_type(instance, "array") and len(instance) > mI:
self.error(u"%r is too long" % (instance,))
self.error("%r is too long" % (instance,))
def validate_uniqueItems(self, uI, instance, schema):
if uI and self.is_type(instance, "array") and not _uniq(instance):
self.error(u"%r has non-unique elements" % instance)
self.error("%r has non-unique elements" % instance)
def validate_pattern(self, patrn, instance, schema):
if self.is_type(instance, "string") and not re.match(patrn, instance):
self.error(u"%r does not match %r" % (instance, patrn))
self.error("%r does not match %r" % (instance, patrn))
def validate_minLength(self, mL, instance, schema):
if self.is_type(instance, "string") and len(instance) < mL:
self.error(u"%r is too short" % (instance,))
self.error("%r is too short" % (instance,))
def validate_maxLength(self, mL, instance, schema):
if self.is_type(instance, "string") and len(instance) > mL:
self.error(u"%r is too long" % (instance,))
self.error("%r is too long" % (instance,))
def validate_enum(self, enums, instance, schema):
if instance not in enums:
self.error(u"%r is not one of %r" % (instance, enums))
self.error("%r is not one of %r" % (instance, enums))
def validate_divisibleBy(self, dB, instance, schema):
if isinstance(dB, float):
@@ -492,14 +508,14 @@ class Validator(object):
failed = instance % dB
if failed:
self.error(u"%r is not divisible by %r" % (instance, dB))
self.error("%r is not divisible by %r" % (instance, dB))
def validate_disallow(self, disallow, instance, schema):
disallow = _list(disallow)
if any(self.is_valid(instance, {"type" : [d]}) for d in disallow):
self.error(
u"%r is disallowed for %r" % (_delist(disallow), instance)
"%r is disallowed for %r" % (_delist(disallow), instance)
)
def validate_extends(self, extends, instance, schema):
@@ -516,10 +532,10 @@ def _extras_msg(extras):
"""
if len(extras) == 1:
verb = u"was"
verb = "was"
else:
verb = u"were"
return u", ".join(repr(extra) for extra in extras), verb
verb = "were"
return ", ".join(repr(extra) for extra in extras), verb
def _list(thing):

301
tests.py
View File

@@ -1,4 +1,4 @@
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
from decimal import Decimal
from functools import wraps
import sys
@@ -9,7 +9,13 @@ if sys.version_info[:2] < (2, 7): # pragma: no cover
else:
import unittest
from jsonschema import SchemaError, ValidationError, validate
from jsonschema import SchemaError, ValidationError, validate, iteritems
# For Python 3
try:
unicode
except NameError:
basestring = unicode = str
class ParametrizedTestCase(type):
@@ -21,7 +27,7 @@ class ParametrizedTestCase(type):
def __new__(cls, name, bases, attrs):
attr = {}
for k, v in attrs.iteritems():
for k, v in iteritems(attrs):
parameters = getattr(v, "_parameters", None)
if parameters is not None:
@@ -33,13 +39,23 @@ class ParametrizedTestCase(type):
if parametrized_name:
names.append(parametrized_name)
fn.__name__ = "_".join(names)
fn_name = "_".join(names)
if sys.version_info[0] < 3:
fn_name = fn_name.encode('utf8')
fn.__name__ = fn_name
attr[fn.__name__] = fn
else:
attr[k] = v
if sys.version_info[0] < 3:
name = name.encode('utf8')
return super(ParametrizedTestCase, cls).__new__(cls, name, bases, attr)
# Inscrutable way to create metaclasses in a Python 2/3 compatible way
# See: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/
ParameterizedTestCase = ParametrizedTestCase(
'ParameterizedTestCase', (object,), {})
def parametrized(*runs):
def parametrized_test(fn):
@@ -76,184 +92,181 @@ def validation_test(schema=(), initkwargs=(), **kwschema):
return _validation_test
class TestValidate(unittest.TestCase):
__metaclass__ = ParametrizedTestCase
class TestValidate(ParameterizedTestCase, unittest.TestCase):
integer = parametrized(
("integer", "valid", 1),
("number", "invalid", 1.1),
("string", "invalid", u"foo"),
("string", "invalid", "foo"),
("object", "invalid", {}),
("array", "invalid", []),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(type=u"integer"))
)(validation_test(type="integer"))
number = parametrized(
("integer", "valid", 1),
("number", "valid", 1.1),
("string", "invalid", u"foo"),
("string", "invalid", "foo"),
("object", "invalid", {}),
("array", "invalid", []),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(type=u"number"))
)(validation_test(type="number"))
string = parametrized(
("integer", "invalid", 1),
("number", "invalid", 1.1),
("unicode", "valid", u"foo"),
("unicode", "valid", "foo"),
("str", "valid", "foo"),
("object", "invalid", {}),
("array", "invalid", []),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(type=u"string"))
)(validation_test(type="string"))
object = parametrized(
("integer", "invalid", 1),
("number", "invalid", 1.1),
("string", "invalid", u"foo"),
("string", "invalid", "foo"),
("object", "valid", {}),
("array", "invalid", []),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(type=u"object"))
)(validation_test(type="object"))
array = parametrized(
("integer", "invalid", 1),
("number", "invalid", 1.1),
("string", "invalid", u"foo"),
("string", "invalid", "foo"),
("object", "invalid", {}),
("array", "valid", []),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(type=u"array"))
)(validation_test(type="array"))
boolean = parametrized(
("integer", "invalid", 1),
("number", "invalid", 1.1),
("string", "invalid", u"foo"),
("string", "invalid", "foo"),
("object", "invalid", {}),
("array", "invalid", []),
("true", "valid", True),
("false", "valid", False),
("null", "invalid", None),
)(validation_test(type=u"boolean"))
)(validation_test(type="boolean"))
null = parametrized(
("integer", "invalid", 1),
("number", "invalid", 1.1),
("string", "invalid", u"foo"),
("string", "invalid", "foo"),
("object", "invalid", {}),
("array", "invalid", []),
("boolean", "invalid", True),
("null", "valid", None),
)(validation_test(type=u"null"))
)(validation_test(type="null"))
any = parametrized(
("integer", "valid", 1),
("number", "valid", 1.1),
("string", "valid", u"foo"),
("string", "valid", "foo"),
("object", "valid", {}),
("array", "valid", []),
("boolean", "valid", True),
("null", "valid", None),
)(validation_test(type=u"any"))
)(validation_test(type="any"))
multiple_types = parametrized(
("integer", "valid", 1),
("string", "valid", u"foo"),
("string", "valid", "foo"),
("number", "invalid", 1.1),
("object", "invalid", {}),
("array", "invalid", []),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(type=[u"integer", u"string"]))
)(validation_test(type=["integer", "string"]))
multiple_types_schema = parametrized(
("match", "valid", [1, 2]),
("other_match", "valid", {u"foo" : u"bar"}),
("other_match", "valid", {"foo" : "bar"}),
("number", "invalid", 1.1),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(type=[u"array", {u"type" : u"object"}]))
)(validation_test(type=["array", {"type" : "object"}]))
multiple_types_subschema = parametrized(
("integer", "valid", 1),
("object_right_type", "valid", {u"foo" : None}),
("object_wrong_type", "invalid", {u"foo" : 1}),
("object_another_wrong_type", "invalid", {u"foo" : 1.1}),
("object_right_type", "valid", {"foo" : None}),
("object_wrong_type", "invalid", {"foo" : 1}),
("object_another_wrong_type", "invalid", {"foo" : 1.1}),
)(validation_test(
type=[u"integer", {"properties" : {u"foo" : {u"type" : u"null"}}}]
type=["integer", {"properties" : {"foo" : {"type" : "null"}}}]
))
properties = parametrized(
("", "valid", {u"foo" : 1, u"bar" : u"baz"}),
("", "valid", {"foo" : 1, "bar" : "baz"}),
("extra_property", "valid",
{u"foo" : 1, u"bar" : u"baz", u"quux" : 42}),
("invalid_type", "invalid", {u"foo" : 1, u"bar" : []}),
{"foo" : 1, "bar" : "baz", "quux" : 42}),
("invalid_type", "invalid", {"foo" : 1, "bar" : []}),
)(validation_test(
{
"properties" : {
u"foo" : {u"type" : u"number"},
u"bar" : {u"type" : u"string"},
"foo" : {"type" : "number"},
"bar" : {"type" : "string"},
}
}
))
patternProperties = parametrized(
("single_match", "valid", {u"foo" : 1}),
("multiple_match", "valid", {u"foo" : 1, u"fah" : 2, u"bar" : u"baz"}),
("single_mismatch", "invalid", {u"foo" : u"bar"}),
("multiple_mismatch", "invalid", {u"foo" : 1, u"fah" : u"bar"}),
)(validation_test(patternProperties={u"f.*" : {u"type" : u"integer"}}))
("single_match", "valid", {"foo" : 1}),
("multiple_match", "valid", {"foo" : 1, "fah" : 2, "bar" : "baz"}),
("single_mismatch", "invalid", {"foo" : "bar"}),
("multiple_mismatch", "invalid", {"foo" : 1, "fah" : "bar"}),
)(validation_test(patternProperties={"f.*" : {"type" : "integer"}}))
multiple_patternProperties = parametrized(
("match", "valid", {u"a" : 21}),
("other_match", "valid", {u"aaaa" : 18}),
("multiple_match", "valid", {u"a" : 21, u"aaaa" : 18}),
("mismatch", "invalid", {u"aaa" : u"bar"}),
("other_mismatch", "invalid", {u"aaaa" : 31}),
("multiple_mismatch", "invalid", {u"aaa" : u"foo", u"aaaa" : 32}),
("match", "valid", {"a" : 21}),
("other_match", "valid", {"aaaa" : 18}),
("multiple_match", "valid", {"a" : 21, "aaaa" : 18}),
("mismatch", "invalid", {"aaa" : "bar"}),
("other_mismatch", "invalid", {"aaaa" : 31}),
("multiple_mismatch", "invalid", {"aaa" : "foo", "aaaa" : 32}),
)(validation_test(patternProperties={
u"a*" : {u"type" : u"integer"},
u"aaa*" : {u"maximum" : 20},
"a*" : {"type" : "integer"},
"aaa*" : {"maximum" : 20},
}
))
def test_additionalProperties_allowed_by_default(self):
schema = {
"properties" : {
u"foo" : {u"type" : u"number"},
u"bar" : {u"type" : u"string"},
"foo" : {"type" : "number"},
"bar" : {"type" : "string"},
}
}
validate({u"foo" : 1, u"bar" : u"baz", u"quux" : False}, schema)
validate({"foo" : 1, "bar" : "baz", "quux" : False}, schema)
@parametrized(
("", False),
("schema", {u"type" : u"boolean"}),
("schema", {"type" : "boolean"}),
)
def additionalProperties(self, aP):
schema = {
"properties" : {
u"foo" : {u"type" : u"number"},
u"bar" : {u"type" : u"string"},
"foo" : {"type" : "number"},
"bar" : {"type" : "string"},
},
"additionalProperties" : aP,
}
with self.assertRaises(ValidationError):
validate({u"foo" : 1, u"bar" : u"baz", u"quux" : u"boom"}, schema)
validate({"foo" : 1, "bar" : "baz", "quux" : "boom"}, schema)
def test_additionalProperties_ignores_nonobjects(self):
validate(None, {"additionalProperties" : False})
@parametrized(
("single_extra", {"foo" : 2}, ["('foo' was unexpected)"]),
("single_extra", {"foo" : 2}, ["'foo' was unexpected)"]),
("multiple_extras",
dict.fromkeys(["foo", "bar", "quux"]),
["'bar'", "'foo'", "'quux'", "were unexpected)"],
@@ -265,38 +278,38 @@ class TestValidate(unittest.TestCase):
with self.assertRaises(ValidationError) as error:
validate(instance, schema)
self.assertTrue(all(err in error.exception.message for err in errs))
self.assertTrue(all(err in unicode(error.exception) for err in errs))
items = parametrized(
("", "valid", [1, 2, 3]),
("wrong_type", "invalid", [1, u"x"]),
)(validation_test(items={u"type" : u"integer"}))
("wrong_type", "invalid", [1, "x"]),
)(validation_test(items={"type" : "integer"}))
items_tuple_typing = parametrized(
("", "valid", [1, u"foo"]),
("wrong_type", "invalid", [u"foo", 1])
)(validation_test(items=[{u"type" : u"integer"}, {u"type" : u"string"}]))
("", "valid", [1, "foo"]),
("wrong_type", "invalid", ["foo", 1])
)(validation_test(items=[{"type" : "integer"}, {"type" : "string"}]))
def test_additionalItems_allowed_by_default(self):
validate(
[1, u"foo", False],
{"items" : [{u"type" : u"integer"}, {u"type" : u"string"}]}
[1, "foo", False],
{"items" : [{"type" : "integer"}, {"type" : "string"}]}
)
additionalItems = parametrized(
("no_additional", "valid", [1, u"foo"]),
("additional", "invalid", [1, u"foo", False]),
("no_additional", "valid", [1, "foo"]),
("additional", "invalid", [1, "foo", False]),
)(validation_test({
"items" : [{u"type" : u"integer"}, {u"type" : u"string"}],
"items" : [{"type" : "integer"}, {"type" : "string"}],
"additionalItems" : False,
}))
additionalItems_schema = parametrized(
("match", "valid", [1, u"foo", 3]),
("mismatch", "invalid", [1, u"foo", u"bar"]),
("match", "valid", [1, "foo", 3]),
("mismatch", "invalid", [1, "foo", "bar"]),
)(validation_test({
"items" : [{u"type" : u"integer"}, {u"type" : u"string"}],
"additionalItems" : {u"type" : u"integer"},
"items" : [{"type" : "integer"}, {"type" : "string"}],
"additionalItems" : {"type" : "integer"},
}))
def test_additionalItems_ignores_nonarrays(self):
@@ -314,62 +327,62 @@ class TestValidate(unittest.TestCase):
@parametrized(
("false_by_default", "valid", {}, {}),
("false_explicit", "valid", {u"required" : False}, {}),
("one", "valid", {u"required" : True}, {}),
("other", "invalid", {}, {u"required" : True}),
("both", "invalid", {u"required" : True}, {u"required" : True}),
("false_explicit", "valid", {"required" : False}, {}),
("one", "valid", {"required" : True}, {}),
("other", "invalid", {}, {"required" : True}),
("both", "invalid", {"required" : True}, {"required" : True}),
)
def required(self, expect, foo, bar):
schema = {
u"properties" : {
u"foo" : {u"type" : u"number"},
u"bar" : {u"type" : u"string"},
"properties" : {
"foo" : {"type" : "number"},
"bar" : {"type" : "string"},
}
}
schema[u"properties"][u"foo"].update(foo)
schema[u"properties"][u"bar"].update(bar)
schema["properties"]["foo"].update(foo)
schema["properties"]["bar"].update(bar)
test = validation_test(schema)
test(self, expect, {u"foo" : 1})
test(self, expect, {"foo" : 1})
dependencies = parametrized(
("neither", "valid", {}),
("nondependant", "valid", {u"foo" : 1}),
("with_dependency", "valid", {u"foo" : 1, u"bar" : 2}),
("missing_dependency", "invalid", {u"bar" : 2}),
)(validation_test(properties={u"bar" : {u"dependencies" : u"foo"}}))
("nondependant", "valid", {"foo" : 1}),
("with_dependency", "valid", {"foo" : 1, "bar" : 2}),
("missing_dependency", "invalid", {"bar" : 2}),
)(validation_test(properties={"bar" : {"dependencies" : "foo"}}))
multiple_dependencies = parametrized(
("neither", "valid", {}),
("nondependants", "valid", {u"foo" : 1, u"bar" : 2}),
("with_dependencies", "valid", {u"foo" : 1, u"bar" : 2, u"quux" : 3}),
("missing_dependency", "invalid", {u"foo" : 1, u"quux" : 2}),
("missing_other_dependency", "invalid", {u"bar" : 1, u"quux" : 2}),
("missing_both_dependencies", "invalid", {u"quux" : 1}),
("nondependants", "valid", {"foo" : 1, "bar" : 2}),
("with_dependencies", "valid", {"foo" : 1, "bar" : 2, "quux" : 3}),
("missing_dependency", "invalid", {"foo" : 1, "quux" : 2}),
("missing_other_dependency", "invalid", {"bar" : 1, "quux" : 2}),
("missing_both_dependencies", "invalid", {"quux" : 1}),
)(validation_test(
properties={u"quux" : {u"dependencies" : [u"foo", u"bar"]}}
properties={"quux" : {"dependencies" : ["foo", "bar"]}}
))
multiple_dependencies_subschema = parametrized(
("", "valid", {u"foo" : 1, u"bar" : 2}),
("wrong_type", "invalid", {u"foo" : u"quux", u"bar" : 2}),
("wrong_type_other", "invalid", {u"foo" : 2, u"bar" : u"quux"}),
("wrong_type_both", "invalid", {u"foo" : u"quux", u"bar" : u"quux"}),
("", "valid", {"foo" : 1, "bar" : 2}),
("wrong_type", "invalid", {"foo" : "quux", "bar" : 2}),
("wrong_type_other", "invalid", {"foo" : 2, "bar" : "quux"}),
("wrong_type_both", "invalid", {"foo" : "quux", "bar" : "quux"}),
)(validation_test(properties={
u"bar" : {
u"dependencies" : {
"bar" : {
"dependencies" : {
"properties" : {
u"foo" : {u"type" : u"integer"},
u"bar" : {u"type" : u"integer"},
"foo" : {"type" : "integer"},
"bar" : {"type" : "integer"},
}}}}))
@parametrized(
("", "valid", {}, 2.6),
("fail", "invalid", {}, .6),
("exclusiveMinimum", "valid", {u"exclusiveMinimum" : True}, 1.2),
("exclusiveMinimum", "valid", {"exclusiveMinimum" : True}, 1.2),
("exclusiveMinimum_fail", "invalid",
{u"exclusiveMinimum" : True}, 1.1),
{"exclusiveMinimum" : True}, 1.1),
)
def minimum(self, expect, eM, instance):
eM["minimum"] = 1.1
@@ -379,9 +392,9 @@ class TestValidate(unittest.TestCase):
@parametrized(
("", "valid", {}, 2.6),
("fail", "invalid", {}, 3.5),
("exclusiveMaximum", "valid", {u"exclusiveMaximum" : True}, 2.2),
("exclusiveMaximum", "valid", {"exclusiveMaximum" : True}, 2.2),
("exclusiveMaximum_fail", "invalid",
{u"exclusiveMaximum" : True}, 3.0),
{"exclusiveMaximum" : True}, 3.0),
)
def maximum(self, expect, eM, instance):
eM["maximum"] = 3.0
@@ -421,32 +434,32 @@ class TestValidate(unittest.TestCase):
)(validation_test(uniqueItems=True))
pattern = parametrized(
("match", "valid", u"aaa"),
("mismatch", "invalid", u"ab"),
("match", "valid", "aaa"),
("mismatch", "invalid", "ab"),
("ignores_other_stuff", "valid", True),
)(validation_test(pattern=u"^a*$"))
)(validation_test(pattern="^a*$"))
minLength = parametrized(
("", "valid", u"foo"),
("too_short", "invalid", u"f"),
("", "valid", "foo"),
("too_short", "invalid", "f"),
("ignores_arrays", "valid", [1]),
)(validation_test(minLength=2))
maxLength = parametrized(
("", "valid", u"f"),
("too_long", "invalid", u"foo"),
("", "valid", "f"),
("too_long", "invalid", "foo"),
("ignores_arrays", "valid", [1, 2, 3]),
)(validation_test(maxLength=2))
@parametrized(
("integer", "valid", 1, [1, 2, 3]),
("integer_fail", "invalid", 6, [1, 2, 3]),
("string", "valid", u"foo", [u"foo", u"bar"]),
("string_fail", "invalid", u"quux", [u"foo", u"bar"]),
("string", "valid", "foo", ["foo", "bar"]),
("string_fail", "invalid", "quux", ["foo", "bar"]),
("bool", "valid", True, [True]),
("bool_fail", "invalid", False, [True]),
("object", "valid", {u"foo" : u"bar"}, [{u"foo" : u"bar"}]),
("object_fail", "invalid", {u"foo" : u"bar"}, [{u"foo" : u"quux"}]),
("object", "valid", {"foo" : "bar"}, [{"foo" : "bar"}]),
("object_fail", "invalid", {"foo" : "bar"}, [{"foo" : "quux"}]),
)
def enum(self, expect, instance, enum):
test = validation_test(enum=enum)
@@ -467,23 +480,23 @@ class TestValidate(unittest.TestCase):
test(self, expect, instance)
disallow = parametrized(
("", "valid", u"foo"),
("", "valid", "foo"),
("disallowed", "invalid", 1),
)(validation_test(disallow=u"integer"))
)(validation_test(disallow="integer"))
multiple_disallow = parametrized(
("", "valid", u"foo"),
("", "valid", "foo"),
("mismatch", "invalid", 1),
("other_mismatch", "invalid", True),
)(validation_test(disallow=[u"integer", u"boolean"]))
)(validation_test(disallow=["integer", "boolean"]))
multiple_disallow_subschema = parametrized(
("match", "valid", 1),
("other_match", "valid", {u"foo" : 1}),
("mismatch", "invalid", u"foo"),
("other_mismatch", "invalid", {u"foo" : u"bar"}),
("other_match", "valid", {"foo" : 1}),
("mismatch", "invalid", "foo"),
("other_mismatch", "invalid", {"foo" : "bar"}),
)(validation_test(
disallow=[u"string", {"properties" : {u"foo" : {u"type" : u"string"}}}]
disallow=["string", {"properties" : {"foo" : {"type" : "string"}}}]
))
@parametrized(
@@ -540,23 +553,30 @@ class TestValidate(unittest.TestCase):
instance = [1, 2]
schema = {
u"disallow" : u"array",
u"enum" : [[u"a", u"b", u"c"], [u"d", u"e", u"f"]],
u"minItems" : 3
"disallow" : "array",
"enum" : [["a", "b", "c"], ["d", "e", "f"]],
"minItems" : 3
}
with self.assertRaises(ValidationError) as e:
validate(instance, schema, stop_on_error=False)
self.assertEqual(sorted(e.exception.errors), sorted([
u"u'array' is disallowed for [1, 2]",
u"[1, 2] is too short",
u"[1, 2] is not one of [[u'a', u'b', u'c'], [u'd', u'e', u'f']]",
]))
if sys.version_info[0] >= 3:
self.assertEqual(sorted(e.exception.errors), sorted([
"'array' is disallowed for [1, 2]",
"[1, 2] is too short",
"[1, 2] is not one of [['a', 'b', 'c'], ['d', 'e', 'f']]",
]))
else:
self.assertEqual(sorted(e.exception.errors), sorted([
"u'array' is disallowed for [1, 2]",
"[1, 2] is too short",
"[1, 2] is not one of [[u'a', u'b', u'c'], [u'd', u'e', u'f']]",
]))
def test_unknown_type_error(self):
with self.assertRaises(SchemaError):
validate(1, {u"type" : u"foo"}, unknown_type="error")
validate(1, {"type" : "foo"}, unknown_type="error")
@unittest.skipIf(
sys.version_info[:2] == (2, 5),
@@ -565,15 +585,15 @@ class TestValidate(unittest.TestCase):
def test_unknown_type_warn(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
validate(1, {u"type" : u"foo"}, unknown_type="warn")
validate(1, {"type" : "foo"}, unknown_type="warn")
self.assertEqual(len(w), 1)
def test_unknown_type_skip(self):
validate(1, {u"type" : u"foo"}, unknown_type="skip")
validate(1, {"type" : "foo"}, unknown_type="skip")
def test_unknown_property_error(self):
with self.assertRaises(SchemaError):
validate(1, {u"foo" : u"bar"}, unknown_property="error")
validate(1, {"foo" : "bar"}, unknown_property="error")
@unittest.skipIf(
sys.version_info[:2] == (2, 5),
@@ -582,13 +602,13 @@ class TestValidate(unittest.TestCase):
def test_unknown_property_warn(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
validate(1, {u"foo" : u"bar"}, unknown_property="warn")
validate(1, {"foo" : "bar"}, unknown_property="warn")
self.assertEqual(len(w), 1)
def test_unknown_property_skip(self):
validate(
1,
{u"foo" : u"foo", u"type" : u"integer"},
{"foo" : "foo", "type" : "integer"},
unknown_property="skip"
)
@@ -596,17 +616,18 @@ class TestValidate(unittest.TestCase):
("integer", "valid", 1),
("number", "valid", 1.1),
("decimal", "valid", Decimal(1) / Decimal(8)),
("string", "invalid", u"foo"),
("string", "invalid", "foo"),
("object", "invalid", {}),
("array", "invalid", []),
("boolean", "invalid", True),
("null", "invalid", None),
)(validation_test(
initkwargs={"number_types" : (int, float, Decimal)},
type=u"number")
type="number")
)
# TODO: we're in need of more meta schema tests
def test_minItems_invalid_string(self):
with self.assertRaises(SchemaError):
validate([1], {"minItems" : "1"}) # needs to be an integer

15
tox.ini
View File

@@ -1,24 +1,17 @@
[tox]
envlist = py25, py26, py27, pypy
envlist = py26, py27, pypy, py32
[testenv]
commands =
trial tests []
nosetests tests.py
{envpython} -m doctest README.rst
deps =
Twisted
[testenv:py25]
deps =
Twisted
unittest2
setenv =
MACOSX_DEPLOYMENT_TARGET = 10.7
nose
[testenv:py26]
deps =
Twisted
nose
unittest2
setenv =
MACOSX_DEPLOYMENT_TARGET = 10.7