Skip to content

Commit 1a7a935

Browse files
committed
Merge remote-tracking branch 'anexia-it/draft2020-12'
* 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
2 parents 72a0c60 + 4547b2a commit 1a7a935

20 files changed

+1095
-59
lines changed

jsonschema/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
draft4_format_checker,
1515
draft6_format_checker,
1616
draft7_format_checker,
17+
draft202012_format_checker,
1718
)
1819
from jsonschema._types import TypeChecker
1920
from jsonschema.exceptions import (
@@ -28,6 +29,7 @@
2829
Draft4Validator,
2930
Draft6Validator,
3031
Draft7Validator,
32+
Draft202012Validator,
3133
RefResolver,
3234
validate,
3335
)

jsonschema/_format.py

+75-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from uuid import UUID
12
import datetime
23
import ipaddress
34
import re
@@ -131,13 +132,14 @@ def conforms(self, instance, format):
131132
draft4_format_checker = FormatChecker()
132133
draft6_format_checker = FormatChecker()
133134
draft7_format_checker = FormatChecker()
134-
135+
draft202012_format_checker = FormatChecker()
135136

136137
_draft_checkers = dict(
137138
draft3=draft3_format_checker,
138139
draft4=draft4_format_checker,
139140
draft6=draft6_format_checker,
140141
draft7=draft7_format_checker,
142+
draft202012=draft202012_format_checker,
141143
)
142144

143145

@@ -147,12 +149,14 @@ def _checks_drafts(
147149
draft4=None,
148150
draft6=None,
149151
draft7=None,
152+
draft202012=None,
150153
raises=(),
151154
):
152155
draft3 = draft3 or name
153156
draft4 = draft4 or name
154157
draft6 = draft6 or name
155158
draft7 = draft7 or name
159+
draft202012 = draft202012 or name
156160

157161
def wrap(func):
158162
if draft3:
@@ -163,13 +167,17 @@ def wrap(func):
163167
func = _draft_checkers["draft6"].checks(draft6, raises)(func)
164168
if draft7:
165169
func = _draft_checkers["draft7"].checks(draft7, raises)(func)
170+
if draft202012:
171+
func = _draft_checkers["draft202012"].checks(
172+
draft202012, raises
173+
)(func)
166174

167175
# Oy. This is bad global state, but relied upon for now, until
168176
# deprecation. See https://github.com/Julian/jsonschema/issues/519
169177
# and test_format_checkers_come_with_defaults
170-
FormatChecker.cls_checks(draft7 or draft6 or draft4 or draft3, raises)(
171-
func,
172-
)
178+
FormatChecker.cls_checks(
179+
draft202012 or draft7 or draft6 or draft4 or draft3, raises
180+
)(func)
173181
return func
174182
return wrap
175183

@@ -187,6 +195,7 @@ def is_email(instance):
187195
draft4="ipv4",
188196
draft6="ipv4",
189197
draft7="ipv4",
198+
draft202012="ipv4",
190199
raises=ipaddress.AddressValueError,
191200
)
192201
def is_ipv4(instance):
@@ -213,6 +222,7 @@ def is_ipv6(instance):
213222
draft4="hostname",
214223
draft6="hostname",
215224
draft7="hostname",
225+
draft202012="hostname",
216226
)
217227
def is_host_name(instance):
218228
if not isinstance(instance, str):
@@ -228,6 +238,7 @@ def is_host_name(instance):
228238
else:
229239
@_checks_drafts(
230240
draft7="idn-hostname",
241+
draft202012="idn-hostname",
231242
raises=(idna.IDNAError, UnicodeError),
232243
)
233244
def is_idn_host_name(instance):
@@ -254,6 +265,7 @@ def is_uri(instance):
254265
@_checks_drafts(
255266
draft6="uri-reference",
256267
draft7="uri-reference",
268+
draft202012="uri-reference",
257269
raises=ValueError,
258270
)
259271
def is_uri_reference(instance):
@@ -262,19 +274,30 @@ def is_uri_reference(instance):
262274
return validate_rfc3986(instance, rule="URI_reference")
263275

264276
else:
265-
@_checks_drafts(draft7="iri", raises=ValueError)
277+
@_checks_drafts(
278+
draft7="iri",
279+
draft202012="iri",
280+
raises=ValueError,
281+
)
266282
def is_iri(instance):
267283
if not isinstance(instance, str):
268284
return True
269285
return rfc3987.parse(instance, rule="IRI")
270286

271-
@_checks_drafts(draft7="iri-reference", raises=ValueError)
287+
@_checks_drafts(
288+
draft7="iri-reference",
289+
draft202012="iri-reference",
290+
raises=ValueError,
291+
)
272292
def is_iri_reference(instance):
273293
if not isinstance(instance, str):
274294
return True
275295
return rfc3987.parse(instance, rule="IRI_reference")
276296

277-
@_checks_drafts(name="uri", raises=ValueError)
297+
@_checks_drafts(
298+
name="uri",
299+
raises=ValueError,
300+
)
278301
def is_uri(instance):
279302
if not isinstance(instance, str):
280303
return True
@@ -283,6 +306,7 @@ def is_uri(instance):
283306
@_checks_drafts(
284307
draft6="uri-reference",
285308
draft7="uri-reference",
309+
draft202012="uri-reference",
286310
raises=ValueError,
287311
)
288312
def is_uri_reference(instance):
@@ -306,7 +330,10 @@ def is_datetime(instance):
306330
return True
307331
return validate_rfc3339(instance.upper())
308332

309-
@_checks_drafts(draft7="time")
333+
@_checks_drafts(
334+
draft7="time",
335+
draft202012="time",
336+
)
310337
def is_time(instance):
311338
if not isinstance(instance, str):
312339
return True
@@ -327,7 +354,12 @@ def _is_date(instance):
327354
return datetime.datetime.strptime(instance, "%Y-%m-%d")
328355

329356

330-
@_checks_drafts(draft3="date", draft7="date", raises=ValueError)
357+
@_checks_drafts(
358+
draft3="date",
359+
draft7="date",
360+
draft202012="date",
361+
raises=ValueError,
362+
)
331363
def is_date(instance):
332364
if not isinstance(instance, str):
333365
return True
@@ -377,6 +409,7 @@ def is_css3_color(instance):
377409
@_checks_drafts(
378410
draft6="json-pointer",
379411
draft7="json-pointer",
412+
draft202012="json-pointer",
380413
raises=jsonpointer.JsonPointerException,
381414
)
382415
def is_json_pointer(instance):
@@ -390,6 +423,7 @@ def is_json_pointer(instance):
390423
# into a new external library.
391424
@_checks_drafts(
392425
draft7="relative-json-pointer",
426+
draft202012="relative-json-pointer",
393427
raises=jsonpointer.JsonPointerException,
394428
)
395429
def is_relative_json_pointer(instance):
@@ -400,6 +434,10 @@ def is_relative_json_pointer(instance):
400434
non_negative_integer, rest = [], ""
401435
for i, character in enumerate(instance):
402436
if character.isdigit():
437+
# digits with a leading "0" are not allowed
438+
if i > 0 and int(instance[i-1]) == 0:
439+
return False
440+
403441
non_negative_integer.append(character)
404442
continue
405443

@@ -419,8 +457,36 @@ def is_relative_json_pointer(instance):
419457
@_checks_drafts(
420458
draft6="uri-template",
421459
draft7="uri-template",
460+
draft202012="uri-template",
422461
)
423462
def is_uri_template(instance):
424463
if not isinstance(instance, str):
425464
return True
426465
return uri_template.validate(instance)
466+
467+
468+
try:
469+
import isoduration
470+
except ImportError: # pragma: no cover
471+
pass
472+
else:
473+
@_checks_drafts(
474+
draft202012="duration",
475+
raises=isoduration.DurationParsingException,
476+
)
477+
def is_duration(instance):
478+
if not isinstance(instance, str):
479+
return True
480+
return isoduration.parse_duration(instance)
481+
482+
483+
@_checks_drafts(
484+
draft202012="uuid",
485+
raises=ValueError,
486+
)
487+
def is_uuid(instance):
488+
if not isinstance(instance, str):
489+
return True
490+
if "-" not in instance:
491+
raise ValueError("Invalid UUID format")
492+
return UUID(instance)

jsonschema/_legacy_validators.py

+69
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22
from jsonschema.exceptions import ValidationError
33

44

5+
def ignore_ref_siblings(schema):
6+
"""
7+
Returns a list of validators that should apply for the given schema
8+
Used for draft7 and earlier
9+
"""
10+
ref = schema.get(u"$ref")
11+
if ref is not None:
12+
return [(u"$ref", ref)]
13+
else:
14+
return schema.items()
15+
16+
517
def dependencies_draft3(validator, dependencies, instance, schema):
618
if not validator.is_type(instance, "object"):
719
return
@@ -27,6 +39,37 @@ def dependencies_draft3(validator, dependencies, instance, schema):
2739
yield ValidationError(message % (each, property))
2840

2941

42+
def dependencies_draft4_draft6_draft7(
43+
validator,
44+
dependencies,
45+
instance,
46+
schema,
47+
):
48+
"""
49+
Support for the ``dependencies`` validator from pre-draft 2019-09.
50+
51+
In later drafts, the validator was split into separate
52+
``dependentRequired`` and ``dependentSchemas`` validators.
53+
"""
54+
if not validator.is_type(instance, "object"):
55+
return
56+
57+
for property, dependency in dependencies.items():
58+
if property not in instance:
59+
continue
60+
61+
if validator.is_type(dependency, "array"):
62+
for each in dependency:
63+
if each not in instance:
64+
message = "%r is a dependency of %r"
65+
yield ValidationError(message % (each, property))
66+
else:
67+
for error in validator.descend(
68+
instance, dependency, schema_path=property,
69+
):
70+
yield error
71+
72+
3073
def disallow_draft3(validator, disallow, instance, schema):
3174
for disallowed in _utils.ensure_list(disallow):
3275
if validator.is_valid(instance, {"type": [disallowed]}):
@@ -61,6 +104,22 @@ def items_draft3_draft4(validator, items, instance, schema):
61104
yield error
62105

63106

107+
def items_draft6_draft7(validator, items, instance, schema):
108+
if not validator.is_type(instance, "array"):
109+
return
110+
111+
if validator.is_type(items, "array"):
112+
for (index, item), subschema in zip(enumerate(instance), items):
113+
for error in validator.descend(
114+
item, subschema, path=index, schema_path=index,
115+
):
116+
yield error
117+
else:
118+
for index, item in enumerate(instance):
119+
for error in validator.descend(item, items, path=index):
120+
yield error
121+
122+
64123
def minimum_draft3_draft4(validator, minimum, instance, schema):
65124
if not validator.is_type(instance, "number"):
66125
return
@@ -138,3 +197,13 @@ def type_draft3(validator, types, instance, schema):
138197
yield ValidationError(
139198
_utils.types_msg(instance, types), context=all_errors,
140199
)
200+
201+
202+
def contains_draft6_draft7(validator, contains, instance, schema):
203+
if not validator.is_type(instance, "array"):
204+
return
205+
206+
if not any(validator.is_valid(element, contains) for element in instance):
207+
yield ValidationError(
208+
"None of %r are valid under the given schema" % (instance,)
209+
)

jsonschema/_types.py

+1
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,4 @@ def remove(self, *types):
185185
),
186186
)
187187
draft7_type_checker = draft6_type_checker
188+
draft202012_type_checker = draft7_type_checker

0 commit comments

Comments
 (0)