@@ -147,6 +147,7 @@ def create(
147
147
applicable_validators : _typing .ApplicableValidators = methodcaller (
148
148
"items" ,
149
149
),
150
+ validate_hooks : Sequence [_typing .ValidateHook ] = (),
150
151
):
151
152
"""
152
153
Create a new validator class.
@@ -207,6 +208,16 @@ def create(
207
208
implement similar behavior, you can typically ignore this argument
208
209
and leave it at its default.
209
210
211
+ validate_hooks:
212
+
213
+ A list of callables, will be called after validate.
214
+
215
+ Each callable should take 4 arguments:
216
+
217
+ 1. is valid or not
218
+ 2. the instance
219
+ 3. the schema
220
+
210
221
Returns:
211
222
212
223
a new `jsonschema.protocols.Validator` class
@@ -220,6 +231,10 @@ def create(
220
231
default = referencing .Specification .OPAQUE ,
221
232
)
222
233
234
+ def _call_validate_hooks (is_valid , instance , schema ):
235
+ for hook in validate_hooks :
236
+ hook (is_valid , instance , schema )
237
+
223
238
@define
224
239
class Validator :
225
240
@@ -228,6 +243,7 @@ class Validator:
228
243
TYPE_CHECKER = type_checker
229
244
FORMAT_CHECKER = format_checker_arg
230
245
ID_OF = staticmethod (id_of )
246
+ VALIDATE_HOOKS = list (validate_hooks ) # noqa: RUF012
231
247
232
248
_APPLICABLE_VALIDATORS = applicable_validators
233
249
_validators = field (init = False , repr = False , eq = False )
@@ -368,6 +384,7 @@ def iter_errors(self, instance, _schema=None):
368
384
_schema , validators = self .schema , self ._validators
369
385
370
386
if _schema is True :
387
+ _call_validate_hooks (True , instance , _schema )
371
388
return
372
389
elif _schema is False :
373
390
yield exceptions .ValidationError (
@@ -377,8 +394,10 @@ def iter_errors(self, instance, _schema=None):
377
394
instance = instance ,
378
395
schema = _schema ,
379
396
)
397
+ _call_validate_hooks (False , instance , _schema )
380
398
return
381
399
400
+ is_valid = True
382
401
for validator , k , v in validators :
383
402
errors = validator (self , v , instance , _schema ) or ()
384
403
for error in errors :
@@ -392,7 +411,9 @@ def iter_errors(self, instance, _schema=None):
392
411
)
393
412
if k not in {"if" , "$ref" }:
394
413
error .schema_path .appendleft (k )
414
+ is_valid = False
395
415
yield error
416
+ _call_validate_hooks (is_valid , instance , _schema )
396
417
397
418
def descend (
398
419
self ,
@@ -403,6 +424,7 @@ def descend(
403
424
resolver = None ,
404
425
):
405
426
if schema is True :
427
+ _call_validate_hooks (True , instance , schema )
406
428
return
407
429
elif schema is False :
408
430
yield exceptions .ValidationError (
@@ -412,6 +434,7 @@ def descend(
412
434
instance = instance ,
413
435
schema = schema ,
414
436
)
437
+ _call_validate_hooks (False , instance , schema )
415
438
return
416
439
417
440
if self ._ref_resolver is not None :
@@ -423,6 +446,7 @@ def descend(
423
446
)
424
447
evolved = self .evolve (schema = schema , _resolver = resolver )
425
448
449
+ is_valid = True
426
450
for k , v in applicable_validators (schema ):
427
451
validator = evolved .VALIDATORS .get (k )
428
452
if validator is None :
@@ -444,10 +468,15 @@ def descend(
444
468
error .path .appendleft (path )
445
469
if schema_path is not None :
446
470
error .schema_path .appendleft (schema_path )
471
+ is_valid = False
447
472
yield error
473
+ _call_validate_hooks (is_valid , instance , schema )
448
474
449
- def validate (self , * args , ** kwargs ):
450
- for error in self .iter_errors (* args , ** kwargs ):
475
+ def validate (self , instance , _schema = None ):
476
+ for error in self .iter_errors (instance , _schema ):
477
+ if _schema is None :
478
+ _schema = self .schema
479
+ _call_validate_hooks (False , instance , _schema )
451
480
raise error
452
481
453
482
def is_type (self , instance , type ):
@@ -498,6 +527,8 @@ def is_valid(self, instance, _schema=None):
498
527
self = self .evolve (schema = _schema )
499
528
500
529
error = next (self .iter_errors (instance ), None )
530
+ if error is not None :
531
+ _call_validate_hooks (False , instance , self .schema )
501
532
return error is None
502
533
503
534
evolve_fields = [
@@ -520,6 +551,7 @@ def extend(
520
551
version = None ,
521
552
type_checker = None ,
522
553
format_checker = None ,
554
+ validate_hooks = (),
523
555
):
524
556
"""
525
557
Create a new validator class by extending an existing one.
@@ -565,6 +597,12 @@ def extend(
565
597
If unprovided, the format checker of the extended
566
598
`jsonschema.protocols.Validator` will be carried along.
567
599
600
+ validate_hooks (collections.abc.Sequence):
601
+
602
+ a list of new validate hooks to extend with, whose
603
+ structure is as in `create`.
604
+
605
+
568
606
Returns:
569
607
570
608
a new `jsonschema.protocols.Validator` class extending the one
@@ -584,6 +622,9 @@ def extend(
584
622
all_validators = dict (validator .VALIDATORS )
585
623
all_validators .update (validators )
586
624
625
+ all_validate_hooks = list (validator .VALIDATE_HOOKS )
626
+ all_validate_hooks .extend (validate_hooks )
627
+
587
628
if type_checker is None :
588
629
type_checker = validator .TYPE_CHECKER
589
630
if format_checker is None :
@@ -596,6 +637,7 @@ def extend(
596
637
format_checker = format_checker ,
597
638
id_of = validator .ID_OF ,
598
639
applicable_validators = validator ._APPLICABLE_VALIDATORS ,
640
+ validate_hooks = all_validate_hooks ,
599
641
)
600
642
601
643
0 commit comments