@@ -422,9 +422,25 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
422
422
"maxProperties" , math .inf
423
423
):
424
424
type_ .remove ("object" )
425
- # Remove no-op requires
426
- if "required" in schema and not schema ["required" ]:
427
- schema .pop ("required" )
425
+ # Discard dependencies values that don't restrict anything
426
+ for k , v in schema .get ("dependencies" , {}).copy ().items ():
427
+ if v == [] or v == TRUTHY :
428
+ schema ["dependencies" ].pop (k )
429
+ # Remove no-op keywords
430
+ for kw , identity in {
431
+ "minItems" : 0 ,
432
+ "items" : {},
433
+ "additionalItems" : {},
434
+ "dependencies" : {},
435
+ "minProperties" : 0 ,
436
+ "properties" : {},
437
+ "propertyNames" : {},
438
+ "patternProperties" : {},
439
+ "additionalProperties" : {},
440
+ "required" : [],
441
+ }.items ():
442
+ if kw in schema and schema [kw ] == identity :
443
+ schema .pop (kw )
428
444
# Canonicalise "required" schemas to remove redundancy
429
445
if "object" in type_ and "required" in schema :
430
446
assert isinstance (schema ["required" ], list )
@@ -433,16 +449,18 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
433
449
# When the presence of a required property requires other properties via
434
450
# dependencies, those properties can be moved to the base required keys.
435
451
dep_names = {
436
- k : sorted (v )
452
+ k : sorted (set ( v ) )
437
453
for k , v in schema ["dependencies" ].items ()
438
454
if isinstance (v , list )
439
455
}
456
+ schema ["dependencies" ].update (dep_names )
440
457
while reqs .intersection (dep_names ):
441
458
for r in reqs .intersection (dep_names ):
442
459
reqs .update (dep_names .pop (r ))
443
- for k , v in list (schema ["dependencies" ].items ()):
444
- if isinstance (v , list ) and k not in dep_names :
445
- schema ["dependencies" ].pop (k )
460
+ schema ["dependencies" ].pop (r )
461
+ # TODO: else merge schema-dependencies of required properties
462
+ # into the base schema after adding required back in and being
463
+ # careful to avoid an infinite loop...
446
464
schema ["required" ] = sorted (reqs )
447
465
max_ = schema .get ("maxProperties" , float ("inf" ))
448
466
assert isinstance (max_ , (int , float ))
@@ -782,9 +800,37 @@ def merged(schemas: List[Any]) -> Optional[Schema]:
782
800
s .pop ("contains" )
783
801
if "not" in out and "not" in s and out ["not" ] != s ["not" ]:
784
802
out ["not" ] = {"anyOf" : [out ["not" ], s .pop ("not" )]}
803
+ if (
804
+ "dependencies" in out
805
+ and "dependencies" in s
806
+ and out ["dependencies" ] != s ["dependencies" ]
807
+ ):
808
+ # Note: draft 2019-09 added separate keywords for name-dependencies
809
+ # and schema-dependencies, but when we add support for that it will
810
+ # be by canonicalising to the existing backwards-compatible keyword.
811
+ #
812
+ # In each dependencies dict, the keys are property names and the values
813
+ # are either a list of required names, or a schema that the whole
814
+ # instance must match. To merge a list and a schema, convert the
815
+ # former into a `required` key!
816
+ odeps = out ["dependencies" ]
817
+ for k , v in odeps .copy ().items ():
818
+ if k in s ["dependencies" ]:
819
+ sval = s ["dependencies" ].pop (k )
820
+ if isinstance (v , list ) and isinstance (sval , list ):
821
+ odeps [k ] = v + sval
822
+ continue
823
+ if isinstance (v , list ):
824
+ v = {"required" : v }
825
+ elif isinstance (sval , list ):
826
+ sval = {"required" : sval }
827
+ m = merged ([v , sval ])
828
+ if m is None :
829
+ return None
830
+ odeps [k ] = m
831
+ odeps .update (s .pop ("dependencies" ))
785
832
786
833
# TODO: merge `items` schemas or lists-of-schemas
787
- # TODO: merge dependencies
788
834
789
835
# This loop handles the remaining cases. Notably, we do not attempt to
790
836
# merge distinct values for:
0 commit comments