@@ -1382,6 +1382,7 @@ def _connect_root_parser(
13821382 subcommand_prefix = self .env_prefix ,
13831383 group = None ,
13841384 alias_prefixes = [],
1385+ model_default = PydanticUndefined ,
13851386 )
13861387
13871388 def _add_parser_args (
@@ -1393,6 +1394,7 @@ def _add_parser_args(
13931394 subcommand_prefix : str ,
13941395 group : Any ,
13951396 alias_prefixes : list [str ],
1397+ model_default : Any ,
13961398 ) -> ArgumentParser :
13971399 subparsers : Any = None
13981400 alias_path_args : dict [str , str ] = {}
@@ -1425,16 +1427,19 @@ def _add_parser_args(
14251427 subcommand_prefix = f'{ subcommand_prefix } { field_name } .' ,
14261428 group = None ,
14271429 alias_prefixes = [],
1430+ model_default = PydanticUndefined ,
14281431 )
14291432 else :
14301433 resolved_names , is_alias_path_only = self ._get_resolved_names (field_name , field_info , alias_path_args )
14311434 arg_flag : str = '--'
14321435 kwargs : dict [str , Any ] = {}
14331436 kwargs ['default' ] = SUPPRESS
1434- kwargs ['help' ] = self ._help_format (field_info )
1437+ kwargs ['help' ] = self ._help_format (field_name , field_info , model_default )
14351438 kwargs ['dest' ] = f'{ arg_prefix } { resolved_names [0 ]} '
14361439 kwargs ['metavar' ] = self ._metavar_format (field_info .annotation )
1437- kwargs ['required' ] = self .cli_enforce_required and field_info .is_required ()
1440+ kwargs ['required' ] = (
1441+ self .cli_enforce_required and field_info .is_required () and model_default is PydanticUndefined
1442+ )
14381443 if kwargs ['dest' ] in added_args :
14391444 continue
14401445 if _annotation_contains_types (
@@ -1462,8 +1467,10 @@ def _add_parser_args(
14621467 arg_flag ,
14631468 arg_names ,
14641469 kwargs ,
1470+ field_name ,
14651471 field_info ,
14661472 resolved_names ,
1473+ model_default = model_default ,
14671474 )
14681475 elif is_alias_path_only :
14691476 continue
@@ -1502,19 +1509,33 @@ def _add_parser_submodels(
15021509 arg_flag : str ,
15031510 arg_names : list [str ],
15041511 kwargs : dict [str , Any ],
1512+ field_name : str ,
15051513 field_info : FieldInfo ,
15061514 resolved_names : tuple [str , ...],
1515+ model_default : Any ,
15071516 ) -> None :
15081517 model_group : Any = None
15091518 model_group_kwargs : dict [str , Any ] = {}
15101519 model_group_kwargs ['title' ] = f'{ arg_names [0 ]} options'
1511- model_group_kwargs ['description' ] = (
1512- None
1513- if sub_models [0 ].__doc__ is None
1514- else dedent (sub_models [0 ].__doc__ )
1515- if self .cli_use_class_docs_for_groups and len (sub_models ) == 1
1516- else field_info .description
1517- )
1520+ model_group_kwargs ['description' ] = field_info .description
1521+ if self .cli_use_class_docs_for_groups and len (sub_models ) == 1 :
1522+ model_group_kwargs ['description' ] = None if sub_models [0 ].__doc__ is None else dedent (sub_models [0 ].__doc__ )
1523+
1524+ if model_default not in (PydanticUndefined , None ):
1525+ if is_model_class (type (model_default )) or is_pydantic_dataclass (type (model_default )):
1526+ model_default = getattr (model_default , field_name )
1527+ else :
1528+ if field_info .default is not PydanticUndefined :
1529+ model_default = field_info .default
1530+ elif field_info .default_factory is not None :
1531+ model_default = field_info .default_factory
1532+ if model_default is None :
1533+ desc_header = f'default: { self .cli_parse_none_str } (undefined)'
1534+ if model_group_kwargs ['description' ] is not None :
1535+ model_group_kwargs ['description' ] = dedent (f'{ desc_header } \n { model_group_kwargs ["description" ]} ' )
1536+ else :
1537+ model_group_kwargs ['description' ] = desc_header
1538+
15181539 if not self .cli_avoid_json :
15191540 added_args .append (arg_names [0 ])
15201541 kwargs ['help' ] = f'set { arg_names [0 ]} from JSON string'
@@ -1529,6 +1550,7 @@ def _add_parser_submodels(
15291550 subcommand_prefix = subcommand_prefix ,
15301551 group = model_group if model_group else model_group_kwargs ,
15311552 alias_prefixes = [f'{ arg_prefix } { name } .' for name in resolved_names [1 :]],
1553+ model_default = model_default ,
15321554 )
15331555
15341556 def _add_parser_alias_paths (
@@ -1618,14 +1640,19 @@ def _metavar_format_recurse(self, obj: Any) -> str:
16181640 def _metavar_format (self , obj : Any ) -> str :
16191641 return self ._metavar_format_recurse (obj ).replace (', ' , ',' )
16201642
1621- def _help_format (self , field_info : FieldInfo ) -> str :
1643+ def _help_format (self , field_name : str , field_info : FieldInfo , model_default : Any ) -> str :
16221644 _help = field_info .description if field_info .description else ''
1623- if field_info .is_required ():
1645+ if field_info .is_required () and model_default in ( PydanticUndefined , None ) :
16241646 if _CliPositionalArg not in field_info .metadata :
1625- _help += ' (required)' if _help else '(required)'
1647+ ifdef = 'ifdef: ' if model_default is None else ''
1648+ _help += f' ({ ifdef } required)' if _help else f'({ ifdef } required)'
16261649 else :
16271650 default = f'(default: { self .cli_parse_none_str } )'
1628- if field_info .default not in (PydanticUndefined , None ):
1651+ if is_model_class (type (model_default )) or is_pydantic_dataclass (type (model_default )):
1652+ default = f'(default: { getattr (model_default , field_name )} )'
1653+ elif model_default not in (PydanticUndefined , None ) and callable (model_default ):
1654+ default = f'(default factory: { self ._metavar_format (model_default )} )'
1655+ elif field_info .default not in (PydanticUndefined , None ):
16291656 default = f'(default: { field_info .default } )'
16301657 elif field_info .default_factory is not None :
16311658 default = f'(default: { field_info .default_factory } )'
0 commit comments