diff --git a/schema_tools/__init__.py b/schema_tools/__init__.py index 39e1636..e37b10b 100644 --- a/schema_tools/__init__.py +++ b/schema_tools/__init__.py @@ -1 +1 @@ -__version__ = "0.0.17" \ No newline at end of file +__version__ = "0.0.18" \ No newline at end of file diff --git a/schema_tools/schema/json.py b/schema_tools/schema/json.py index 936f6e7..337f30d 100644 --- a/schema_tools/schema/json.py +++ b/schema_tools/schema/json.py @@ -27,10 +27,10 @@ def __init__(self, properties=None, definitions=None, else: raise ValueError("can't handle properties", self.properties) - if definitions is None: definitions = [] - self.definitions = definitions - for definition in self.definitions: - definition.parent = self + self.definitions = [] + if definitions: + for definition in definitions: + self.add_definition(definition) self.allOf = allOf if self.allOf: self.allOf.parent = self @@ -39,6 +39,10 @@ def __init__(self, properties=None, definitions=None, self.oneOf = oneOf if self.oneOf: self.oneOf.parent = self + def add_definition(self, definition): + definition.parent = self + self.definitions.append(definition) + def definition(self, key, return_definition=True): for definition in self.definitions: if definition.name == key: @@ -353,7 +357,14 @@ def to_dict(self, deref=False, prefix=None, stack=None): return { "$ref" : self.ref } def resolve(self, return_definition=True, strip_id=False): - url, fragment = urldefrag(self.ref) + url = None + fragment = None + parts = self.ref.split("#") + if len(parts) == 1: + url = self.ref + else: + url = parts[0] + fragment = parts[1] if url: doc = self._fetch(url) if strip_id: @@ -364,22 +375,36 @@ def resolve(self, return_definition=True, strip_id=False): else: doc = self.root - if fragment: - if fragment.startswith("/definitions/"): - name = fragment.replace("/definitions/", "") - if not doc.definition: - raise ValueError("doc " + repr(doc) + " has no definitions ?!") - return doc.definition(name, return_definition=return_definition) - elif fragment.startswith("/properties/"): - name = fragment.replace("/properties/", "") - return doc.property(name, return_definition=return_definition) - elif fragment.startswith("/components/schemas/"): - name = fragment.replace("/components/schemas/", "") - return doc.definition(name, return_definition=return_definition) - else: - raise NotImplementedError + if not fragment: return doc + + name = None + + if fragment.startswith("/definitions/"): + name = fragment.replace("/definitions/", "") + if not doc.definition: + raise ValueError("doc " + repr(doc) + " has no definitions ?!") + fragment_schema = doc.definition(name, return_definition=return_definition) + elif fragment.startswith("/properties/"): + name = fragment.replace("/properties/", "") + fragment_schema = doc.property(name, return_definition=return_definition) + elif fragment.startswith("/components/schemas/"): + name = fragment.replace("/components/schemas/", "") + fragment_schema = doc.definition(name, return_definition=return_definition) + else: + raise NotImplementedError + + # FIXME: when refering to a fragment, the fragment itself can refer to + # something else in its own file. A partial solution here includes + # all other definitions. Refering to properties or the whole schema + # remains problematic. - return doc + if isinstance(fragment_schema, ObjectSchema) and doc.definition: + for definition in doc.definitions: + if definition.name != name: + print("adding", definition.name) + fragment_schema.add_definition(Definition(definition.name, definition._definition)) + + return fragment_schema def _fetch(self, url): s = requests.Session() @@ -402,6 +427,7 @@ def _fetch(self, url): try: return loads(src, parser=yaml) except Exception as e: + print(src) raise ValueError("unable to parse '{}', due to '{}'".format(url, str(e))) def _select(self, *path, stack=None): diff --git a/tests/schemas/invoice.json b/tests/schemas/invoice.json index 94d27ac..bee397b 100644 --- a/tests/schemas/invoice.json +++ b/tests/schemas/invoice.json @@ -12,6 +12,9 @@ "type" : { "$ref" : "#/definitions/type" }, + "kind" : { + "$ref" : "file:tests/schemas/product.json#/definitions/kind" + }, "product" : { "$ref" : "file:tests/schemas/product.json" }, diff --git a/tests/schemas/product.json b/tests/schemas/product.json index fe60f9b..01d69d9 100644 --- a/tests/schemas/product.json +++ b/tests/schemas/product.json @@ -16,5 +16,20 @@ "subproduct" : { "$ref" : "#" } + }, + "definitions" : { + "kindEnum" : { + "enum" : [ + "a", "b", "c" + ] + }, + "kind" : { + "type" : "object", + "properties" : { + "x": { + "$ref" : "#/definitions/kindEnum" + } + } + } } } diff --git a/tests/test_dependencies.py b/tests/test_dependencies.py index b9b6398..73bfee0 100644 --- a/tests/test_dependencies.py +++ b/tests/test_dependencies.py @@ -4,6 +4,7 @@ def test_dependency_discovery(asset): original_file = asset("invoice.json") schema = load(original_file) # invoice + # kind # product # guid # money @@ -12,8 +13,8 @@ def test_dependency_discovery(asset): # currencies # ---------- # product, guid, money - assert len(schema.dependencies()) == 2 # product and money - assert len(schema.dependencies(external=True)) == 4 # + guid and currencies + assert len(schema.dependencies()) == 3 # kind, product & money + assert len(schema.dependencies(external=True)) == 5 # + guid and currencies def test_dependencies_within_references(asset): src = """ diff --git a/tests/test_deref_to_dict.py b/tests/test_deref_to_dict.py index ba6662f..309fd0d 100644 --- a/tests/test_deref_to_dict.py +++ b/tests/test_deref_to_dict.py @@ -29,4 +29,10 @@ def test_ensure_dereferenced_combinations_generate_correct_ref_path(asset): assert d["properties"]["lines"]["anyOf"][1]["properties"]["subproduct"]["$ref"] == \ "#/properties/lines/anyOf/1" - \ No newline at end of file + +def test_ensure_dereferenced_fragment_includes_local_references(asset): + original_file = asset("invoice.json") + d = load(original_file).to_dict(deref=True) + + assert "definitions" in d["properties"]["lines"]["items"]["properties"]["kind"] + assert "kindEnum" in d["properties"]["lines"]["items"]["properties"]["kind"]["definitions"]