@@ -81,14 +81,6 @@ def parse_args(self, args: List[str] = None):
81
81
namespace = parser .parse_args (args )
82
82
83
83
# Extract args
84
- models : List [Tuple [str , Iterable [Path ]]] = [
85
- (model_name , itertools .chain (* map (_process_path , paths )))
86
- for model_name , * paths in namespace .model or ()
87
- ]
88
- models_lists : List [Tuple [str , Tuple [str , Path ]]] = [
89
- (model_name , (lookup , Path (path )))
90
- for model_name , lookup , path in namespace .list or ()
91
- ]
92
84
parser = getattr (FileLoaders , namespace .input_format )
93
85
self .output_file = namespace .output
94
86
self .enable_datetime = namespace .datetime
@@ -104,8 +96,8 @@ def parse_args(self, args: List[str] = None):
104
96
dict_keys_fields : List [str ] = namespace .dict_keys_fields
105
97
preamble : str = namespace .preamble
106
98
107
- self .validate ( models_lists , merge_policy , framework , code_generator )
108
- self .setup_models_data ( models , models_lists , parser )
99
+ self .setup_models_data ( namespace . model or (), namespace . list or (), parser )
100
+ self .validate ( merge_policy , framework , code_generator )
109
101
self .set_args (merge_policy , structure , framework , code_generator , code_generator_kwargs_raw ,
110
102
dict_keys_regex , dict_keys_fields , disable_unicode_conversion , preamble )
111
103
@@ -144,20 +136,15 @@ def version_string(self):
144
136
'"""\n '
145
137
)
146
138
147
- def validate (self , models_list , merge_policy , framework , code_generator ):
139
+ def validate (self , merge_policy , framework , code_generator ):
148
140
"""
149
141
Validate parsed args
150
142
151
- :param models_list: List of pairs (model name, list of lookup expr and filesystem path)
152
143
:param merge_policy: List of merge policies. Each merge policy is either string or string and policy arguments
153
144
:param framework: Framework name (predefined code generator)
154
145
:param code_generator: Code generator import string
155
146
:return:
156
147
"""
157
- names = {name for name , _ in models_list }
158
- if len (names ) != len (models_list ):
159
- raise ValueError ("Model names under -l flag should be unique" )
160
-
161
148
for m in merge_policy :
162
149
if isinstance (m , list ):
163
150
if m [0 ] not in self .MODEL_CMP_MAPPING :
@@ -172,23 +159,33 @@ def validate(self, models_list, merge_policy, framework, code_generator):
172
159
173
160
def setup_models_data (
174
161
self ,
175
- models : Iterable [Tuple [str , Iterable [Path ]]],
176
- models_lists : Iterable [Tuple [str , Tuple [str , Path ]]],
162
+ models : Iterable [Union [
163
+ Tuple [str , str ],
164
+ Tuple [str , str , str ],
165
+ ]],
166
+ models_lists : Iterable [Tuple [str , str , str ]],
177
167
parser : 'FileLoaders.T'
178
168
):
179
169
"""
180
170
Initialize lazy loaders for models data
181
171
"""
182
- models_dict : Dict [str , List [Iterable [dict ]]] = defaultdict (list )
183
- for model_name , paths in models :
184
- models_dict [model_name ].append (parser (path ) for path in paths )
185
- for model_name , (lookup , path ) in models_lists :
186
- models_dict [model_name ].append (iter_json_file (parser (path ), lookup ))
172
+ models_dict : Dict [str , List [dict ]] = defaultdict (list )
173
+
174
+ models = list (models ) + list (models_lists )
175
+ for model_tuple in models :
176
+ if len (model_tuple ) == 2 :
177
+ model_name , path_raw = model_tuple
178
+ lookup = '-'
179
+ elif len (model_tuple ) == 3 :
180
+ model_name , lookup , path_raw = model_tuple
181
+ else :
182
+ raise RuntimeError ('`--model` argument should contain exactly 2 or 3 strings' )
187
183
188
- self .models_data = {
189
- model_name : itertools .chain (* list_of_gen )
190
- for model_name , list_of_gen in models_dict .items ()
191
- }
184
+ for real_path in process_path (path_raw ):
185
+ iterator = iter_json_file (parser (real_path ), lookup )
186
+ models_dict [model_name ].extend (iterator )
187
+
188
+ self .models_data = models_dict
192
189
193
190
def set_args (
194
191
self ,
@@ -257,20 +254,13 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
257
254
258
255
parser .add_argument (
259
256
"-m" , "--model" ,
260
- nargs = "+" , action = "append" , metavar = ("<Model name>" , " <JSON files> " ),
257
+ nargs = "+" , action = "append" , metavar = ("<Model name> [ <JSON lookup>] <File path or pattern>" , " " ),
261
258
help = "Model name and its JSON data as path or unix-like path pattern.\n "
262
259
"'*', '**' or '?' patterns symbols are supported.\n \n "
263
- )
264
- parser .add_argument (
265
- "-l" , "--list" ,
266
- nargs = 3 , action = "append" , metavar = ("<Model name>" , "<JSON key>" , "<JSON file>" ),
267
- help = "Like -m but given json file should contain list of model data.\n "
260
+ "JSON data could be array of models or single model\n \n "
268
261
"If this file contains dict with nested list than you can pass\n "
269
- "<JSON key> to lookup. Deep lookups are supported by dot-separated path.\n "
270
- "If no lookup needed pass '-' as <JSON key>\n \n "
271
-
272
- "I.e. for file that contains dict {\" a\" : {\" b\" : [model_data, ...]}} you should\n "
273
- "pass 'a.b' as <JSON key>.\n \n "
262
+ "<JSON lookup>. Deep lookups are supported by dot-separated path.\n "
263
+ "If no lookup needed pass '-' as <JSON lookup> (default)\n \n "
274
264
)
275
265
parser .add_argument (
276
266
"-i" , "--input-format" ,
@@ -377,6 +367,11 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
377
367
type = str ,
378
368
help = "Code to insert into the generated file after the imports and before the list of classes\n \n "
379
369
)
370
+ parser .add_argument (
371
+ "-l" , "--list" ,
372
+ nargs = 3 , action = "append" , metavar = ("<Model name>" , "<JSON lookup>" , "<JSON file>" ),
373
+ help = "DEPRECATED, use --model argument instead"
374
+ )
380
375
381
376
return parser
382
377
@@ -395,27 +390,6 @@ def main():
395
390
print (cli .run ())
396
391
397
392
398
- def path_split (path : str ) -> List [str ]:
399
- """
400
- Split path into list of components
401
-
402
- :param path: string path
403
- :return: List of files/patterns
404
- """
405
- folders = []
406
- while True :
407
- path , folder = os .path .split (path )
408
-
409
- if folder :
410
- folders .append (folder )
411
- else :
412
- if path :
413
- folders .append (path )
414
- break
415
- folders .reverse ()
416
- return folders
417
-
418
-
419
393
class FileLoaders :
420
394
T = Callable [[Path ], Union [dict , list ]]
421
395
@@ -442,7 +416,7 @@ def ini(path: Path) -> dict:
442
416
443
417
def dict_lookup (d : Union [dict , list ], lookup : str ) -> Union [dict , list ]:
444
418
"""
445
- Extract nested dictionary value from key path.
419
+ Extract nested value from key path.
446
420
If lookup is "-" returns dict as is.
447
421
448
422
:param d: Nested dict
@@ -460,25 +434,26 @@ def dict_lookup(d: Union[dict, list], lookup: str) -> Union[dict, list]:
460
434
461
435
def iter_json_file (data : Union [dict , list ], lookup : str ) -> Generator [Union [dict , list ], Any , None ]:
462
436
"""
463
- Loads given 'path' file, perform lookup and return generator over json list.
437
+ Perform lookup and return generator over json list.
464
438
Does not open file until iteration is started.
465
439
466
- :param path: File Path instance
440
+ :param data: JSON data
467
441
:param lookup: Dot separated lookup path
468
- :return:
442
+ :return: Generator of the model data
469
443
"""
470
- l = dict_lookup (data , lookup )
471
- assert isinstance (l , list ), f"Dict lookup return { type (l )} but list is expected, check your lookup path"
472
- yield from l
444
+ item = dict_lookup (data , lookup )
445
+ if isinstance (item , list ):
446
+ yield from item
447
+ elif isinstance (item , dict ):
448
+ yield item
449
+ else :
450
+ raise TypeError (f'dict or list is expected at { lookup if lookup != "-" else "JSON root" } , not { type (item )} ' )
473
451
474
452
475
- def _process_path (path : str ) -> Iterable [Path ]:
453
+ def process_path (path : str ) -> Iterable [Path ]:
476
454
"""
477
455
Convert path pattern into path iterable.
478
456
If non-pattern path is given return tuple of one element: (path,)
479
-
480
- :param path:
481
- :return:
482
457
"""
483
458
split_path = path_split (path )
484
459
clean_path = list (itertools .takewhile (
@@ -502,3 +477,24 @@ def _process_path(path: str) -> Iterable[Path]:
502
477
return path .glob (pattern_path )
503
478
else :
504
479
return path ,
480
+
481
+
482
+ def path_split (path : str ) -> List [str ]:
483
+ """
484
+ Split path into list of components
485
+
486
+ :param path: string path
487
+ :return: List of files/patterns
488
+ """
489
+ folders = []
490
+ while True :
491
+ path , folder = os .path .split (path )
492
+
493
+ if folder :
494
+ folders .append (folder )
495
+ else :
496
+ if path :
497
+ folders .append (path )
498
+ break
499
+ folders .reverse ()
500
+ return folders
0 commit comments