11from collections .abc import Hashable
2+ from copy import deepcopy
23from functools import reduce
34from pathlib import Path
45from typing import Any
@@ -25,25 +26,27 @@ def _yaml_to_dict(path: Path) -> dict:
2526 s = yaml .safe_load (f )
2627 return s
2728
28- def add_file_to_store (self , ref : str ) -> None :
29- try :
30- file_path , node_path = ref .split ('#/' )
31- except (AttributeError , ValueError ):
32- raise FirstYAMLReaderError (f'"$ref" with value <{ ref } > is not valid.' )
29+ def add_file_to_store (self , file_path : str ) -> None :
30+ path_to_spec_file = Path (self .path .parent , file_path )
3331
34- if file_path and file_path not in self .store :
35- path_to_spec_file = Path (self .path .parent , file_path )
32+ try :
33+ self .store [file_path ] = self ._yaml_to_dict (path_to_spec_file )
34+ except FileNotFoundError :
35+ raise FirstYAMLReaderError (f'No such file or directory: <{ file_path } >' )
3636
37- try :
38- self .store [file_path ] = self ._yaml_to_dict (path_to_spec_file )
39- except FileNotFoundError :
40- raise FirstYAMLReaderError (f'No such file or directory: <{ file_path } >' )
37+ return self .store [file_path ]
4138
4239 def search_file (self , obj : dict or list ) -> None :
4340 if isinstance (obj , dict ):
4441 ref = obj .get ('$ref' )
4542 if ref :
46- self .add_file_to_store (ref )
43+ try :
44+ file_path , _ = ref .split ('#/' )
45+ except (AttributeError , ValueError ):
46+ raise FirstYAMLReaderError (f'"$ref" with value <{ ref } > is not valid.' )
47+
48+ if file_path and file_path not in self .store :
49+ self .search_file (self .add_file_to_store (file_path ))
4750 else :
4851 for _ , v in obj .items ():
4952 self .search_file (v )
@@ -62,7 +65,9 @@ def load(self) -> 'YAMLReader':
6265 return self
6366
6467
65- class Resolver :
68+ class RefResolver :
69+ """Resolve links to various parts of the specification."""
70+
6671 def __init__ (self , yaml_reader : YAMLReader ):
6772 self .yaml_reader = yaml_reader
6873 self .resolved_spec = None
@@ -74,23 +79,18 @@ def get_value_of_key_from_dict(source_dict: dict, key: Hashable) -> Any:
7479 return source_dict [key ]
7580
7681 try :
77- return reduce (get_value_of_key_from_dict , keys , self .yaml_reader .store [file_path ])
82+ return deepcopy (
83+ reduce (get_value_of_key_from_dict , keys , self .yaml_reader .store [file_path ])
84+ )
7885 except KeyError :
7986 raise FirstResolverError (f'No such path: "{ node_path } "' )
8087
81- def _get_schema (self , root_file_path : str , ref : str ) -> Any :
82- try :
83- file_path , node_path = ref .split ('#/' )
84- except (AttributeError , ValueError ):
85- raise FirstResolverError (
86- f'"$ref" with value <{ ref } > is not valid in file <{ root_file_path } >'
87- )
88-
88+ def _get_schema (self , root_file_name : str , file_path : str or None , node_path : str ) -> Any :
8989 if file_path and node_path :
9090 obj = self ._get_schema_via_local_ref (file_path , node_path )
9191
9292 elif node_path and not file_path :
93- obj = self ._get_schema_via_local_ref (root_file_path , node_path )
93+ obj = self ._get_schema_via_local_ref (root_file_name , node_path )
9494
9595 else :
9696 raise NotImplementedError
@@ -101,7 +101,23 @@ def _resolving_all_refs(self, file_path: str, obj: Any) -> Any:
101101 if isinstance (obj , dict ):
102102 ref = obj .get ('$ref' , ...)
103103 if ref is not ...:
104- obj = self ._resolving_all_refs (file_path , self ._get_schema (file_path , ref ))
104+ try :
105+ file_path_from_ref , node_path = ref .split ('#/' )
106+ except (AttributeError , ValueError ):
107+ raise FirstResolverError (
108+ f'"$ref" with value <{ ref } > is not valid in file <{ file_path } >'
109+ )
110+
111+ if file_path_from_ref :
112+ obj = self ._resolving_all_refs (
113+ file_path_from_ref ,
114+ self ._get_schema (file_path , file_path_from_ref , node_path ),
115+ )
116+ else :
117+ obj = self ._resolving_all_refs (
118+ file_path , self ._get_schema (file_path , file_path_from_ref , node_path )
119+ )
120+
105121 else :
106122 for key , value in obj .items ():
107123 obj [key ] = self ._resolving_all_refs (file_path , value )
@@ -114,14 +130,14 @@ def _resolving_all_refs(self, file_path: str, obj: Any) -> Any:
114130
115131 return obj
116132
117- def resolving (self ) -> 'Resolver ' :
133+ def resolving (self ) -> 'RefResolver ' :
118134 root_file_path = self .yaml_reader .root_file_name
119135 root_spec = self .yaml_reader .store [root_file_path ]
120136 self .resolved_spec = self ._resolving_all_refs (root_file_path , root_spec )
121137 return self
122138
123139
124- def load_from_yaml (path : Path ) -> Resolver :
140+ def load_from_yaml (path : Path ) -> RefResolver :
125141 yaml_reader = YAMLReader (path ).load ()
126- resolved_obj = Resolver (yaml_reader ).resolving ()
142+ resolved_obj = RefResolver (yaml_reader ).resolving ()
127143 return resolved_obj .resolved_spec
0 commit comments