Skip to content

Commit

Permalink
Merge remote-tracking branch 'anexia-it/draft2020-12'
Browse files Browse the repository at this point in the history
* anexia-it/draft2020-12:
  #782: Remove ecmascript validation, extend dynamicRef skip description
  #782: Add compatibility to draft7 and older
  #782: Code clenaup, fixes validation messages
  #782: Extend format tests
  #782: Resolve meta schema vocabularies from local cache
  #782: Refactor items behavior with prefixItems
  #782: Update validation message for unevaluatedProperties and unevaluatedItems
  #782: Fixes failing styles
  #782: Adapt validator test for draft2020-12, fixes code styles
  #782: Fixes relative json pointer format validation for leading zero on digit
  #782: Load dependencies from legacy validators
  #782: Implements dynamicRef validations
  #782: Add validation for uuid format
  #782: Implements defs validations
  #782: Extend resolver for anchor
  #782: Fixes ref resolver for folders
  #782: Fixes ref validation priority
  #782: Extend implementation of ref
  #782: Implements unevaluatedProperties validations
  #782: Implements unevaluatedItems validations
  #782: Extend contains with minContains and maxContaints, add contains legacy validator
  #782: Adapt items to work with prefixItems
  #782: Add checks for prefixItems, basic check for unevaluatedItems
  #782: Extend format check for draft2020-12, add duration format check
  #782: Add dependentRequired and dependentSchemas validation
  #782: Split format and regular test cases on draft2020-12
  #782: Enable draft2020-12 test suite
  • Loading branch information
Julian committed Aug 4, 2021
2 parents 72a0c60 + 4547b2a commit 1a7a935
Show file tree
Hide file tree
Showing 20 changed files with 1,095 additions and 59 deletions.
2 changes: 2 additions & 0 deletions jsonschema/__init__.py
Expand Up @@ -14,6 +14,7 @@
draft4_format_checker,
draft6_format_checker,
draft7_format_checker,
draft202012_format_checker,
)
from jsonschema._types import TypeChecker
from jsonschema.exceptions import (
Expand All @@ -28,6 +29,7 @@
Draft4Validator,
Draft6Validator,
Draft7Validator,
Draft202012Validator,
RefResolver,
validate,
)
Expand Down
84 changes: 75 additions & 9 deletions jsonschema/_format.py
@@ -1,3 +1,4 @@
from uuid import UUID
import datetime
import ipaddress
import re
Expand Down Expand Up @@ -131,13 +132,14 @@ def conforms(self, instance, format):
draft4_format_checker = FormatChecker()
draft6_format_checker = FormatChecker()
draft7_format_checker = FormatChecker()

draft202012_format_checker = FormatChecker()

_draft_checkers = dict(
draft3=draft3_format_checker,
draft4=draft4_format_checker,
draft6=draft6_format_checker,
draft7=draft7_format_checker,
draft202012=draft202012_format_checker,
)


Expand All @@ -147,12 +149,14 @@ def _checks_drafts(
draft4=None,
draft6=None,
draft7=None,
draft202012=None,
raises=(),
):
draft3 = draft3 or name
draft4 = draft4 or name
draft6 = draft6 or name
draft7 = draft7 or name
draft202012 = draft202012 or name

def wrap(func):
if draft3:
Expand All @@ -163,13 +167,17 @@ def wrap(func):
func = _draft_checkers["draft6"].checks(draft6, raises)(func)
if draft7:
func = _draft_checkers["draft7"].checks(draft7, raises)(func)
if draft202012:
func = _draft_checkers["draft202012"].checks(
draft202012, raises
)(func)

# Oy. This is bad global state, but relied upon for now, until
# deprecation. See https://github.com/Julian/jsonschema/issues/519
# and test_format_checkers_come_with_defaults
FormatChecker.cls_checks(draft7 or draft6 or draft4 or draft3, raises)(
func,
)
FormatChecker.cls_checks(
draft202012 or draft7 or draft6 or draft4 or draft3, raises
)(func)
return func
return wrap

Expand All @@ -187,6 +195,7 @@ def is_email(instance):
draft4="ipv4",
draft6="ipv4",
draft7="ipv4",
draft202012="ipv4",
raises=ipaddress.AddressValueError,
)
def is_ipv4(instance):
Expand All @@ -213,6 +222,7 @@ def is_ipv6(instance):
draft4="hostname",
draft6="hostname",
draft7="hostname",
draft202012="hostname",
)
def is_host_name(instance):
if not isinstance(instance, str):
Expand All @@ -228,6 +238,7 @@ def is_host_name(instance):
else:
@_checks_drafts(
draft7="idn-hostname",
draft202012="idn-hostname",
raises=(idna.IDNAError, UnicodeError),
)
def is_idn_host_name(instance):
Expand All @@ -254,6 +265,7 @@ def is_uri(instance):
@_checks_drafts(
draft6="uri-reference",
draft7="uri-reference",
draft202012="uri-reference",
raises=ValueError,
)
def is_uri_reference(instance):
Expand All @@ -262,19 +274,30 @@ def is_uri_reference(instance):
return validate_rfc3986(instance, rule="URI_reference")

else:
@_checks_drafts(draft7="iri", raises=ValueError)
@_checks_drafts(
draft7="iri",
draft202012="iri",
raises=ValueError,
)
def is_iri(instance):
if not isinstance(instance, str):
return True
return rfc3987.parse(instance, rule="IRI")

@_checks_drafts(draft7="iri-reference", raises=ValueError)
@_checks_drafts(
draft7="iri-reference",
draft202012="iri-reference",
raises=ValueError,
)
def is_iri_reference(instance):
if not isinstance(instance, str):
return True
return rfc3987.parse(instance, rule="IRI_reference")

@_checks_drafts(name="uri", raises=ValueError)
@_checks_drafts(
name="uri",
raises=ValueError,
)
def is_uri(instance):
if not isinstance(instance, str):
return True
Expand All @@ -283,6 +306,7 @@ def is_uri(instance):
@_checks_drafts(
draft6="uri-reference",
draft7="uri-reference",
draft202012="uri-reference",
raises=ValueError,
)
def is_uri_reference(instance):
Expand All @@ -306,7 +330,10 @@ def is_datetime(instance):
return True
return validate_rfc3339(instance.upper())

@_checks_drafts(draft7="time")
@_checks_drafts(
draft7="time",
draft202012="time",
)
def is_time(instance):
if not isinstance(instance, str):
return True
Expand All @@ -327,7 +354,12 @@ def _is_date(instance):
return datetime.datetime.strptime(instance, "%Y-%m-%d")


@_checks_drafts(draft3="date", draft7="date", raises=ValueError)
@_checks_drafts(
draft3="date",
draft7="date",
draft202012="date",
raises=ValueError,
)
def is_date(instance):
if not isinstance(instance, str):
return True
Expand Down Expand Up @@ -377,6 +409,7 @@ def is_css3_color(instance):
@_checks_drafts(
draft6="json-pointer",
draft7="json-pointer",
draft202012="json-pointer",
raises=jsonpointer.JsonPointerException,
)
def is_json_pointer(instance):
Expand All @@ -390,6 +423,7 @@ def is_json_pointer(instance):
# into a new external library.
@_checks_drafts(
draft7="relative-json-pointer",
draft202012="relative-json-pointer",
raises=jsonpointer.JsonPointerException,
)
def is_relative_json_pointer(instance):
Expand All @@ -400,6 +434,10 @@ def is_relative_json_pointer(instance):
non_negative_integer, rest = [], ""
for i, character in enumerate(instance):
if character.isdigit():
# digits with a leading "0" are not allowed
if i > 0 and int(instance[i-1]) == 0:
return False

non_negative_integer.append(character)
continue

Expand All @@ -419,8 +457,36 @@ def is_relative_json_pointer(instance):
@_checks_drafts(
draft6="uri-template",
draft7="uri-template",
draft202012="uri-template",
)
def is_uri_template(instance):
if not isinstance(instance, str):
return True
return uri_template.validate(instance)


try:
import isoduration
except ImportError: # pragma: no cover
pass
else:
@_checks_drafts(
draft202012="duration",
raises=isoduration.DurationParsingException,
)
def is_duration(instance):
if not isinstance(instance, str):
return True
return isoduration.parse_duration(instance)


@_checks_drafts(
draft202012="uuid",
raises=ValueError,
)
def is_uuid(instance):
if not isinstance(instance, str):
return True
if "-" not in instance:
raise ValueError("Invalid UUID format")
return UUID(instance)
69 changes: 69 additions & 0 deletions jsonschema/_legacy_validators.py
Expand Up @@ -2,6 +2,18 @@
from jsonschema.exceptions import ValidationError


def ignore_ref_siblings(schema):
"""
Returns a list of validators that should apply for the given schema
Used for draft7 and earlier
"""
ref = schema.get(u"$ref")
if ref is not None:
return [(u"$ref", ref)]
else:
return schema.items()


def dependencies_draft3(validator, dependencies, instance, schema):
if not validator.is_type(instance, "object"):
return
Expand All @@ -27,6 +39,37 @@ def dependencies_draft3(validator, dependencies, instance, schema):
yield ValidationError(message % (each, property))


def dependencies_draft4_draft6_draft7(
validator,
dependencies,
instance,
schema,
):
"""
Support for the ``dependencies`` validator from pre-draft 2019-09.
In later drafts, the validator was split into separate
``dependentRequired`` and ``dependentSchemas`` validators.
"""
if not validator.is_type(instance, "object"):
return

for property, dependency in dependencies.items():
if property not in instance:
continue

if validator.is_type(dependency, "array"):
for each in dependency:
if each not in instance:
message = "%r is a dependency of %r"
yield ValidationError(message % (each, property))
else:
for error in validator.descend(
instance, dependency, schema_path=property,
):
yield error


def disallow_draft3(validator, disallow, instance, schema):
for disallowed in _utils.ensure_list(disallow):
if validator.is_valid(instance, {"type": [disallowed]}):
Expand Down Expand Up @@ -61,6 +104,22 @@ def items_draft3_draft4(validator, items, instance, schema):
yield error


def items_draft6_draft7(validator, items, instance, schema):
if not validator.is_type(instance, "array"):
return

if validator.is_type(items, "array"):
for (index, item), subschema in zip(enumerate(instance), items):
for error in validator.descend(
item, subschema, path=index, schema_path=index,
):
yield error
else:
for index, item in enumerate(instance):
for error in validator.descend(item, items, path=index):
yield error


def minimum_draft3_draft4(validator, minimum, instance, schema):
if not validator.is_type(instance, "number"):
return
Expand Down Expand Up @@ -138,3 +197,13 @@ def type_draft3(validator, types, instance, schema):
yield ValidationError(
_utils.types_msg(instance, types), context=all_errors,
)


def contains_draft6_draft7(validator, contains, instance, schema):
if not validator.is_type(instance, "array"):
return

if not any(validator.is_valid(element, contains) for element in instance):
yield ValidationError(
"None of %r are valid under the given schema" % (instance,)
)
1 change: 1 addition & 0 deletions jsonschema/_types.py
Expand Up @@ -185,3 +185,4 @@ def remove(self, *types):
),
)
draft7_type_checker = draft6_type_checker
draft202012_type_checker = draft7_type_checker

0 comments on commit 1a7a935

Please sign in to comment.