Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions pydantic_settings/sources/providers/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ def _add_parser_args(
group: Any,
alias_prefixes: list[str],
model_default: Any,
is_model_suppressed: bool = False,
) -> ArgumentParser:
subparsers: Any = None
alias_path_args: dict[str, str] = {}
Expand Down Expand Up @@ -738,7 +739,7 @@ def _add_parser_args(
is_parser_submodel = sub_models and not is_append_action
kwargs: dict[str, Any] = {}
kwargs['default'] = CLI_SUPPRESS
kwargs['help'] = self._help_format(field_name, field_info, model_default)
kwargs['help'] = self._help_format(field_name, field_info, model_default, is_model_suppressed)
kwargs['metavar'] = self._metavar_format(field_info.annotation)
kwargs['required'] = (
self.cli_enforce_required and field_info.is_required() and model_default is PydanticUndefined
Expand Down Expand Up @@ -782,6 +783,7 @@ def _add_parser_args(
field_info,
alias_names,
model_default=model_default,
is_model_suppressed=is_model_suppressed,
)
elif not is_alias_path_only:
if group is not None:
Expand Down Expand Up @@ -869,6 +871,7 @@ def _add_parser_submodels(
field_info: FieldInfo,
alias_names: tuple[str, ...],
model_default: Any,
is_model_suppressed: bool,
) -> None:
if issubclass(model, CliMutuallyExclusiveGroup):
# Argparse has deprecated "calling add_argument_group() or add_mutually_exclusive_group() on a
Expand Down Expand Up @@ -906,11 +909,14 @@ def _add_parser_submodels(
model_group_kwargs['description'] = desc_header

preferred_alias = alias_names[0]
is_model_suppressed = self._is_field_suppressed(field_info) or is_model_suppressed
if not self.cli_avoid_json:
added_args.append(arg_names[0])
kwargs['nargs'] = '?'
kwargs['const'] = '{}'
kwargs['help'] = f'set {arg_names[0]} from JSON string (default: {{}})'
kwargs['help'] = kwargs['help'] = (
CLI_SUPPRESS if is_model_suppressed else f'set {arg_names[0]} from JSON string (default: {{}})'
)
model_group = self._add_group(parser, **model_group_kwargs)
self._add_argument(model_group, *(f'{flag_prefix}{name}' for name in arg_names), **kwargs)
for model in sub_models:
Expand All @@ -923,6 +929,7 @@ def _add_parser_submodels(
group=model_group if model_group else model_group_kwargs,
alias_prefixes=[f'{arg_prefix}{name}.' for name in alias_names[1:]],
model_default=model_default,
is_model_suppressed=is_model_suppressed,
)

def _add_parser_alias_paths(
Expand Down Expand Up @@ -1015,9 +1022,11 @@ def _metavar_format_recurse(self, obj: Any) -> str:
def _metavar_format(self, obj: Any) -> str:
return self._metavar_format_recurse(obj).replace(', ', ',')

def _help_format(self, field_name: str, field_info: FieldInfo, model_default: Any) -> str:
def _help_format(
self, field_name: str, field_info: FieldInfo, model_default: Any, is_model_suppressed: bool
) -> str:
_help = field_info.description if field_info.description else ''
if _help == CLI_SUPPRESS or CLI_SUPPRESS in field_info.metadata:
if is_model_suppressed or self._is_field_suppressed(field_info):
return CLI_SUPPRESS

if field_info.is_required() and model_default in (PydanticUndefined, None):
Expand All @@ -1037,3 +1046,7 @@ def _help_format(self, field_name: str, field_info: FieldInfo, model_default: An
default = f'(default factory: {self._metavar_format(field_info.default_factory)})'
_help += f' {default}' if _help else default
return _help.replace('%', '%%') if issubclass(type(self._root_parser), ArgumentParser) else _help

def _is_field_suppressed(self, field_info: FieldInfo) -> bool:
_help = field_info.description if field_info.description else ''
return _help == CLI_SUPPRESS or CLI_SUPPRESS in field_info.metadata
28 changes: 26 additions & 2 deletions tests/test_source_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2139,9 +2139,25 @@ def cli_cmd(self) -> None:


def test_cli_suppress(capsys, monkeypatch):
class DeepHiddenSubModel(BaseModel):
deep_hidden_a: int
deep_hidden_b: int

class HiddenSubModel(BaseModel):
hidden_a: int
hidden_b: int
deep_hidden_obj: DeepHiddenSubModel

class SubModel(BaseModel):
visible_a: int
visible_b: int
deep_hidden_obj: CliSuppress[DeepHiddenSubModel]

class Settings(BaseSettings, cli_parse_args=True):
field_a: CliSuppress[int] = 0
field_b: str = Field(default=1, description=CLI_SUPPRESS)
hidden_obj: CliSuppress[HiddenSubModel]
visible_obj: SubModel

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

assert (
capsys.readouterr().out
== f"""usage: example.py [-h]
== f"""usage: example.py [-h] [--visible_obj JSON] [--visible_obj.visible_a int]
[--visible_obj.visible_b int]

{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit
-h, --help show this help message and exit

visible_obj options:
--visible_obj JSON set visible_obj from JSON string
--visible_obj.visible_a int
(required)
--visible_obj.visible_b int
(required)
"""
)

Expand Down