Skip to content

Commit d626a74

Browse files
author
Peter Amstutz
committed
Implement $mixin
1 parent 93ed1fb commit d626a74

File tree

2 files changed

+66
-15
lines changed

2 files changed

+66
-15
lines changed

schema_salad/ref_resolver.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import requests
88
import urlparse
99
import re
10+
import copy
1011
import ruamel.yaml as yaml
1112
try:
1213
from ruamel.yaml import CSafeLoader as SafeLoader
@@ -259,25 +260,30 @@ def resolve_ref(self, ref, base_url=None, checklinks=True):
259260

260261
obj = None # type: Dict[unicode, Any]
261262
inc = False
263+
mixin = None
262264

263265
# If `ref` is a dict, look for special directives.
264266
if isinstance(ref, dict):
265267
obj = ref
266-
if u"$import" in ref:
268+
if u"$import" in obj:
267269
if len(obj) == 1:
268270
ref = obj[u"$import"]
269271
obj = None
270272
else:
271273
raise ValueError(
272-
"'$import' must be the only field in %s" % (str(obj)))
274+
u"'$import' must be the only field in %s" % (str(obj)))
273275
elif u"$include" in obj:
274276
if len(obj) == 1:
275277
ref = obj[u"$include"]
276278
inc = True
277279
obj = None
278280
else:
279281
raise ValueError(
280-
"'$include' must be the only field in %s" % (str(obj)))
282+
u"'$include' must be the only field in %s" % (str(obj)))
283+
elif u"$mixin" in obj:
284+
ref = obj[u"$mixin"]
285+
mixin = obj
286+
obj = None
281287
else:
282288
ref = None
283289
for identifier in self.identifiers:
@@ -286,15 +292,17 @@ def resolve_ref(self, ref, base_url=None, checklinks=True):
286292
break
287293
if not ref:
288294
raise ValueError(
289-
"Object `%s` does not have identifier field in %s" % (obj, self.identifiers))
295+
u"Object `%s` does not have identifier field in %s" % (obj, self.identifiers))
290296

291297
if not isinstance(ref, (str, unicode)):
292-
raise ValueError("Must be string: `%s`" % str(ref))
298+
raise ValueError(u"Must be string: `%s`" % str(ref))
293299

300+
print ref, base_url
294301
url = self.expand_url(ref, base_url, scoped_id=(obj is not None))
302+
print url
295303

296304
# Has this reference been loaded already?
297-
if url in self.idx:
305+
if url in self.idx and (not mixin):
298306
return self.idx[url], {}
299307

300308
# "$include" directive means load raw text
@@ -309,14 +317,25 @@ def resolve_ref(self, ref, base_url=None, checklinks=True):
309317
else:
310318
# Load structured document
311319
doc_url, frg = urlparse.urldefrag(url)
312-
if doc_url in self.idx:
320+
if doc_url in self.idx and (not mixin):
321+
# If the base document is in the index, it was already loaded,
322+
# so if we didn't find the reference earlier then it must not
323+
# exist.
313324
raise validate.ValidationException(
314-
"Reference `#%s` not found in file `%s`." % (frg, doc_url))
315-
doc = self.fetch(doc_url)
325+
u"Reference `#%s` not found in file `%s`." % (frg, doc_url))
326+
doc = self.fetch(doc_url, inject_ids=(not mixin))
316327

317328
# Recursively expand urls and resolve directives
318-
resolved_obj, metadata = self.resolve_all(
319-
doc if doc else obj, doc_url, checklinks=checklinks)
329+
if mixin:
330+
doc = copy.deepcopy(doc)
331+
doc.update(mixin)
332+
del doc["$mixin"]
333+
url = None
334+
resolved_obj, metadata = self.resolve_all(
335+
doc, base_url, file_base=doc_url, checklinks=checklinks)
336+
else:
337+
resolved_obj, metadata = self.resolve_all(
338+
doc if doc else obj, doc_url, checklinks=checklinks)
320339

321340
# Requested reference should be in the index now, otherwise it's a bad
322341
# reference
@@ -477,6 +496,8 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
477496
# Handle $import and $include
478497
if (u'$import' in document or u'$include' in document):
479498
return self.resolve_ref(document, base_url=file_base, checklinks=checklinks)
499+
elif u'$mixin' in document:
500+
return self.resolve_ref(document, base_url=base_url, checklinks=checklinks)
480501
elif isinstance(document, list):
481502
pass
482503
else:
@@ -534,7 +555,7 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
534555
document[key], _ = loader.resolve_all(
535556
val, base_url, file_base=file_base, checklinks=False)
536557
except validate.ValidationException as v:
537-
_logger.debug("loader is %s", id(loader))
558+
_logger.warn("loader is %s", id(loader), exc_info=v)
538559
raise validate.ValidationException("(%s) (%s) Validation error in field %s:\n%s" % (
539560
id(loader), file_base, key, validate.indent(str(v))))
540561

@@ -543,7 +564,7 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
543564
try:
544565
while i < len(document):
545566
val = document[i]
546-
if isinstance(val, dict) and u"$import" in val:
567+
if isinstance(val, dict) and (u"$import" in val or u"$mixin" in val):
547568
l, _ = loader.resolve_ref(val, base_url=file_base, checklinks=False)
548569
if isinstance(l, list): # never true?
549570
del document[i]
@@ -558,6 +579,7 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
558579
val, base_url, file_base=file_base, checklinks=False)
559580
i += 1
560581
except validate.ValidationException as v:
582+
_logger.warn("failed", exc_info=v)
561583
raise validate.ValidationException("(%s) (%s) Validation error in position %i:\n%s" % (
562584
id(loader), file_base, i, validate.indent(str(v))))
563585

@@ -601,7 +623,7 @@ def fetch_text(self, url):
601623
else:
602624
raise ValueError('Unsupported scheme in url: %s' % url)
603625

604-
def fetch(self, url): # type: (unicode) -> Any
626+
def fetch(self, url, inject_ids=True): # type: (unicode) -> Any
605627
if url in self.idx:
606628
return self.idx[url]
607629
try:
@@ -614,7 +636,7 @@ def fetch(self, url): # type: (unicode) -> Any
614636
result = yaml.load(textIO, Loader=SafeLoader)
615637
except yaml.parser.ParserError as e: # type: ignore
616638
raise validate.ValidationException("Syntax error %s" % (e))
617-
if isinstance(result, dict) and self.identifiers:
639+
if isinstance(result, dict) and inject_ids and self.identifiers:
618640
for identifier in self.identifiers:
619641
if identifier not in result:
620642
result[identifier] = url

tests/test_examples.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import rdflib
77
import ruamel.yaml as yaml
88
import json
9+
import os
910

1011
try:
1112
from ruamel.yaml import CSafeLoader as SafeLoader
@@ -322,5 +323,33 @@ def test_scoped_id(self):
322323
print(g.serialize(format="n3"))
323324

324325

326+
def test_mixin(self):
327+
ldr = schema_salad.ref_resolver.Loader({})
328+
ra = ldr.resolve_ref({"$mixin": "mixin.yml", "one": "five"},
329+
base_url="file://"+os.getcwd()+"/tests/")
330+
self.assertEqual({'id': 'four', 'one': 'five'}, ra[0])
331+
332+
ldr = schema_salad.ref_resolver.Loader({"id": "@id"})
333+
base_url="file://"+os.getcwd()+"/tests/"
334+
ra = ldr.resolve_all([{
335+
"id": "a",
336+
"m": {"$mixin": "mixin.yml"}
337+
}, {
338+
"id": "b",
339+
"m": {"$mixin": "mixin.yml"}
340+
}], base_url=base_url)
341+
self.assertEqual([{
342+
'id': base_url+'#a',
343+
'm': {
344+
'id': base_url+u'#a/four',
345+
'one': 'two'
346+
},
347+
}, {
348+
'id': base_url+'#b',
349+
'm': {
350+
'id': base_url+u'#b/four',
351+
'one': 'two'}
352+
}], ra[0])
353+
325354
if __name__ == '__main__':
326355
unittest.main()

0 commit comments

Comments
 (0)