Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions pydantic_settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
AzureKeyVaultSettingsSource,
CliDualFlag,
CliExplicitFlag,
CliIgnoreArg,
CliImplicitFlag,
CliMutuallyExclusiveGroup,
CliPositionalArg,
Expand Down Expand Up @@ -42,6 +43,7 @@
'CliToggleFlag',
'CliDualFlag',
'CliMutuallyExclusiveGroup',
'CliIgnoreArg',
'CliPositionalArg',
'CliSettingsSource',
'CliSubCommand',
Expand Down
2 changes: 2 additions & 0 deletions pydantic_settings/sources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
CLI_SUPPRESS,
CliDualFlag,
CliExplicitFlag,
CliIgnoreArg,
CliImplicitFlag,
CliMutuallyExclusiveGroup,
CliPositionalArg,
Expand Down Expand Up @@ -45,6 +46,7 @@
'CliToggleFlag',
'CliDualFlag',
'CliMutuallyExclusiveGroup',
'CliIgnoreArg',
'CliPositionalArg',
'CliSettingsSource',
'CliSubCommand',
Expand Down
2 changes: 2 additions & 0 deletions pydantic_settings/sources/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .cli import (
CliDualFlag,
CliExplicitFlag,
CliIgnoreArg,
CliImplicitFlag,
CliMutuallyExclusiveGroup,
CliPositionalArg,
Expand All @@ -30,6 +31,7 @@
'CliToggleFlag',
'CliDualFlag',
'CliMutuallyExclusiveGroup',
'CliIgnoreArg',
'CliPositionalArg',
'CliSettingsSource',
'CliSubCommand',
Expand Down
9 changes: 9 additions & 0 deletions pydantic_settings/sources/providers/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
PydanticModel,
_CliDualFlag,
_CliExplicitFlag,
_CliIgnoreArg,
_CliImplicitFlag,
_CliPositionalArg,
_CliSubCommand,
Expand Down Expand Up @@ -228,6 +229,10 @@ def is_append_action(self) -> bool:
def is_parser_submodel(self) -> bool:
return not self.subcommand_dest and bool(self.sub_models) and not self.is_append_action

@cached_property
def is_ignore_arg(self) -> bool:
return _CliIgnoreArg in self.field_info.metadata

@cached_property
def is_no_decode(self) -> bool:
return self.field_info is not None and (
Expand All @@ -246,6 +251,7 @@ def is_no_decode(self) -> bool:
CliDualFlag = Annotated[_CliBoolFlag, _CliDualFlag]
CLI_SUPPRESS = SUPPRESS
CliSuppress = Annotated[T, CLI_SUPPRESS]
CliIgnoreArg = Annotated[T, _CliIgnoreArg]
CliUnknownArgs = Annotated[list[str], Field(default=[]), _CliUnknownArgs, NoDecode]


Expand Down Expand Up @@ -930,6 +936,9 @@ def _add_parser_args(
)
alias_path_args.update(arg.alias_paths)

if arg.is_ignore_arg:
continue

if arg.subcommand_dest:
for sub_model in arg.sub_models:
subcommand_alias = arg.subcommand_alias(sub_model)
Expand Down
5 changes: 5 additions & 0 deletions pydantic_settings/sources/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class _CliPositionalArg:
pass


class _CliIgnoreArg:
pass


class _CliImplicitFlag:
pass

Expand Down Expand Up @@ -82,6 +86,7 @@ class _CliUnknownArgs:
'_CliImplicitFlag',
'_CliToggleFlag',
'_CliDualFlag',
'_CliIgnoreArg',
'_CliPositionalArg',
'_CliSubCommand',
'_CliUnknownArgs',
Expand Down
58 changes: 44 additions & 14 deletions tests/test_source_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
CLI_SUPPRESS,
CliDualFlag,
CliExplicitFlag,
CliIgnoreArg,
CliImplicitFlag,
CliMutuallyExclusiveGroup,
CliPositionalArg,
Expand Down Expand Up @@ -1912,6 +1913,7 @@ class Cfg(BaseSettings, cli_flag_prefix_char='+'):
def test_cli_user_settings_source(parser_type, prefix):
class Cfg(BaseSettings):
pet: Literal['dog', 'cat', 'bird'] = 'bird'
num2: CliIgnoreArg[int] = -1

if parser_type is pytest.Parser:
parser = pytest.Parser(_ispytest=True)
Expand Down Expand Up @@ -1948,19 +1950,30 @@ class Cfg(BaseSettings):
add_arg = parser.add_argument
cli_cfg_settings = CliSettingsSource(Cfg, cli_prefix=prefix, root_parser=parser)

arg_prefix = f'{prefix}.' if prefix else ''

add_arg('--fruit', choices=['pear', 'kiwi', 'lime'])
add_arg('--num-list', action='append', type=int)
add_arg('--num', type=int)
add_arg(f'--{arg_prefix}num2', type=int)

args = ['--fruit', 'pear', '--num', '0', '--num-list', '1', '--num-list', '2', '--num-list', '3']
parsed_args = parse_args(args)
assert CliApp.run(Cfg, cli_args=parsed_args, cli_settings_source=cli_cfg_settings).model_dump() == {'pet': 'bird'}
assert CliApp.run(Cfg, cli_args=args, cli_settings_source=cli_cfg_settings).model_dump() == {'pet': 'bird'}
assert Cfg(_cli_settings_source=cli_cfg_settings(parsed_args=parsed_args)).model_dump() == {'pet': 'bird'}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=args)).model_dump() == {'pet': 'bird'}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=False)).model_dump() == {'pet': 'bird'}
assert CliApp.run(Cfg, cli_args=parsed_args, cli_settings_source=cli_cfg_settings).model_dump() == {
'pet': 'bird',
'num2': -1,
}
assert CliApp.run(Cfg, cli_args=args, cli_settings_source=cli_cfg_settings).model_dump() == {
'pet': 'bird',
'num2': -1,
}
assert Cfg(_cli_settings_source=cli_cfg_settings(parsed_args=parsed_args)).model_dump() == {
'pet': 'bird',
'num2': -1,
}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=args)).model_dump() == {'pet': 'bird', 'num2': -1}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=False)).model_dump() == {'pet': 'bird', 'num2': -1}

arg_prefix = f'{prefix}.' if prefix else ''
args = [
'--fruit',
'kiwi',
Expand All @@ -1972,15 +1985,26 @@ class Cfg(BaseSettings):
'2',
'--num-list',
'3',
f'--{arg_prefix}num2',
'1',
f'--{arg_prefix}pet',
'dog',
]
parsed_args = parse_args(args)
assert CliApp.run(Cfg, cli_args=parsed_args, cli_settings_source=cli_cfg_settings).model_dump() == {'pet': 'dog'}
assert CliApp.run(Cfg, cli_args=args, cli_settings_source=cli_cfg_settings).model_dump() == {'pet': 'dog'}
assert Cfg(_cli_settings_source=cli_cfg_settings(parsed_args=parsed_args)).model_dump() == {'pet': 'dog'}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=args)).model_dump() == {'pet': 'dog'}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=False)).model_dump() == {'pet': 'bird'}
assert CliApp.run(Cfg, cli_args=parsed_args, cli_settings_source=cli_cfg_settings).model_dump() == {
'pet': 'dog',
'num2': 1,
}
assert CliApp.run(Cfg, cli_args=args, cli_settings_source=cli_cfg_settings).model_dump() == {
'pet': 'dog',
'num2': 1,
}
assert Cfg(_cli_settings_source=cli_cfg_settings(parsed_args=parsed_args)).model_dump() == {
'pet': 'dog',
'num2': 1,
}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=args)).model_dump() == {'pet': 'dog', 'num2': 1}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=False)).model_dump() == {'pet': 'bird', 'num2': -1}

parsed_args = parse_args(
[
Expand All @@ -1996,13 +2020,19 @@ class Cfg(BaseSettings):
'3',
f'--{arg_prefix}pet',
'cat',
f'--{arg_prefix}num2',
'2',
]
)
assert CliApp.run(Cfg, cli_args=vars(parsed_args), cli_settings_source=cli_cfg_settings).model_dump() == {
'pet': 'cat'
'pet': 'cat',
'num2': 2,
}
assert Cfg(_cli_settings_source=cli_cfg_settings(parsed_args=vars(parsed_args))).model_dump() == {
'pet': 'cat',
'num2': 2,
}
assert Cfg(_cli_settings_source=cli_cfg_settings(parsed_args=vars(parsed_args))).model_dump() == {'pet': 'cat'}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=False)).model_dump() == {'pet': 'bird'}
assert Cfg(_cli_settings_source=cli_cfg_settings(args=False)).model_dump() == {'pet': 'bird', 'num2': -1}


@pytest.mark.parametrize('prefix', ['', 'cfg'])
Expand Down