3939import tempfile
4040import warnings
4141from collections import defaultdict
42- from collections .abc import Sequence # noqa: TC003 # pydantic needs it
42+ from collections .abc import Callable , Sequence # noqa: TC003 # pydantic needs it
4343from enum import IntEnum
4444from io import TextIOBase
4545from pathlib import Path
@@ -176,7 +176,7 @@ def get_fields(cls) -> dict[str, Any]:
176176 """Get model fields."""
177177 return cls .__fields__
178178
179- @field_validator ("aliases" , "extra_template_data" , "custom_formatters_kwargs" , mode = "before" )
179+ @field_validator ("aliases" , "extra_template_data" , "custom_formatters_kwargs" , "default_values" , mode = "before" )
180180 def validate_file (cls , value : Any ) -> TextIOBase | None : # noqa: N805
181181 """Validate and open file path."""
182182 if value is None : # pragma: no cover
@@ -503,6 +503,7 @@ def validate_class_name_affix_scope(cls, v: str | ClassNameAffixScope | None) ->
503503 snake_case_field : bool = False
504504 strip_default_none : bool = False
505505 aliases : Optional [TextIOBase ] = None # noqa: UP045
506+ default_values : Optional [TextIOBase ] = None # noqa: UP045
506507 disable_timestamp : bool = False
507508 enable_version_header : bool = False
508509 enable_command_header : bool = False
@@ -854,6 +855,38 @@ def generate_cli_command(config: dict[str, TomlValue]) -> str:
854855 return " " .join (parts ) + "\n "
855856
856857
858+ def _load_json_config (
859+ file_handle : TextIOBase | None ,
860+ name : str ,
861+ validator : Callable [[Any ], str | None ],
862+ ) -> tuple [dict [str , Any ] | None , str | None ]:
863+ """Load and validate a JSON configuration file.
864+
865+ Args:
866+ file_handle: The file handle to read from, or None.
867+ name: The name of the config for error messages.
868+ validator: A function that validates the loaded data and returns an error message or None.
869+
870+ Returns:
871+ A tuple of (loaded_dict, error_message). If successful, error_message is None.
872+ If file_handle is None, returns (None, None).
873+ """
874+ if file_handle is None :
875+ return None , None
876+
877+ with file_handle as data :
878+ try :
879+ result = json .load (data )
880+ except json .JSONDecodeError as e :
881+ return None , f"Unable to load { name } : { e } "
882+
883+ error = validator (result )
884+ if error :
885+ return None , f"Unable to load { name } : { error } "
886+
887+ return result , None
888+
889+
857890def run_generate_from_config ( # noqa: PLR0913, PLR0917
858891 config : Config ,
859892 input_ : Path | str | ParseResult ,
@@ -863,6 +896,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917
863896 command_line : str | None ,
864897 custom_formatters_kwargs : dict [str , str ] | None ,
865898 settings_path : Path | None = None ,
899+ default_value_overrides : dict [str , Any ] | None = None ,
866900) -> None :
867901 """Run code generation with the given config and parameters."""
868902 result = generate (
@@ -990,6 +1024,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917
9901024 all_exports_collision_strategy = config .all_exports_collision_strategy ,
9911025 field_type_collision_strategy = config .field_type_collision_strategy ,
9921026 module_split_mode = config .module_split_mode ,
1027+ default_value_overrides = default_value_overrides ,
9931028 )
9941029
9951030 if output is None and result is not None : # pragma: no cover
@@ -1175,46 +1210,45 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912,
11751210 else :
11761211 config .additional_imports = list (config .additional_imports ) + additional_imports_from_template_data
11771212
1178- if config .aliases is None :
1179- aliases = None
1180- else :
1181- with config .aliases as data :
1182- try :
1183- aliases = json .load (data )
1184- except json .JSONDecodeError as e :
1185- print (f"Unable to load alias mapping: { e } " , file = sys .stderr ) # noqa: T201
1186- return Exit .ERROR
1187- if not isinstance (aliases , dict ) or not all (
1213+ def _validate_aliases (data : Any ) -> str | None :
1214+ if not isinstance (data , dict ) or not all (
11881215 isinstance (k , str ) and (isinstance (v , str ) or (isinstance (v , list ) and all (isinstance (i , str ) for i in v )))
1189- for k , v in aliases .items ()
1216+ for k , v in data .items ()
11901217 ):
1191- print ( # noqa: T201
1192- "Alias mapping must be a JSON mapping with string keys and string or list of strings values "
1193- '(e.g. {"from": "to", "field": ["alias1", "alias2"]})' ,
1194- file = sys .stderr ,
1218+ return (
1219+ "must be a JSON mapping with string keys and string or list of strings values "
1220+ '(e.g. {"from": "to", "field": ["alias1", "alias2"]})'
11951221 )
1196- return Exit . ERROR
1222+ return None
11971223
1198- if config .custom_formatters_kwargs is None :
1199- custom_formatters_kwargs = None
1200- else :
1201- with config .custom_formatters_kwargs as data :
1202- try :
1203- custom_formatters_kwargs = json .load (data )
1204- except json .JSONDecodeError as e : # pragma: no cover
1205- print ( # noqa: T201
1206- f"Unable to load custom_formatters_kwargs mapping: { e } " ,
1207- file = sys .stderr ,
1208- )
1209- return Exit .ERROR
1210- if not isinstance (custom_formatters_kwargs , dict ) or not all (
1211- isinstance (k , str ) and isinstance (v , str ) for k , v in custom_formatters_kwargs .items ()
1212- ): # pragma: no cover
1213- print ( # noqa: T201
1214- 'Custom formatters kwargs mapping must be a JSON string mapping (e.g. {"from": "to", ...})' ,
1215- file = sys .stderr ,
1216- )
1217- return Exit .ERROR
1224+ def _validate_string_key_dict (data : Any ) -> str | None :
1225+ if not isinstance (data , dict ) or not all (isinstance (k , str ) for k in data ):
1226+ return "must be a JSON object with string keys"
1227+ return None
1228+
1229+ def _validate_string_mapping (data : Any ) -> str | None :
1230+ if not isinstance (data , dict ) or not all (isinstance (k , str ) and isinstance (v , str ) for k , v in data .items ()):
1231+ return 'must be a JSON string mapping (e.g. {"key": "value", ...})'
1232+ return None
1233+
1234+ aliases , error = _load_json_config (config .aliases , "alias mapping" , _validate_aliases )
1235+ if error :
1236+ print (error , file = sys .stderr ) # noqa: T201
1237+ return Exit .ERROR
1238+
1239+ default_value_overrides , error = _load_json_config (
1240+ config .default_values , "default values mapping" , _validate_string_key_dict
1241+ )
1242+ if error :
1243+ print (error , file = sys .stderr ) # noqa: T201
1244+ return Exit .ERROR
1245+
1246+ custom_formatters_kwargs , error = _load_json_config (
1247+ config .custom_formatters_kwargs , "custom_formatters_kwargs mapping" , _validate_string_mapping
1248+ )
1249+ if error :
1250+ print (error , file = sys .stderr ) # noqa: T201
1251+ return Exit .ERROR
12181252
12191253 if config .check :
12201254 config_output = cast ("Path" , config .output )
@@ -1260,6 +1294,7 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912,
12601294 command_line = shlex .join (["datamodel-codegen" , * args ]) if config .enable_command_header else None ,
12611295 custom_formatters_kwargs = custom_formatters_kwargs ,
12621296 settings_path = config .output ,
1297+ default_value_overrides = default_value_overrides ,
12631298 )
12641299 except InvalidClassNameError as e :
12651300 print (f"{ e } You have to set `--class-name` option" , file = sys .stderr ) # noqa: T201
@@ -1308,7 +1343,9 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912,
13081343 try :
13091344 from datamodel_code_generator .watch import watch_and_regenerate # noqa: PLC0415
13101345
1311- return watch_and_regenerate (config , extra_template_data , aliases , custom_formatters_kwargs )
1346+ return watch_and_regenerate (
1347+ config , extra_template_data , aliases , custom_formatters_kwargs , default_value_overrides
1348+ )
13121349 except Exception as e : # noqa: BLE001
13131350 print (str (e ), file = sys .stderr ) # noqa: T201
13141351 return Exit .ERROR
0 commit comments