7
7
import requests
8
8
import urlparse
9
9
import re
10
+ import copy
10
11
import ruamel .yaml as yaml
11
12
try :
12
13
from ruamel .yaml import CSafeLoader as SafeLoader
@@ -259,25 +260,30 @@ def resolve_ref(self, ref, base_url=None, checklinks=True):
259
260
260
261
obj = None # type: Dict[unicode, Any]
261
262
inc = False
263
+ mixin = None
262
264
263
265
# If `ref` is a dict, look for special directives.
264
266
if isinstance (ref , dict ):
265
267
obj = ref
266
- if u"$import" in ref :
268
+ if u"$import" in obj :
267
269
if len (obj ) == 1 :
268
270
ref = obj [u"$import" ]
269
271
obj = None
270
272
else :
271
273
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 )))
273
275
elif u"$include" in obj :
274
276
if len (obj ) == 1 :
275
277
ref = obj [u"$include" ]
276
278
inc = True
277
279
obj = None
278
280
else :
279
281
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
281
287
else :
282
288
ref = None
283
289
for identifier in self .identifiers :
@@ -286,15 +292,15 @@ def resolve_ref(self, ref, base_url=None, checklinks=True):
286
292
break
287
293
if not ref :
288
294
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 ))
290
296
291
297
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 ))
293
299
294
300
url = self .expand_url (ref , base_url , scoped_id = (obj is not None ))
295
301
296
302
# Has this reference been loaded already?
297
- if url in self .idx :
303
+ if url in self .idx and ( not mixin ) :
298
304
return self .idx [url ], {}
299
305
300
306
# "$include" directive means load raw text
@@ -309,14 +315,25 @@ def resolve_ref(self, ref, base_url=None, checklinks=True):
309
315
else :
310
316
# Load structured document
311
317
doc_url , frg = urlparse .urldefrag (url )
312
- if doc_url in self .idx :
318
+ if doc_url in self .idx and (not mixin ):
319
+ # If the base document is in the index, it was already loaded,
320
+ # so if we didn't find the reference earlier then it must not
321
+ # exist.
313
322
raise validate .ValidationException (
314
- "Reference `#%s` not found in file `%s`." % (frg , doc_url ))
315
- doc = self .fetch (doc_url )
323
+ u "Reference `#%s` not found in file `%s`." % (frg , doc_url ))
324
+ doc = self .fetch (doc_url , inject_ids = ( not mixin ) )
316
325
317
326
# Recursively expand urls and resolve directives
318
- resolved_obj , metadata = self .resolve_all (
319
- doc if doc else obj , doc_url , checklinks = checklinks )
327
+ if mixin :
328
+ doc = copy .deepcopy (doc )
329
+ doc .update (mixin )
330
+ del doc ["$mixin" ]
331
+ url = None
332
+ resolved_obj , metadata = self .resolve_all (
333
+ doc , base_url , file_base = doc_url , checklinks = checklinks )
334
+ else :
335
+ resolved_obj , metadata = self .resolve_all (
336
+ doc if doc else obj , doc_url , checklinks = checklinks )
320
337
321
338
# Requested reference should be in the index now, otherwise it's a bad
322
339
# reference
@@ -477,6 +494,8 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
477
494
# Handle $import and $include
478
495
if (u'$import' in document or u'$include' in document ):
479
496
return self .resolve_ref (document , base_url = file_base , checklinks = checklinks )
497
+ elif u'$mixin' in document :
498
+ return self .resolve_ref (document , base_url = base_url , checklinks = checklinks )
480
499
elif isinstance (document , list ):
481
500
pass
482
501
else :
@@ -534,7 +553,7 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
534
553
document [key ], _ = loader .resolve_all (
535
554
val , base_url , file_base = file_base , checklinks = False )
536
555
except validate .ValidationException as v :
537
- _logger .debug ("loader is %s" , id (loader ))
556
+ _logger .warn ("loader is %s" , id (loader ), exc_info = v )
538
557
raise validate .ValidationException ("(%s) (%s) Validation error in field %s:\n %s" % (
539
558
id (loader ), file_base , key , validate .indent (str (v ))))
540
559
@@ -543,7 +562,7 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
543
562
try :
544
563
while i < len (document ):
545
564
val = document [i ]
546
- if isinstance (val , dict ) and u"$import" in val :
565
+ if isinstance (val , dict ) and ( u"$import" in val or u"$mixin" in val ) :
547
566
l , _ = loader .resolve_ref (val , base_url = file_base , checklinks = False )
548
567
if isinstance (l , list ): # never true?
549
568
del document [i ]
@@ -558,6 +577,7 @@ def resolve_all(self, document, base_url, file_base=None, checklinks=True):
558
577
val , base_url , file_base = file_base , checklinks = False )
559
578
i += 1
560
579
except validate .ValidationException as v :
580
+ _logger .warn ("failed" , exc_info = v )
561
581
raise validate .ValidationException ("(%s) (%s) Validation error in position %i:\n %s" % (
562
582
id (loader ), file_base , i , validate .indent (str (v ))))
563
583
@@ -601,7 +621,7 @@ def fetch_text(self, url):
601
621
else :
602
622
raise ValueError ('Unsupported scheme in url: %s' % url )
603
623
604
- def fetch (self , url ): # type: (unicode) -> Any
624
+ def fetch (self , url , inject_ids = True ): # type: (unicode, bool ) -> Any
605
625
if url in self .idx :
606
626
return self .idx [url ]
607
627
try :
@@ -614,7 +634,7 @@ def fetch(self, url): # type: (unicode) -> Any
614
634
result = yaml .load (textIO , Loader = SafeLoader )
615
635
except yaml .parser .ParserError as e : # type: ignore
616
636
raise validate .ValidationException ("Syntax error %s" % (e ))
617
- if isinstance (result , dict ) and self .identifiers :
637
+ if isinstance (result , dict ) and inject_ids and self .identifiers :
618
638
for identifier in self .identifiers :
619
639
if identifier not in result :
620
640
result [identifier ] = url
0 commit comments