16
16
import json
17
17
import math
18
18
import re
19
+ from fractions import Fraction
19
20
from typing import Any , Dict , List , Optional , Tuple , Union
20
21
21
22
import jsonschema
@@ -263,6 +264,9 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
263
264
k : v if isinstance (v , list ) else canonicalish (v )
264
265
for k , v in schema [key ].items ()
265
266
}
267
+ # multipleOf is semantically unaffected by the sign, so ensure it's positive
268
+ if "multipleOf" in schema :
269
+ schema ["multipleOf" ] = abs (schema ["multipleOf" ])
266
270
267
271
type_ = get_type (schema )
268
272
if "number" in type_ :
@@ -289,6 +293,10 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
289
293
if "integer" in type_ :
290
294
lo , hi = get_integer_bounds (schema )
291
295
mul = schema .get ("multipleOf" )
296
+ if mul is not None and "number" not in type_ and Fraction (mul ).numerator == 1 :
297
+ # Every integer is a multiple of 1/n for all natural numbers n.
298
+ schema .pop ("multipleOf" )
299
+ mul = None
292
300
if lo is not None and isinstance (mul , int ) and mul > 1 and (lo % mul ):
293
301
lo += mul - (lo % mul )
294
302
if hi is not None and isinstance (mul , int ) and mul > 1 and (hi % mul ):
@@ -303,6 +311,8 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
303
311
304
312
if lo is not None and hi is not None and lo > hi :
305
313
type_ .remove ("integer" )
314
+ elif type_ == ["integer" ] and lo == hi and make_validator (schema ).is_valid (lo ):
315
+ return {"const" : lo }
306
316
307
317
if "array" in type_ and "contains" in schema :
308
318
if isinstance (schema .get ("items" ), dict ):
@@ -542,11 +552,8 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
542
552
tmp = schema .copy ()
543
553
ao = tmp .pop ("allOf" )
544
554
out = merged ([tmp ] + ao )
545
- if isinstance ( out , dict ): # pragma: no branch
555
+ if out is not None :
546
556
schema = out
547
- # TODO: this assertion is soley because mypy 0.750 doesn't know
548
- # that `schema` is a dict otherwise. Needs minimal report upstream.
549
- assert isinstance (schema , dict )
550
557
if "oneOf" in schema :
551
558
one_of = schema .pop ("oneOf" )
552
559
assert isinstance (one_of , list )
@@ -701,8 +708,8 @@ def merged(schemas: List[Any]) -> Optional[Schema]:
701
708
if isinstance (x , int ) and isinstance (y , int ):
702
709
out ["multipleOf" ] = x * y // math .gcd (x , y )
703
710
elif x != y :
704
- ratio = max (x , y ) / min (x , y )
705
- if ratio == int ( ratio ) : # e.g. x=0.5, y=2
711
+ ratio = Fraction ( max (x , y )) / Fraction ( min (x , y ) )
712
+ if ratio . denominator == 1 : # e.g. .75, 1.5
706
713
out ["multipleOf" ] = max (x , y )
707
714
else :
708
715
return None
0 commit comments