summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst4
-rw-r--r--README.rst1
-rw-r--r--docs/errors.rst8
-rw-r--r--docs/faq.rst14
-rw-r--r--docs/validate.rst2
-rw-r--r--jsonschema/__init__.py2
-rw-r--r--jsonschema/_format.py8
-rw-r--r--jsonschema/_types.py1
-rw-r--r--jsonschema/benchmarks/issue232.py2
-rw-r--r--jsonschema/schemas/draft7.json168
-rw-r--r--jsonschema/tests/test_jsonschema_test_suite.py34
-rw-r--r--jsonschema/tests/test_validators.py30
-rw-r--r--jsonschema/validators.py45
13 files changed, 298 insertions, 21 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6c9e218..7c24e95 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,8 +1,8 @@
v3.0.0
------
-* Support for Draft 6
-* Draft 6 is now the default
+* Support for Draft 6 and Draft 7
+* Draft 7 is now the default
* New ``TypeChecker`` object for more complex type definitions (and overrides)
* Falling back to isodate for the date-time format checker is no longer
attempted, in accordance with the specification
diff --git a/README.rst b/README.rst
index 5069edf..99c0b37 100644
--- a/README.rst
+++ b/README.rst
@@ -57,6 +57,7 @@ Features
--------
* Full support for
+ `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft7Validator>`_,
`Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft6Validator>`_,
`Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft4Validator>`_
and
diff --git a/docs/errors.rst b/docs/errors.rst
index eb1f661..708425f 100644
--- a/docs/errors.rst
+++ b/docs/errors.rst
@@ -137,7 +137,7 @@ These attributes can be clarified with a short example:
}
}
instance = [{}, 3, "foo"]
- v = Draft6Validator(schema)
+ v = Draft7Validator(schema)
errors = sorted(v.iter_errors(instance), key=lambda e: e.path)
The error messages in this situation are not very helpful on their own.
@@ -347,14 +347,14 @@ to guess the most relevant error in a given bunch.
.. doctest::
- >>> from jsonschema import Draft6Validator
+ >>> from jsonschema import Draft7Validator
>>> from jsonschema.exceptions import best_match
>>> schema = {
... "type": "array",
... "minItems": 3,
... }
- >>> print(best_match(Draft6Validator(schema).iter_errors(11)).message)
+ >>> print(best_match(Draft7Validator(schema).iter_errors(11)).message)
11 is not of type 'array'
@@ -423,7 +423,7 @@ to guess the most relevant error in a given bunch.
... },
... }
>>> instance = {"name": 123, "phones": {"home": [123]}}
- >>> errors = Draft6Validator(schema).iter_errors(instance)
+ >>> errors = Draft7Validator(schema).iter_errors(instance)
>>> [
... e.path[-1]
... for e in sorted(errors, key=exceptions.relevance)
diff --git a/docs/faq.rst b/docs/faq.rst
index 6ed36dc..dbfcbb5 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -25,7 +25,7 @@ themselves will need to be valid under the schema.)
.. code-block:: python
- from jsonschema import Draft6Validator, validators
+ from jsonschema import Draft7Validator, validators
def extend_with_default(validator_class):
@@ -46,21 +46,21 @@ themselves will need to be valid under the schema.)
)
- DefaultValidatingDraft6Validator = extend_with_default(Draft6Validator)
+ DefaultValidatingDraft7Validator = extend_with_default(Draft7Validator)
# Example usage:
obj = {}
schema = {'properties': {'foo': {'default': 'bar'}}}
- # Note jsonschem.validate(obj, schema, cls=DefaultValidatingDraft6Validator)
+ # Note jsonschem.validate(obj, schema, cls=DefaultValidatingDraft7Validator)
# will not work because the metaschema contains `default` directives.
- DefaultValidatingDraft6Validator(schema).validate(obj)
+ DefaultValidatingDraft7Validator(schema).validate(obj)
assert obj == {'foo': 'bar'}
See the above-linked document for more info on how this works, but
basically, it just extends the :validator:`properties` validator on a
-`jsonschema.Draft6Validator` to then go ahead and update all the
+`jsonschema.Draft7Validator` to then go ahead and update all the
defaults.
.. note::
@@ -95,7 +95,7 @@ defaults.
}
obj = {}
- DefaultValidatingDraft6Validator(schema).validate(obj)
+ DefaultValidatingDraft7Validator(schema).validate(obj)
assert obj == {'outer-object': {'inner-object': 'INNER-DEFAULT'}}
...but if you don't provide a default value for your object,
@@ -105,7 +105,7 @@ defaults.
del schema["properties"]["outer-object"]["default"]
obj2 = {}
- DefaultValidatingDraft6Validator(schema).validate(obj2)
+ DefaultValidatingDraft7Validator(schema).validate(obj2)
assert obj2 == {} # whoops
diff --git a/docs/validate.rst b/docs/validate.rst
index 0a9e26f..8b06a11 100644
--- a/docs/validate.rst
+++ b/docs/validate.rst
@@ -235,6 +235,8 @@ the JSON Schema specification. For details on the methods and attributes
that each validator class provides see the `IValidator` interface,
which each included validator class implements.
+.. autoclass:: Draft7Validator
+
.. autoclass:: Draft6Validator
.. autoclass:: Draft4Validator
diff --git a/jsonschema/__init__.py b/jsonschema/__init__.py
index fb10f5a..0da56aa 100644
--- a/jsonschema/__init__.py
+++ b/jsonschema/__init__.py
@@ -17,12 +17,14 @@ from jsonschema._format import (
draft3_format_checker,
draft4_format_checker,
draft6_format_checker,
+ draft7_format_checker,
)
from jsonschema._types import TypeChecker
from jsonschema.validators import (
Draft3Validator,
Draft4Validator,
Draft6Validator,
+ Draft7Validator,
RefResolver,
validate,
)
diff --git a/jsonschema/_format.py b/jsonschema/_format.py
index 28a12f0..e2ef976 100644
--- a/jsonschema/_format.py
+++ b/jsonschema/_format.py
@@ -129,7 +129,7 @@ class FormatChecker(object):
return True
-_draft_checkers = {"draft3": [], "draft4": [], "draft6": []}
+_draft_checkers = {"draft3": [], "draft4": [], "draft6": [], "draft7": []}
def _checks_drafts(
@@ -137,11 +137,13 @@ def _checks_drafts(
draft3=None,
draft4=None,
draft6=None,
+ draft7=None,
raises=(),
):
draft3 = draft3 or name
draft4 = draft4 or name
draft6 = draft6 or name
+ draft7 = draft7 or name
def wrap(func):
if draft3:
@@ -153,6 +155,9 @@ def _checks_drafts(
if draft6:
_draft_checkers["draft6"].append(draft6)
func = FormatChecker.cls_checks(draft6, raises)(func)
+ if draft7:
+ _draft_checkers["draft7"].append(draft7)
+ func = FormatChecker.cls_checks(draft7, raises)(func)
return func
return wrap
@@ -307,3 +312,4 @@ else:
draft3_format_checker = FormatChecker(_draft_checkers["draft3"])
draft4_format_checker = FormatChecker(_draft_checkers["draft4"])
draft6_format_checker = FormatChecker(_draft_checkers["draft6"])
+draft7_format_checker = FormatChecker(_draft_checkers["draft7"])
diff --git a/jsonschema/_types.py b/jsonschema/_types.py
index d3ef1c2..f556ded 100644
--- a/jsonschema/_types.py
+++ b/jsonschema/_types.py
@@ -185,3 +185,4 @@ draft6_type_checker = draft4_type_checker.redefine(
isinstance(instance, float) and instance.is_integer()
),
)
+draft7_type_checker = draft6_type_checker
diff --git a/jsonschema/benchmarks/issue232.py b/jsonschema/benchmarks/issue232.py
index 2e89f73..368b744 100644
--- a/jsonschema/benchmarks/issue232.py
+++ b/jsonschema/benchmarks/issue232.py
@@ -17,7 +17,7 @@ collection = Collection(
path=FilePath(__file__).sibling("issue232"),
remotes=m(),
name="issue232",
- validator=jsonschema.Draft6Validator,
+ validator=jsonschema.Draft7Validator,
)
diff --git a/jsonschema/schemas/draft7.json b/jsonschema/schemas/draft7.json
new file mode 100644
index 0000000..5bee90e
--- /dev/null
+++ b/jsonschema/schemas/draft7.json
@@ -0,0 +1,168 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": true
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true,
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "if": {"$ref": "#"},
+ "then": {"$ref": "#"},
+ "else": {"$ref": "#"},
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": true
+}
diff --git a/jsonschema/tests/test_jsonschema_test_suite.py b/jsonschema/tests/test_jsonschema_test_suite.py
index 110c5f4..e434f8a 100644
--- a/jsonschema/tests/test_jsonschema_test_suite.py
+++ b/jsonschema/tests/test_jsonschema_test_suite.py
@@ -12,9 +12,11 @@ from jsonschema import (
Draft3Validator,
Draft4Validator,
Draft6Validator,
+ Draft7Validator,
draft3_format_checker,
draft4_format_checker,
draft6_format_checker,
+ draft7_format_checker,
)
from jsonschema.tests._suite import Suite
from jsonschema.validators import _DEPRECATED_DEFAULT_TYPES, create
@@ -24,6 +26,7 @@ SUITE = Suite()
DRAFT3 = SUITE.version(name="draft3")
DRAFT4 = SUITE.version(name="draft4")
DRAFT6 = SUITE.version(name="draft6")
+DRAFT7 = SUITE.version(name="draft7")
def skip_tests_containing_descriptions(**kwargs):
@@ -139,6 +142,37 @@ TestDraft6 = DRAFT6.to_unittest_testcase(
)
+TestDraft7 = DRAFT7.to_unittest_testcase(
+ DRAFT7.tests(),
+ DRAFT7.format_tests(),
+ DRAFT7.optional_tests_of(name="bignum"),
+ DRAFT7.optional_tests_of(name="zeroTerminatedFloats"),
+ Validator=Draft7Validator,
+ format_checker=draft7_format_checker,
+ skip=lambda test: (
+ narrow_unicode_build(test)
+ or missing_format(draft7_format_checker)(test)
+ or skip_tests_containing_descriptions(
+ ref={
+ "valid tree": "An actual bug, this needs fixing.",
+ },
+ refRemote={
+ "number is valid": "An actual bug, this needs fixing.",
+ "string is invalid": "An actual bug, this needs fixing.",
+ },
+ )(test)
+ or skip_tests_containing_descriptions(
+ **{
+ "date-time": {
+ "case-insensitive T and Z":
+ "Upstream bug in strict_rfc3339",
+ },
+ }
+ )(test)
+ ),
+)
+
+
TestDraft3LegacyTypeCheck = DRAFT3.to_unittest_testcase(
DRAFT3.tests_of(name="type"),
name="TestDraft3LegacyTypeCheck",
diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py
index 9ebff12..8bb3880 100644
--- a/jsonschema/tests/test_validators.py
+++ b/jsonschema/tests/test_validators.py
@@ -464,7 +464,7 @@ class TestValidationErrorMessages(TestCase):
message = self.message_for(
instance="something",
schema=False,
- cls=validators.Draft6Validator,
+ cls=validators.Draft7Validator,
)
self.assertIn("False schema does not allow 'something'", message)
@@ -1132,6 +1132,19 @@ class TestValidatorFor(TestCase):
validators.Draft6Validator,
)
+ def test_draft_7(self):
+ schema = {"$schema": "http://json-schema.org/draft-07/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-07/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
def test_True(self):
self.assertIs(
validators.validator_for(True),
@@ -1213,8 +1226,19 @@ class TestValidate(TestCase):
Validator=validators.Draft6Validator,
)
- def test_draft6_validator_is_the_default(self):
- self.assertUses(schema={}, Validator=validators.Draft6Validator)
+ def test_draft7_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema#"},
+ Validator=validators.Draft7Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema"},
+ Validator=validators.Draft7Validator,
+ )
+
+ def test_draft7_validator_is_the_default(self):
+ self.assertUses(schema={}, Validator=validators.Draft7Validator)
def test_validation_error_message(self):
with self.assertRaises(ValidationError) as e:
diff --git a/jsonschema/validators.py b/jsonschema/validators.py
index 4c2b79e..fd68a9f 100644
--- a/jsonschema/validators.py
+++ b/jsonschema/validators.py
@@ -498,7 +498,46 @@ Draft6Validator = create(
version="draft6",
)
-_LATEST_VERSION = Draft6Validator
+Draft7Validator = create(
+ meta_schema=_utils.load_schema("draft7"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"const": _validators.const,
+ u"contains": _validators.contains,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"exclusiveMaximum": _validators.exclusiveMaximum,
+ u"exclusiveMinimum": _validators.exclusiveMinimum,
+ u"format": _validators.format,
+ u"items": _validators.items,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _validators.maximum,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _validators.minimum,
+ u"multipleOf": _validators.multipleOf,
+ u"oneOf": _validators.oneOf,
+ u"not": _validators.not_,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"propertyNames": _validators.propertyNames,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft7_type_checker,
+ version="draft7",
+)
+
+_LATEST_VERSION = Draft7Validator
class RefResolver(object):
@@ -775,7 +814,7 @@ def validate(instance, schema, cls=None, *args, **kwargs):
in less obvious or consistent ways. If you know you have a valid schema
already or don't care, you might prefer using the
`IValidator.validate` method directly on a specific validator
- (e.g. ``Draft6Validator.validate``).
+ (e.g. ``Draft7Validator.validate``).
Arguments:
@@ -798,7 +837,7 @@ def validate(instance, schema, cls=None, *args, **kwargs):
proper validator will be used. The specification recommends that all
schemas contain :validator:`$schema` properties for this reason. If no
:validator:`$schema` property is found, the default validator class is
- `Draft6Validator`.
+ the latest released draft.
Any other provided positional and keyword arguments will be passed on when
instantiating the ``cls``.