diff options
author | Julian Berman <Julian@GrayVines.com> | 2018-09-30 09:48:24 -0400 |
---|---|---|
committer | Julian Berman <Julian@GrayVines.com> | 2018-09-30 09:48:24 -0400 |
commit | 8cc6a5af0b5ab343707c0c71021c477651aa479b (patch) | |
tree | 4dbf7390d9b74b69e02a38facc57eadb24662f09 | |
parent | e0add0ee189d9aa97f8326a27da6a925e24a5cae (diff) | |
parent | ceb37aac7ea96fc90966441728bd035c04d8fdbf (diff) | |
download | jsonschema-8cc6a5af0b5ab343707c0c71021c477651aa479b.tar.gz |
Merge branch 'draft7'v3.0.0a3
* draft7:
If/Then/Else.
Enable uri-reference and uri-template for draft 7.
Date is apparently back.
First step on Draft7 support.
-rw-r--r-- | CHANGELOG.rst | 4 | ||||
-rw-r--r-- | README.rst | 1 | ||||
-rw-r--r-- | docs/errors.rst | 8 | ||||
-rw-r--r-- | docs/faq.rst | 14 | ||||
-rw-r--r-- | docs/validate.rst | 2 | ||||
-rw-r--r-- | jsonschema/__init__.py | 2 | ||||
-rw-r--r-- | jsonschema/_format.py | 20 | ||||
-rw-r--r-- | jsonschema/_types.py | 1 | ||||
-rw-r--r-- | jsonschema/_validators.py | 13 | ||||
-rw-r--r-- | jsonschema/benchmarks/issue232.py | 2 | ||||
-rw-r--r-- | jsonschema/schemas/draft7.json | 168 | ||||
-rw-r--r-- | jsonschema/tests/test_jsonschema_test_suite.py | 34 | ||||
-rw-r--r-- | jsonschema/tests/test_validators.py | 30 | ||||
-rw-r--r-- | jsonschema/validators.py | 46 |
14 files changed, 321 insertions, 24 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 @@ -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..75bd48c 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 @@ -211,7 +216,11 @@ else: return True return rfc3987.parse(instance, rule="URI") - @_checks_drafts(draft6="uri-reference", raises=ValueError) + @_checks_drafts( + draft6="uri-reference", + draft7="uri-reference", + raises=ValueError, + ) def is_uri_reference(instance): if not isinstance(instance, str_types): return True @@ -237,7 +246,7 @@ def is_regex(instance): return re.compile(instance) -@_checks_drafts(draft3="date", raises=ValueError) +@_checks_drafts(draft3="date", draft7="date", raises=ValueError) def is_date(instance): if not isinstance(instance, str_types): return True @@ -294,7 +303,9 @@ except ImportError: pass else: @_checks_drafts( - draft6="uri-template", raises=uritemplate.exceptions.InvalidTemplate, + draft6="uri-template", + draft7="uri-template", + raises=uritemplate.exceptions.InvalidTemplate, ) def is_uri_template( instance, @@ -307,3 +318,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/_validators.py b/jsonschema/_validators.py index 2d6d2aa..1107586 100644 --- a/jsonschema/_validators.py +++ b/jsonschema/_validators.py @@ -529,3 +529,16 @@ def not_(validator, not_schema, instance, schema): yield ValidationError( "%r is not allowed for %r" % (not_schema, instance) ) + + +def if_(validator, if_schema, instance, schema): + # FIXME: paths + if validator.is_valid(instance, if_schema): + if u"then" in schema: + then = schema[u"then"] + for error in validator.descend(instance, then): + yield error + elif u"else" in schema: + else_ = schema[u"else"] + for error in validator.descend(instance, else_): + yield error 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..fed56a2 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -498,7 +498,47 @@ 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"if": _validators.if_, + 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 +815,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 +838,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``. |