1
+ from __future__ import annotations
2
+
1
3
from fractions import Fraction
2
4
import re
5
+ import typing
3
6
4
7
from jsonschema ._utils import (
5
8
ensure_list ,
13
16
from jsonschema .exceptions import FormatError , ValidationError
14
17
15
18
19
+ class Keyword :
20
+ """
21
+ The Keyword wraps a function to maintain full back-compatibility.
22
+
23
+ The class adds the ability to annotate the keyword implementation while
24
+ behaving like a function, i.e. `Keyword(func)` behaves like `func` itself
25
+ in most cases, and allows a more formal description of the behavior and
26
+ dependencies.
27
+ """
28
+
29
+ def __init__ (
30
+ self ,
31
+ func : typing .Optional [callable ] = None , / ,
32
+ needs : typing .Optional [typing .Iterable [str ]] = (),
33
+ ):
34
+ self .func = func
35
+ self .needs = needs
36
+
37
+ def __call__ (self , validator , property , instance , schema ):
38
+ if self .func is None :
39
+ raise NotImplementedError (f"{ self } has no validation method." )
40
+ return self .func (validator , property , instance , schema )
41
+
42
+
43
+ def keyword (func : typing .Optional [callable ] = None , / , ** kwargs ):
44
+ """
45
+ Syntactic sugar to decorate a function as a keyword.
46
+
47
+ The function is not modified, but wrapped by a Keyword object. This allows
48
+ the same function to be reused as by multiple validators with different
49
+ annotations.
50
+
51
+ Examples
52
+ --------
53
+ Functionally equivalent to the un-decorated function.
54
+ >>> @keyword
55
+ ... def myKeyword(validator, property, instance, schema):
56
+ ... ...
57
+
58
+ Mark the keyword "else" to be evaluated after "if"
59
+ >>> @keyword(needs=["if"])
60
+ ... def else_(validator, property, instance, schema):
61
+ ... ...
62
+
63
+ >>> kw = keyword(else_, needs="if")
64
+
65
+ """
66
+ if func is None :
67
+ return lambda fun : Keyword (fun , ** kwargs )
68
+ return Keyword (func , ** kwargs )
69
+
70
+
16
71
def patternProperties (validator , patternProperties , instance , schema ):
17
72
if not validator .is_type (instance , "object" ):
18
73
return
@@ -389,6 +444,21 @@ def if_(validator, if_schema, instance, schema):
389
444
yield from validator .descend (instance , else_ , schema_path = "else" )
390
445
391
446
447
+ @keyword (
448
+ needs = [
449
+ "prefixItems" ,
450
+ "items" ,
451
+ "contains" ,
452
+ "allOf" ,
453
+ "anyOf" ,
454
+ "oneOf" ,
455
+ "not" ,
456
+ "if" ,
457
+ "then" ,
458
+ "else" ,
459
+ "dependentSchemas" ,
460
+ ],
461
+ )
392
462
def unevaluatedItems (validator , unevaluatedItems , instance , schema ):
393
463
if not validator .is_type (instance , "array" ):
394
464
return
@@ -404,6 +474,21 @@ def unevaluatedItems(validator, unevaluatedItems, instance, schema):
404
474
yield ValidationError (error % extras_msg (unevaluated_items ))
405
475
406
476
477
+ @keyword (
478
+ needs = [
479
+ "properties" ,
480
+ "patternProperties" ,
481
+ "additionalProperties" ,
482
+ "allOf" ,
483
+ "anyOf" ,
484
+ "oneOf" ,
485
+ "not" ,
486
+ "if" ,
487
+ "then" ,
488
+ "else" ,
489
+ "dependentSchemas" ,
490
+ ],
491
+ )
407
492
def unevaluatedProperties (validator , unevaluatedProperties , instance , schema ):
408
493
if not validator .is_type (instance , "object" ):
409
494
return
0 commit comments