1
1
import argparse
2
+ import configparser
2
3
import importlib
3
4
import itertools
4
5
import json
10
11
from pathlib import Path
11
12
from typing import Any , Callable , Dict , Generator , Iterable , List , Tuple , Type , Union
12
13
14
+ try :
15
+ import yaml
16
+ except ImportError :
17
+ try :
18
+ import ruamel .yaml as yaml
19
+ except ImportError :
20
+ yaml = None
21
+
13
22
from . import __version__ as VERSION
14
23
from .dynamic_typing import ModelMeta , register_datetime_classes
15
24
from .generator import MetadataGenerator
@@ -80,6 +89,7 @@ def parse_args(self, args: List[str] = None):
80
89
(model_name , (lookup , Path (path )))
81
90
for model_name , lookup , path in namespace .list or ()
82
91
]
92
+ parser = getattr (FileLoaders , namespace .input_format )
83
93
self .output_file = namespace .output
84
94
self .enable_datetime = namespace .datetime
85
95
disable_unicode_conversion = namespace .disable_unicode_conversion
@@ -94,7 +104,7 @@ def parse_args(self, args: List[str] = None):
94
104
dict_keys_fields : List [str ] = namespace .dict_keys_fields
95
105
96
106
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 )
98
108
self .set_args (merge_policy , structure , framework , code_generator , code_generator_kwargs_raw ,
99
109
dict_keys_regex , dict_keys_fields , disable_unicode_conversion )
100
110
@@ -157,16 +167,20 @@ def validate(self, models, models_list, merge_policy, framework, code_generator)
157
167
elif framework != 'custom' and code_generator is not None :
158
168
raise ValueError ("--code-generator argument has no effect without '--framework custom' argument" )
159
169
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
+ ):
162
176
"""
163
177
Initialize lazy loaders for models data
164
178
"""
165
179
models_dict : Dict [str , List [Iterable [dict ]]] = defaultdict (list )
166
180
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 )
168
182
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 ))
170
184
171
185
self .models_data = {
172
186
model_name : itertools .chain (* list_of_gen )
@@ -252,6 +266,12 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
252
266
"I.e. for file that contains dict {\" a\" : {\" b\" : [model_data, ...]}} you should\n "
253
267
"pass 'a.b' as <JSON key>.\n \n "
254
268
)
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
+ )
255
275
parser .add_argument (
256
276
"-o" , "--output" ,
257
277
metavar = "FILE" , default = "" ,
@@ -385,7 +405,31 @@ def path_split(path: str) -> List[str]:
385
405
return folders
386
406
387
407
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 ]:
389
433
"""
390
434
Extract nested dictionary value from key path.
391
435
If lookup is "-" returns dict as is.
@@ -403,7 +447,7 @@ def dict_lookup(d: dict, lookup: str) -> Union[dict, list]:
403
447
return d
404
448
405
449
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 ]:
407
451
"""
408
452
Loads given 'path' file, perform lookup and return generator over json list.
409
453
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,
412
456
:param lookup: Dot separated lookup path
413
457
:return:
414
458
"""
415
- with path .open () as f :
416
- l = json .load (f )
417
- l = dict_lookup (l , lookup )
459
+ l = dict_lookup (data , lookup )
418
460
assert isinstance (l , list ), f"Dict lookup return { type (l )} but list is expected, check your lookup path"
419
461
yield from l
420
462
421
463
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
-
430
464
def _process_path (path : str ) -> Iterable [Path ]:
431
465
"""
432
466
Convert path pattern into path iterable.
0 commit comments