Skip to content

Commit

Permalink
Add support for suppressing fields from CLI help. (#436)
Browse files Browse the repository at this point in the history
  • Loading branch information
kschwab authored Oct 7, 2024
1 parent 0d605d0 commit d2e498a
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 4 deletions.
36 changes: 36 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,42 @@ print(Settings().model_dump())
#> {'my_arg': 'hi'}
```

#### Suppressing Fields from CLI Help Text

To suppress a field from the CLI help text, the `CliSuppress` annotation can be used for field types, or the
`CLI_SUPPRESS` string constant can be used for field descriptions.

```py
import sys

from pydantic import Field

from pydantic_settings import CLI_SUPPRESS, BaseSettings, CliSuppress


class Settings(BaseSettings, cli_parse_args=True):
"""Suppress fields from CLI help text."""

field_a: CliSuppress[int] = 0
field_b: str = Field(default=1, description=CLI_SUPPRESS)


try:
sys.argv = ['example.py', '--help']
Settings()
except SystemExit as e:
print(e)
#> 0
"""
usage: example.py [-h]
Suppress fields from CLI help text.
options:
-h, --help show this help message and exit
"""
```

### Integrating with Existing Parsers

A CLI settings source can be integrated with existing parsers by overriding the default CLI settings source with a user
Expand Down
4 changes: 4 additions & 0 deletions pydantic_settings/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from .main import BaseSettings, CliApp, SettingsConfigDict
from .sources import (
CLI_SUPPRESS,
AzureKeyVaultSettingsSource,
CliExplicitFlag,
CliImplicitFlag,
CliPositionalArg,
CliSettingsSource,
CliSubCommand,
CliSuppress,
DotEnvSettingsSource,
EnvSettingsSource,
InitSettingsSource,
Expand All @@ -27,6 +29,8 @@
'CliApp',
'CliSettingsSource',
'CliSubCommand',
'CliSuppress',
'CLI_SUPPRESS',
'CliPositionalArg',
'CliExplicitFlag',
'CliImplicitFlag',
Expand Down
9 changes: 7 additions & 2 deletions pydantic_settings/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ def error(self, message: str) -> NoReturn:
_CliBoolFlag = TypeVar('_CliBoolFlag', bound=bool)
CliImplicitFlag = Annotated[_CliBoolFlag, _CliImplicitFlag]
CliExplicitFlag = Annotated[_CliBoolFlag, _CliExplicitFlag]
CLI_SUPPRESS = SUPPRESS
CliSuppress = Annotated[T, CLI_SUPPRESS]


def get_subcommand(
Expand Down Expand Up @@ -1647,7 +1649,7 @@ def _add_parser_args(
)
is_parser_submodel = sub_models and not is_append_action
kwargs: dict[str, Any] = {}
kwargs['default'] = SUPPRESS
kwargs['default'] = CLI_SUPPRESS
kwargs['help'] = self._help_format(field_name, field_info, model_default)
kwargs['metavar'] = self._metavar_format(field_info.annotation)
kwargs['required'] = (
Expand Down Expand Up @@ -1816,7 +1818,7 @@ def _add_parser_alias_paths(
else f'{arg_prefix.replace(subcommand_prefix, "", 1)}{name}'
)
kwargs: dict[str, Any] = {}
kwargs['default'] = SUPPRESS
kwargs['default'] = CLI_SUPPRESS
kwargs['help'] = 'pydantic alias path'
kwargs['dest'] = f'{arg_prefix}{name}'
if metavar == 'dict' or is_nested_alias_path:
Expand Down Expand Up @@ -1883,6 +1885,9 @@ def _metavar_format(self, obj: Any) -> str:

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

if field_info.is_required() and model_default in (PydanticUndefined, None):
if _CliPositionalArg not in field_info.metadata:
ifdef = 'ifdef: ' if model_default is None else ''
Expand Down
27 changes: 25 additions & 2 deletions tests/test_source_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
SettingsConfigDict,
)
from pydantic_settings.sources import (
CLI_SUPPRESS,
CliExplicitFlag,
CliImplicitFlag,
CliPositionalArg,
CliSettingsSource,
CliSubCommand,
CliSuppress,
SettingsError,
get_subcommand,
)
Expand Down Expand Up @@ -1648,10 +1650,10 @@ def test_cli_flag_prefix_char():
class Cfg(BaseSettings, cli_flag_prefix_char='+'):
my_var: str = Field(validation_alias=AliasChoices('m', 'my-var'))

cfg = Cfg(_cli_parse_args=['++my-var=hello'])
cfg = CliApp.run(Cfg, cli_args=['++my-var=hello'])
assert cfg.model_dump() == {'my_var': 'hello'}

cfg = Cfg(_cli_parse_args=['+m=hello'])
cfg = CliApp.run(Cfg, cli_args=['+m=hello'])
assert cfg.model_dump() == {'my_var': 'hello'}


Expand Down Expand Up @@ -2017,3 +2019,24 @@ def cli_cmd(self) -> None:
CliApp.run_subcommand(self)

CliApp.run(Root, cli_args=['child', '--val=hello'])


def test_cli_suppress(capsys, monkeypatch):
class Settings(BaseSettings, cli_parse_args=True):
field_a: CliSuppress[int] = 0
field_b: str = Field(default=1, description=CLI_SUPPRESS)

with monkeypatch.context() as m:
m.setattr(sys, 'argv', ['example.py', '--help'])

with pytest.raises(SystemExit):
CliApp.run(Settings)

assert (
capsys.readouterr().out
== f"""usage: example.py [-h]
{ARGPARSE_OPTIONS_TEXT}:
-h, --help show this help message and exit
"""
)

0 comments on commit d2e498a

Please sign in to comment.