Skip to content

Commit d2e498a

Browse files
authored
Add support for suppressing fields from CLI help. (#436)
1 parent 0d605d0 commit d2e498a

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

docs/index.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,42 @@ print(Settings().model_dump())
13261326
#> {'my_arg': 'hi'}
13271327
```
13281328

1329+
#### Suppressing Fields from CLI Help Text
1330+
1331+
To suppress a field from the CLI help text, the `CliSuppress` annotation can be used for field types, or the
1332+
`CLI_SUPPRESS` string constant can be used for field descriptions.
1333+
1334+
```py
1335+
import sys
1336+
1337+
from pydantic import Field
1338+
1339+
from pydantic_settings import CLI_SUPPRESS, BaseSettings, CliSuppress
1340+
1341+
1342+
class Settings(BaseSettings, cli_parse_args=True):
1343+
"""Suppress fields from CLI help text."""
1344+
1345+
field_a: CliSuppress[int] = 0
1346+
field_b: str = Field(default=1, description=CLI_SUPPRESS)
1347+
1348+
1349+
try:
1350+
sys.argv = ['example.py', '--help']
1351+
Settings()
1352+
except SystemExit as e:
1353+
print(e)
1354+
#> 0
1355+
"""
1356+
usage: example.py [-h]
1357+
1358+
Suppress fields from CLI help text.
1359+
1360+
options:
1361+
-h, --help show this help message and exit
1362+
"""
1363+
```
1364+
13291365
### Integrating with Existing Parsers
13301366

13311367
A CLI settings source can be integrated with existing parsers by overriding the default CLI settings source with a user

pydantic_settings/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from .main import BaseSettings, CliApp, SettingsConfigDict
22
from .sources import (
3+
CLI_SUPPRESS,
34
AzureKeyVaultSettingsSource,
45
CliExplicitFlag,
56
CliImplicitFlag,
67
CliPositionalArg,
78
CliSettingsSource,
89
CliSubCommand,
10+
CliSuppress,
911
DotEnvSettingsSource,
1012
EnvSettingsSource,
1113
InitSettingsSource,
@@ -27,6 +29,8 @@
2729
'CliApp',
2830
'CliSettingsSource',
2931
'CliSubCommand',
32+
'CliSuppress',
33+
'CLI_SUPPRESS',
3034
'CliPositionalArg',
3135
'CliExplicitFlag',
3236
'CliImplicitFlag',

pydantic_settings/sources.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ def error(self, message: str) -> NoReturn:
156156
_CliBoolFlag = TypeVar('_CliBoolFlag', bound=bool)
157157
CliImplicitFlag = Annotated[_CliBoolFlag, _CliImplicitFlag]
158158
CliExplicitFlag = Annotated[_CliBoolFlag, _CliExplicitFlag]
159+
CLI_SUPPRESS = SUPPRESS
160+
CliSuppress = Annotated[T, CLI_SUPPRESS]
159161

160162

161163
def get_subcommand(
@@ -1647,7 +1649,7 @@ def _add_parser_args(
16471649
)
16481650
is_parser_submodel = sub_models and not is_append_action
16491651
kwargs: dict[str, Any] = {}
1650-
kwargs['default'] = SUPPRESS
1652+
kwargs['default'] = CLI_SUPPRESS
16511653
kwargs['help'] = self._help_format(field_name, field_info, model_default)
16521654
kwargs['metavar'] = self._metavar_format(field_info.annotation)
16531655
kwargs['required'] = (
@@ -1816,7 +1818,7 @@ def _add_parser_alias_paths(
18161818
else f'{arg_prefix.replace(subcommand_prefix, "", 1)}{name}'
18171819
)
18181820
kwargs: dict[str, Any] = {}
1819-
kwargs['default'] = SUPPRESS
1821+
kwargs['default'] = CLI_SUPPRESS
18201822
kwargs['help'] = 'pydantic alias path'
18211823
kwargs['dest'] = f'{arg_prefix}{name}'
18221824
if metavar == 'dict' or is_nested_alias_path:
@@ -1883,6 +1885,9 @@ def _metavar_format(self, obj: Any) -> str:
18831885

18841886
def _help_format(self, field_name: str, field_info: FieldInfo, model_default: Any) -> str:
18851887
_help = field_info.description if field_info.description else ''
1888+
if _help == CLI_SUPPRESS or CLI_SUPPRESS in field_info.metadata:
1889+
return CLI_SUPPRESS
1890+
18861891
if field_info.is_required() and model_default in (PydanticUndefined, None):
18871892
if _CliPositionalArg not in field_info.metadata:
18881893
ifdef = 'ifdef: ' if model_default is None else ''

tests/test_source_cli.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@
3030
SettingsConfigDict,
3131
)
3232
from pydantic_settings.sources import (
33+
CLI_SUPPRESS,
3334
CliExplicitFlag,
3435
CliImplicitFlag,
3536
CliPositionalArg,
3637
CliSettingsSource,
3738
CliSubCommand,
39+
CliSuppress,
3840
SettingsError,
3941
get_subcommand,
4042
)
@@ -1648,10 +1650,10 @@ def test_cli_flag_prefix_char():
16481650
class Cfg(BaseSettings, cli_flag_prefix_char='+'):
16491651
my_var: str = Field(validation_alias=AliasChoices('m', 'my-var'))
16501652

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

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

16571659

@@ -2017,3 +2019,24 @@ def cli_cmd(self) -> None:
20172019
CliApp.run_subcommand(self)
20182020

20192021
CliApp.run(Root, cli_args=['child', '--val=hello'])
2022+
2023+
2024+
def test_cli_suppress(capsys, monkeypatch):
2025+
class Settings(BaseSettings, cli_parse_args=True):
2026+
field_a: CliSuppress[int] = 0
2027+
field_b: str = Field(default=1, description=CLI_SUPPRESS)
2028+
2029+
with monkeypatch.context() as m:
2030+
m.setattr(sys, 'argv', ['example.py', '--help'])
2031+
2032+
with pytest.raises(SystemExit):
2033+
CliApp.run(Settings)
2034+
2035+
assert (
2036+
capsys.readouterr().out
2037+
== f"""usage: example.py [-h]
2038+
2039+
{ARGPARSE_OPTIONS_TEXT}:
2040+
-h, --help show this help message and exit
2041+
"""
2042+
)

0 commit comments

Comments
 (0)