Skip to content

Commit ed7fd42

Browse files
kschwabhramezani
andauthored
CLI submodel suppress. (#587)
Co-authored-by: Hasan Ramezani <[email protected]>
1 parent e9fb316 commit ed7fd42

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

pydantic_settings/sources/providers/cli.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@ def _add_parser_args(
665665
group: Any,
666666
alias_prefixes: list[str],
667667
model_default: Any,
668+
is_model_suppressed: bool = False,
668669
) -> ArgumentParser:
669670
subparsers: Any = None
670671
alias_path_args: dict[str, str] = {}
@@ -738,7 +739,7 @@ def _add_parser_args(
738739
is_parser_submodel = sub_models and not is_append_action
739740
kwargs: dict[str, Any] = {}
740741
kwargs['default'] = CLI_SUPPRESS
741-
kwargs['help'] = self._help_format(field_name, field_info, model_default)
742+
kwargs['help'] = self._help_format(field_name, field_info, model_default, is_model_suppressed)
742743
kwargs['metavar'] = self._metavar_format(field_info.annotation)
743744
kwargs['required'] = (
744745
self.cli_enforce_required and field_info.is_required() and model_default is PydanticUndefined
@@ -782,6 +783,7 @@ def _add_parser_args(
782783
field_info,
783784
alias_names,
784785
model_default=model_default,
786+
is_model_suppressed=is_model_suppressed,
785787
)
786788
elif not is_alias_path_only:
787789
if group is not None:
@@ -869,6 +871,7 @@ def _add_parser_submodels(
869871
field_info: FieldInfo,
870872
alias_names: tuple[str, ...],
871873
model_default: Any,
874+
is_model_suppressed: bool,
872875
) -> None:
873876
if issubclass(model, CliMutuallyExclusiveGroup):
874877
# Argparse has deprecated "calling add_argument_group() or add_mutually_exclusive_group() on a
@@ -906,11 +909,14 @@ def _add_parser_submodels(
906909
model_group_kwargs['description'] = desc_header
907910

908911
preferred_alias = alias_names[0]
912+
is_model_suppressed = self._is_field_suppressed(field_info) or is_model_suppressed
909913
if not self.cli_avoid_json:
910914
added_args.append(arg_names[0])
911915
kwargs['nargs'] = '?'
912916
kwargs['const'] = '{}'
913-
kwargs['help'] = f'set {arg_names[0]} from JSON string (default: {{}})'
917+
kwargs['help'] = kwargs['help'] = (
918+
CLI_SUPPRESS if is_model_suppressed else f'set {arg_names[0]} from JSON string (default: {{}})'
919+
)
914920
model_group = self._add_group(parser, **model_group_kwargs)
915921
self._add_argument(model_group, *(f'{flag_prefix}{name}' for name in arg_names), **kwargs)
916922
for model in sub_models:
@@ -923,6 +929,7 @@ def _add_parser_submodels(
923929
group=model_group if model_group else model_group_kwargs,
924930
alias_prefixes=[f'{arg_prefix}{name}.' for name in alias_names[1:]],
925931
model_default=model_default,
932+
is_model_suppressed=is_model_suppressed,
926933
)
927934

928935
def _add_parser_alias_paths(
@@ -1015,9 +1022,11 @@ def _metavar_format_recurse(self, obj: Any) -> str:
10151022
def _metavar_format(self, obj: Any) -> str:
10161023
return self._metavar_format_recurse(obj).replace(', ', ',')
10171024

1018-
def _help_format(self, field_name: str, field_info: FieldInfo, model_default: Any) -> str:
1025+
def _help_format(
1026+
self, field_name: str, field_info: FieldInfo, model_default: Any, is_model_suppressed: bool
1027+
) -> str:
10191028
_help = field_info.description if field_info.description else ''
1020-
if _help == CLI_SUPPRESS or CLI_SUPPRESS in field_info.metadata:
1029+
if is_model_suppressed or self._is_field_suppressed(field_info):
10211030
return CLI_SUPPRESS
10221031

10231032
if field_info.is_required() and model_default in (PydanticUndefined, None):
@@ -1037,3 +1046,7 @@ def _help_format(self, field_name: str, field_info: FieldInfo, model_default: An
10371046
default = f'(default factory: {self._metavar_format(field_info.default_factory)})'
10381047
_help += f' {default}' if _help else default
10391048
return _help.replace('%', '%%') if issubclass(type(self._root_parser), ArgumentParser) else _help
1049+
1050+
def _is_field_suppressed(self, field_info: FieldInfo) -> bool:
1051+
_help = field_info.description if field_info.description else ''
1052+
return _help == CLI_SUPPRESS or CLI_SUPPRESS in field_info.metadata

tests/test_source_cli.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -2139,9 +2139,25 @@ def cli_cmd(self) -> None:
21392139

21402140

21412141
def test_cli_suppress(capsys, monkeypatch):
2142+
class DeepHiddenSubModel(BaseModel):
2143+
deep_hidden_a: int
2144+
deep_hidden_b: int
2145+
2146+
class HiddenSubModel(BaseModel):
2147+
hidden_a: int
2148+
hidden_b: int
2149+
deep_hidden_obj: DeepHiddenSubModel
2150+
2151+
class SubModel(BaseModel):
2152+
visible_a: int
2153+
visible_b: int
2154+
deep_hidden_obj: CliSuppress[DeepHiddenSubModel]
2155+
21422156
class Settings(BaseSettings, cli_parse_args=True):
21432157
field_a: CliSuppress[int] = 0
21442158
field_b: str = Field(default=1, description=CLI_SUPPRESS)
2159+
hidden_obj: CliSuppress[HiddenSubModel]
2160+
visible_obj: SubModel
21452161

21462162
with monkeypatch.context() as m:
21472163
m.setattr(sys, 'argv', ['example.py', '--help'])
@@ -2151,10 +2167,18 @@ class Settings(BaseSettings, cli_parse_args=True):
21512167

21522168
assert (
21532169
capsys.readouterr().out
2154-
== f"""usage: example.py [-h]
2170+
== f"""usage: example.py [-h] [--visible_obj [JSON]] [--visible_obj.visible_a int]
2171+
[--visible_obj.visible_b int]
21552172
21562173
{ARGPARSE_OPTIONS_TEXT}:
2157-
-h, --help show this help message and exit
2174+
-h, --help show this help message and exit
2175+
2176+
visible_obj options:
2177+
--visible_obj [JSON] set visible_obj from JSON string (default: {{}})
2178+
--visible_obj.visible_a int
2179+
(required)
2180+
--visible_obj.visible_b int
2181+
(required)
21582182
"""
21592183
)
21602184

0 commit comments

Comments
 (0)