Skip to content

Commit

Permalink
limited temp solution for deref of fragmented refs
Browse files Browse the repository at this point in the history
  • Loading branch information
christophevg committed Mar 5, 2021
1 parent d2d4a64 commit c2a3a21
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 24 deletions.
2 changes: 1 addition & 1 deletion schema_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.0.17"
__version__ = "0.0.18"
66 changes: 46 additions & 20 deletions schema_tools/schema/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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()
Expand All @@ -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):
Expand Down
3 changes: 3 additions & 0 deletions tests/schemas/invoice.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"type" : {
"$ref" : "#/definitions/type"
},
"kind" : {
"$ref" : "file:tests/schemas/product.json#/definitions/kind"
},
"product" : {
"$ref" : "file:tests/schemas/product.json"
},
Expand Down
15 changes: 15 additions & 0 deletions tests/schemas/product.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,20 @@
"subproduct" : {
"$ref" : "#"
}
},
"definitions" : {
"kindEnum" : {
"enum" : [
"a", "b", "c"
]
},
"kind" : {
"type" : "object",
"properties" : {
"x": {
"$ref" : "#/definitions/kindEnum"
}
}
}
}
}
5 changes: 3 additions & 2 deletions tests/test_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ def test_dependency_discovery(asset):
original_file = asset("invoice.json")
schema = load(original_file)
# invoice
# kind
# product
# guid
# money
Expand All @@ -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 = """
Expand Down
8 changes: 7 additions & 1 deletion tests/test_deref_to_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"


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"]

0 comments on commit c2a3a21

Please sign in to comment.