From 7fa937795f33282e2511703d86b1965f2d004b58 Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Fri, 17 Jul 2020 10:20:21 +1000 Subject: [PATCH] Fix #58 items merging --- CHANGELOG.md | 3 ++ src/hypothesis_jsonschema/__init__.py | 2 +- src/hypothesis_jsonschema/_canonicalise.py | 41 ++++++++++++++++++++-- tests/test_canonicalise.py | 19 ++++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edef7c8..b19acb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +#### 0.17.3 - 2020-07-17 +- improved handling of overlapping `items` keywords (#58) + #### 0.17.2 - 2020-07-16 - improved handling of overlapping `dependencies` keywords (#57) diff --git a/src/hypothesis_jsonschema/__init__.py b/src/hypothesis_jsonschema/__init__.py index a1b40ad..d8b6236 100644 --- a/src/hypothesis_jsonschema/__init__.py +++ b/src/hypothesis_jsonschema/__init__.py @@ -3,7 +3,7 @@ The only public API is `from_schema`; check the docstring for details. """ -__version__ = "0.17.2" +__version__ = "0.17.3" __all__ = ["from_schema"] from ._from_schema import from_schema diff --git a/src/hypothesis_jsonschema/_canonicalise.py b/src/hypothesis_jsonschema/_canonicalise.py index 47d7de5..b653f67 100644 --- a/src/hypothesis_jsonschema/_canonicalise.py +++ b/src/hypothesis_jsonschema/_canonicalise.py @@ -13,6 +13,7 @@ between "I'd like it to be faster" and "doesn't finish at all". """ +import itertools import json import math import re @@ -298,7 +299,7 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]: elif isinstance(schema.get(key), (bool, dict)): schema[key] = canonicalish(schema[key]) else: - assert key not in schema + assert key not in schema, (key, schema[key]) for key in SCHEMA_OBJECT_KEYS: if key in schema: schema[key] = { @@ -831,8 +832,42 @@ def merged(schemas: List[Any]) -> Optional[Schema]: return None odeps[k] = m odeps.update(s.pop("dependencies")) - - # TODO: merge `items` schemas or lists-of-schemas + if "items" in out or "items" in s: + oitems = out.pop("items", TRUTHY) + sitems = s.pop("items", TRUTHY) + if isinstance(oitems, list) and isinstance(sitems, list): + out["items"] = [] + out["additionalItems"] = merged( + [ + out.get("additionalItems", TRUTHY), + s.get("additionalItems", TRUTHY), + ] + ) + for a, b in itertools.zip_longest(oitems, sitems): + if a is None: + a = out.get("additionalItems", TRUTHY) + elif b is None: + b = s.get("additionalItems", TRUTHY) + out["items"].append(merged([a, b])) + elif isinstance(oitems, list): + out["items"] = [merged([x, sitems]) for x in oitems] + out["additionalItems"] = merged( + [out.get("additionalItems", TRUTHY), sitems] + ) + elif isinstance(sitems, list): + out["items"] = [merged([x, oitems]) for x in sitems] + out["additionalItems"] = merged( + [s.get("additionalItems", TRUTHY), oitems] + ) + else: + out["items"] = merged([oitems, sitems]) + if out["items"] is None: + return None + if isinstance(out["items"], list) and None in out["items"]: + return None + if out.get("additionalItems", TRUTHY) is None: + return None + s.pop("additionalItems", None) # This loop handles the remaining cases. Notably, we do not attempt to # merge distinct values for: diff --git a/tests/test_canonicalise.py b/tests/test_canonicalise.py index 6fd4d83..9d4df91 100644 --- a/tests/test_canonicalise.py +++ b/tests/test_canonicalise.py @@ -401,6 +401,25 @@ def test_canonicalises_to_expected(schema, expected): ], None, ), + ([{"items": {"pattern": "a"}}, {"items": {"pattern": "b"}}], None), + ([{"items": [{"pattern": "a"}]}, {"items": [{"pattern": "b"}]}], None,), + ( + [ + {"items": [{}], "additionalItems": {"pattern": "a"}}, + {"items": [{}], "additionalItems": {"pattern": "b"}}, + ], + None, + ), + ( + [ + {"items": [{}, {"type": "string"}], "additionalItems": False}, + {"items": [{"type": "string"}]}, + ], + { + "items": [{"type": "string"}, {"type": "string"}], + "additionalItems": FALSEY, + }, + ), ] + [ ([{lo: 0, hi: 9}, {lo: 1, hi: 10}], {lo: 1, hi: 9})