11import argparse
2+ import configparser
23import importlib
34import itertools
45import json
1011from pathlib import Path
1112from typing import Any , Callable , Dict , Generator , Iterable , List , Tuple , Type , Union
1213
14+ try :
15+ import yaml
16+ except ImportError :
17+ try :
18+ import ruamel .yaml as yaml
19+ except ImportError :
20+ yaml = None
21+
1322from . import __version__ as VERSION
1423from .dynamic_typing import ModelMeta , register_datetime_classes
1524from .generator import MetadataGenerator
@@ -80,6 +89,7 @@ def parse_args(self, args: List[str] = None):
8089 (model_name , (lookup , Path (path )))
8190 for model_name , lookup , path in namespace .list or ()
8291 ]
92+ parser = getattr (FileLoaders , namespace .input_format )
8393 self .output_file = namespace .output
8494 self .enable_datetime = namespace .datetime
8595 disable_unicode_conversion = namespace .disable_unicode_conversion
@@ -94,7 +104,7 @@ def parse_args(self, args: List[str] = None):
94104 dict_keys_fields : List [str ] = namespace .dict_keys_fields
95105
96106 self .validate (models , models_lists , merge_policy , framework , code_generator )
97- self .setup_models_data (models , models_lists )
107+ self .setup_models_data (models , models_lists , parser )
98108 self .set_args (merge_policy , structure , framework , code_generator , code_generator_kwargs_raw ,
99109 dict_keys_regex , dict_keys_fields , disable_unicode_conversion )
100110
@@ -157,16 +167,20 @@ def validate(self, models, models_list, merge_policy, framework, code_generator)
157167 elif framework != 'custom' and code_generator is not None :
158168 raise ValueError ("--code-generator argument has no effect without '--framework custom' argument" )
159169
160- def setup_models_data (self , models : Iterable [Tuple [str , Iterable [Path ]]],
161- models_lists : Iterable [Tuple [str , Tuple [str , Path ]]]):
170+ def setup_models_data (
171+ self ,
172+ models : Iterable [Tuple [str , Iterable [Path ]]],
173+ models_lists : Iterable [Tuple [str , Tuple [str , Path ]]],
174+ parser : 'FileLoaders.T'
175+ ):
162176 """
163177 Initialize lazy loaders for models data
164178 """
165179 models_dict : Dict [str , List [Iterable [dict ]]] = defaultdict (list )
166180 for model_name , paths in models :
167- models_dict [model_name ].append (map ( safe_json_load , paths ) )
181+ models_dict [model_name ].append (parser ( path ) for path in paths )
168182 for model_name , (lookup , path ) in models_lists :
169- models_dict [model_name ].append (iter_json_file (path , lookup ))
183+ models_dict [model_name ].append (iter_json_file (parser ( path ) , lookup ))
170184
171185 self .models_data = {
172186 model_name : itertools .chain (* list_of_gen )
@@ -252,6 +266,12 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
252266 "I.e. for file that contains dict {\" a\" : {\" b\" : [model_data, ...]}} you should\n "
253267 "pass 'a.b' as <JSON key>.\n \n "
254268 )
269+ parser .add_argument (
270+ "-i" , "--input-format" ,
271+ metavar = "FORMAT" , default = "json" ,
272+ choices = ['json' , 'yaml' , 'ini' ],
273+ help = "Input files parser ('PyYaml' is required to parse yaml files)\n \n "
274+ )
255275 parser .add_argument (
256276 "-o" , "--output" ,
257277 metavar = "FILE" , default = "" ,
@@ -385,7 +405,31 @@ def path_split(path: str) -> List[str]:
385405 return folders
386406
387407
388- def dict_lookup (d : dict , lookup : str ) -> Union [dict , list ]:
408+ class FileLoaders :
409+ T = Callable [[Path ], Union [dict , list ]]
410+
411+ @staticmethod
412+ def json (path : Path ) -> Union [dict , list ]:
413+ with path .open () as fp :
414+ return json .load (fp )
415+
416+ @staticmethod
417+ def yaml (path : Path ) -> Union [dict , list ]:
418+ if yaml is None :
419+ print ('Yaml parser is not installed. To parse yaml files PyYaml (or ruamel.yaml) is required.' )
420+ raise ImportError ('yaml' )
421+ with path .open () as fp :
422+ return yaml .safe_load (fp )
423+
424+ @staticmethod
425+ def ini (path : Path ) -> dict :
426+ config = configparser .ConfigParser ()
427+ with path .open () as fp :
428+ config .read_file (fp )
429+ return {s : dict (config .items (s )) for s in config .sections ()}
430+
431+
432+ def dict_lookup (d : Union [dict , list ], lookup : str ) -> Union [dict , list ]:
389433 """
390434 Extract nested dictionary value from key path.
391435 If lookup is "-" returns dict as is.
@@ -403,7 +447,7 @@ def dict_lookup(d: dict, lookup: str) -> Union[dict, list]:
403447 return d
404448
405449
406- def iter_json_file (path : Path , lookup : str ) -> Generator [Union [dict , list ], Any , None ]:
450+ def iter_json_file (data : Union [ dict , list ] , lookup : str ) -> Generator [Union [dict , list ], Any , None ]:
407451 """
408452 Loads given 'path' file, perform lookup and return generator over json list.
409453 Does not open file until iteration is started.
@@ -412,21 +456,11 @@ def iter_json_file(path: Path, lookup: str) -> Generator[Union[dict, list], Any,
412456 :param lookup: Dot separated lookup path
413457 :return:
414458 """
415- with path .open () as f :
416- l = json .load (f )
417- l = dict_lookup (l , lookup )
459+ l = dict_lookup (data , lookup )
418460 assert isinstance (l , list ), f"Dict lookup return { type (l )} but list is expected, check your lookup path"
419461 yield from l
420462
421463
422- def safe_json_load (path : Path ) -> Union [dict , list ]:
423- """
424- Open file, load json and close it.
425- """
426- with path .open (encoding = "utf-8" ) as f :
427- return json .load (f )
428-
429-
430464def _process_path (path : str ) -> Iterable [Path ]:
431465 """
432466 Convert path pattern into path iterable.
0 commit comments