44import json
55import os .path
66import re
7+ import sys
78from collections import defaultdict
89from datetime import datetime
910from pathlib import Path
1011from typing import Any , Callable , Dict , Generator , Iterable , List , Tuple , Type , Union
1112
12- import json_to_models
13- from json_to_models .dynamic_typing import ModelMeta , register_datetime_classes
14- from json_to_models .generator import MetadataGenerator
15- from json_to_models .models import ModelsStructureType
16- from json_to_models .models .attr import AttrsModelCodeGenerator
17- from json_to_models .models .base import GenericModelCodeGenerator , generate_code
18- from json_to_models .models .dataclasses import DataclassModelCodeGenerator
19- from json_to_models .models .structure import compose_models , compose_models_flat
20- from json_to_models .registry import (
13+ from . import __version__ as VERSION
14+ from .dynamic_typing import ModelMeta , register_datetime_classes
15+ from .generator import MetadataGenerator
16+ from .models import ModelsStructureType
17+ from .models .attr import AttrsModelCodeGenerator
18+ from .models .base import GenericModelCodeGenerator , generate_code
19+ from .models .dataclasses import DataclassModelCodeGenerator
20+ from .models .structure import compose_models , compose_models_flat
21+ from .registry import (
2122 ModelCmp , ModelFieldsEquals , ModelFieldsNumberMatch , ModelFieldsPercentMatch , ModelRegistry
2223)
23- from json_to_models .utils import convert_args
24+ from .utils import convert_args
2425
2526STRUCTURE_FN_TYPE = Callable [[Dict [str , ModelMeta ]], ModelsStructureType ]
2627bool_js_style = lambda s : {"true" : True , "false" : False }.get (s , None )
@@ -75,7 +76,9 @@ def parse_args(self, args: List[str] = None):
7576 (model_name , (lookup , Path (path )))
7677 for model_name , lookup , path in namespace .list or ()
7778 ]
79+ self .output_file = namespace .output
7880 self .enable_datetime = namespace .datetime
81+ disable_unicode_conversion = namespace .disable_unicode_conversion
7982 self .strings_converters = namespace .strings_converters
8083 merge_policy = [m .split ("_" ) if "_" in m else m for m in namespace .merge ]
8184 structure = namespace .structure
@@ -88,7 +91,7 @@ def parse_args(self, args: List[str] = None):
8891 self .validate (models , models_lists , merge_policy , framework , code_generator )
8992 self .setup_models_data (models , models_lists )
9093 self .set_args (merge_policy , structure , framework , code_generator , code_generator_kwargs_raw ,
91- dict_keys_regex , dict_keys_fields )
94+ dict_keys_regex , dict_keys_fields , disable_unicode_conversion )
9295
9396 def run (self ):
9497 if self .enable_datetime :
@@ -104,7 +107,23 @@ def run(self):
104107 registry .merge_models (generator )
105108 registry .generate_names ()
106109 structure = self .structure_fn (registry .models_map )
107- return generate_code (structure , self .model_generator , class_generator_kwargs = self .model_generator_kwargs )
110+ output = self .version_string + \
111+ generate_code (structure , self .model_generator , class_generator_kwargs = self .model_generator_kwargs )
112+ if self .output_file :
113+ with open (self .output_file , "w" , encoding = "utf-8" ) as f :
114+ f .write (output )
115+ return f"Output is written to { self .output_file } "
116+ else :
117+ return output
118+
119+ @property
120+ def version_string (self ):
121+ return (
122+ 'r"""\n '
123+ f'generated by json2python-models v{ VERSION } at { datetime .now ().ctime ()} \n '
124+ f'command: { " " .join (sys .argv )} \n '
125+ '"""\n '
126+ )
108127
109128 def validate (self , models , models_list , merge_policy , framework , code_generator ):
110129 """
@@ -149,9 +168,17 @@ def setup_models_data(self, models: Iterable[Tuple[str, Iterable[Path]]],
149168 for model_name , list_of_gen in models_dict .items ()
150169 }
151170
152- def set_args (self , merge_policy : List [Union [List [str ], str ]],
153- structure : str , framework : str , code_generator : str , code_generator_kwargs_raw : List [str ],
154- dict_keys_regex : List [str ], dict_keys_fields : List [str ]):
171+ def set_args (
172+ self ,
173+ merge_policy : List [Union [List [str ], str ]],
174+ structure : str ,
175+ framework : str ,
176+ code_generator : str ,
177+ code_generator_kwargs_raw : List [str ],
178+ dict_keys_regex : List [str ],
179+ dict_keys_fields : List [str ],
180+ disable_unicode_conversion : bool
181+ ):
155182 """
156183 Convert CLI args to python representation and set them to appropriate object attributes
157184 """
@@ -175,6 +202,7 @@ def set_args(self, merge_policy: List[Union[List[str], str]],
175202 self .model_generator = getattr (m , cls )
176203
177204 self .model_generator_kwargs = {} if not self .strings_converters else {'post_init_converters' : True }
205+ self .model_generator_kwargs ['convert_unicode' ] = not disable_unicode_conversion
178206 if code_generator_kwargs_raw :
179207 for item in code_generator_kwargs_raw :
180208 if item [0 ] == '"' :
@@ -216,6 +244,11 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
216244 "I.e. for file that contains dict {\" a\" : {\" b\" : [model_data, ...]}} you should\n "
217245 "pass 'a.b' as <JSON key>.\n \n "
218246 )
247+ parser .add_argument (
248+ "-o" , "--output" ,
249+ metavar = "FILE" , default = "" ,
250+ help = "Path to output file\n \n "
251+ )
219252 parser .add_argument (
220253 "-f" , "--framework" ,
221254 default = "base" ,
@@ -243,22 +276,29 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
243276 action = "store_true" ,
244277 help = "Enable generation of string types converters (i.e. IsoDatetimeString or BooleanString).\n \n "
245278 )
279+ parser .add_argument (
280+ "--disable-unicode-conversion" , "--no-unidecode" ,
281+ action = "store_true" ,
282+ help = "Disabling unicode conversion in fields and class names.\n \n "
283+ )
246284
247285 default_percent = f"{ ModelFieldsPercentMatch .DEFAULT * 100 :.0f} "
248286 default_number = f"{ ModelFieldsNumberMatch .DEFAULT :.0f} "
249287 parser .add_argument (
250288 "--merge" ,
251289 default = ["percent" , "number" ],
252290 nargs = "+" ,
253- help = f"Merge policy settings. Default is 'percent_{ default_percent } number_{ default_number } ' (percent of field match\n "
254- "or number of fields match).\n "
255- "Possible values are:\n "
256- "'percent[_<percent>]' - two models had a certain percentage of matched field names.\n "
257- f" Default percent is { default_percent } %%. "
258- "Custom value could be i.e. 'percent_95'.\n "
259- "'number[_<number>]' - two models had a certain number of matched field names.\n "
260- f" Default number of fields is { default_number } .\n "
261- "'exact' - two models should have exact same field names to merge.\n \n "
291+ help = (
292+ f"Merge policy settings. Default is 'percent_{ default_percent } number_{ default_number } ' (percent of field match\n "
293+ "or number of fields match).\n "
294+ "Possible values are:\n "
295+ "'percent[_<percent>]' - two models had a certain percentage of matched field names.\n "
296+ f" Default percent is { default_percent } %%. "
297+ "Custom value could be i.e. 'percent_95'.\n "
298+ "'number[_<number>]' - two models had a certain number of matched field names.\n "
299+ f" Default number of fields is { default_number } .\n "
300+ "'exact' - two models should have exact same field names to merge.\n \n "
301+ )
262302 )
263303 parser .add_argument (
264304 "--dict-keys-regex" , "--dkr" ,
@@ -293,8 +333,7 @@ def _create_argparser(cls) -> argparse.ArgumentParser:
293333 return parser
294334
295335
296- def main (version_string = None ):
297- import sys
336+ def main ():
298337 import os
299338
300339 if os .getenv ("TRAVIS" , None ) or os .getenv ("FORCE_COVERAGE" , None ):
@@ -305,14 +344,7 @@ def main(version_string=None):
305344
306345 cli = Cli ()
307346 cli .parse_args ()
308- if not version_string :
309- version_string = (
310- 'r"""\n '
311- f'generated by json2python-models v{ json_to_models .__version__ } at { datetime .now ().ctime ()} \n '
312- f'command: { " " .join (sys .argv )} \n '
313- '"""\n '
314- )
315- print (version_string + cli .run ())
347+ print (cli .run ())
316348
317349
318350def path_split (path : str ) -> List [str ]:
@@ -374,7 +406,7 @@ def safe_json_load(path: Path) -> Union[dict, list]:
374406 """
375407 Open file, load json and close it.
376408 """
377- with path .open () as f :
409+ with path .open (encoding = "utf-8" ) as f :
378410 return json .load (f )
379411
380412
0 commit comments