Skip to content

Commit ef8fc02

Browse files
committed
Add CLI ignore unknown args config option.
1 parent fa21884 commit ef8fc02

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

pydantic_settings/main.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class SettingsConfigDict(ConfigDict, total=False):
4242
cli_exit_on_error: bool
4343
cli_prefix: str
4444
cli_implicit_flags: bool | None
45+
cli_ignore_unknown_args: bool | None
4546
secrets_dir: PathType | None
4647
json_file: PathType | None
4748
json_file_encoding: str | None
@@ -120,6 +121,7 @@ class BaseSettings(BaseModel):
120121
_cli_prefix: The root parser command line arguments prefix. Defaults to "".
121122
_cli_implicit_flags: Whether `bool` fields should be implicitly converted into CLI boolean flags.
122123
(e.g. --flag, --no-flag). Defaults to `False`.
124+
_cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`.
123125
_secrets_dir: The secret files directory or a sequence of directories. Defaults to `None`.
124126
"""
125127

@@ -145,6 +147,7 @@ def __init__(
145147
_cli_exit_on_error: bool | None = None,
146148
_cli_prefix: str | None = None,
147149
_cli_implicit_flags: bool | None = None,
150+
_cli_ignore_unknown_args: bool | None = None,
148151
_secrets_dir: PathType | None = None,
149152
**values: Any,
150153
) -> None:
@@ -172,6 +175,7 @@ def __init__(
172175
_cli_exit_on_error=_cli_exit_on_error,
173176
_cli_prefix=_cli_prefix,
174177
_cli_implicit_flags=_cli_implicit_flags,
178+
_cli_ignore_unknown_args=_cli_ignore_unknown_args,
175179
_secrets_dir=_secrets_dir,
176180
)
177181
)
@@ -223,6 +227,7 @@ def _settings_build_values(
223227
_cli_exit_on_error: bool | None = None,
224228
_cli_prefix: str | None = None,
225229
_cli_implicit_flags: bool | None = None,
230+
_cli_ignore_unknown_args: bool | None = None,
226231
_secrets_dir: PathType | None = None,
227232
) -> dict[str, Any]:
228233
# Determine settings config values
@@ -280,6 +285,11 @@ def _settings_build_values(
280285
cli_implicit_flags = (
281286
_cli_implicit_flags if _cli_implicit_flags is not None else self.model_config.get('cli_implicit_flags')
282287
)
288+
cli_ignore_unknown_args = (
289+
_cli_ignore_unknown_args
290+
if _cli_ignore_unknown_args is not None
291+
else self.model_config.get('cli_ignore_unknown_args')
292+
)
283293

284294
secrets_dir = _secrets_dir if _secrets_dir is not None else self.model_config.get('secrets_dir')
285295

@@ -339,6 +349,7 @@ def _settings_build_values(
339349
cli_exit_on_error=cli_exit_on_error,
340350
cli_prefix=cli_prefix,
341351
cli_implicit_flags=cli_implicit_flags,
352+
cli_ignore_unknown_args=cli_ignore_unknown_args,
342353
case_sensitive=case_sensitive,
343354
)
344355
if cli_settings_source is None
@@ -388,6 +399,7 @@ def _settings_build_values(
388399
cli_exit_on_error=True,
389400
cli_prefix='',
390401
cli_implicit_flags=False,
402+
cli_ignore_unknown_args=False,
391403
json_file=None,
392404
json_file_encoding=None,
393405
yaml_file=None,

pydantic_settings/sources.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,7 @@ class CliSettingsSource(EnvSettingsSource, Generic[T]):
10301030
cli_prefix: Prefix for command line arguments added under the root parser. Defaults to "".
10311031
cli_implicit_flags: Whether `bool` fields should be implicitly converted into CLI boolean flags.
10321032
(e.g. --flag, --no-flag). Defaults to `False`.
1033+
cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`.
10331034
case_sensitive: Whether CLI "--arg" names should be read with case-sensitivity. Defaults to `True`.
10341035
Note: Case-insensitive matching is only supported on the internal root parser and does not apply to CLI
10351036
subcommands.
@@ -1058,9 +1059,10 @@ def __init__(
10581059
cli_exit_on_error: bool | None = None,
10591060
cli_prefix: str | None = None,
10601061
cli_implicit_flags: bool | None = None,
1062+
cli_ignore_unknown_args: bool | None = None,
10611063
case_sensitive: bool | None = True,
10621064
root_parser: Any = None,
1063-
parse_args_method: Callable[..., Any] | None = ArgumentParser.parse_args,
1065+
parse_args_method: Callable[..., Any] | None = None,
10641066
add_argument_method: Callable[..., Any] | None = ArgumentParser.add_argument,
10651067
add_argument_group_method: Callable[..., Any] | None = ArgumentParser.add_argument_group,
10661068
add_parser_method: Callable[..., Any] | None = _SubParsersAction.add_parser,
@@ -1106,6 +1108,11 @@ def __init__(
11061108
if cli_implicit_flags is not None
11071109
else settings_cls.model_config.get('cli_implicit_flags', False)
11081110
)
1111+
self.cli_ignore_unknown_args = (
1112+
cli_ignore_unknown_args
1113+
if cli_ignore_unknown_args is not None
1114+
else settings_cls.model_config.get('cli_ignore_unknown_args', False)
1115+
)
11091116

11101117
case_sensitive = case_sensitive if case_sensitive is not None else True
11111118
if not case_sensitive and root_parser is not None:
@@ -1521,14 +1528,19 @@ def none_parser_method(*args: Any, **kwargs: Any) -> Any:
15211528
def _connect_root_parser(
15221529
self,
15231530
root_parser: T,
1524-
parse_args_method: Callable[..., Any] | None = ArgumentParser.parse_args,
1531+
parse_args_method: Callable[..., Any] | None,
15251532
add_argument_method: Callable[..., Any] | None = ArgumentParser.add_argument,
15261533
add_argument_group_method: Callable[..., Any] | None = ArgumentParser.add_argument_group,
15271534
add_parser_method: Callable[..., Any] | None = _SubParsersAction.add_parser,
15281535
add_subparsers_method: Callable[..., Any] | None = ArgumentParser.add_subparsers,
15291536
formatter_class: Any = RawDescriptionHelpFormatter,
15301537
) -> None:
1538+
def _parse_known_args(*args: Any, **kwargs: Any) -> Namespace:
1539+
return ArgumentParser.parse_known_args(*args, **kwargs)[0]
1540+
15311541
self._root_parser = root_parser
1542+
if parse_args_method is None:
1543+
parse_args_method = _parse_known_args if self.cli_ignore_unknown_args else ArgumentParser.parse_args
15321544
self._parse_args = self._connect_parser_method(parse_args_method, 'parsed_args_method')
15331545
self._add_argument = self._connect_parser_method(add_argument_method, 'add_argument_method')
15341546
self._add_argument_group = self._connect_parser_method(add_argument_group_method, 'add_argument_group_method')

tests/test_settings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3981,6 +3981,18 @@ class Settings(BaseSettings, cli_parse_args=True): ...
39813981
Settings(_cli_exit_on_error=False)
39823982

39833983

3984+
def test_cli_ignore_unknown_args():
3985+
class Cfg(BaseSettings, cli_ignore_unknown_args=True):
3986+
this: str = 'hello'
3987+
that: int = 123
3988+
3989+
cfg = Cfg(_cli_parse_args=['not_my_positional_arg', '--not-my-optional-arg=456'])
3990+
assert cfg.model_dump() == {'this': 'hello', 'that': 123}
3991+
3992+
cfg = Cfg(_cli_parse_args=['not_my_positional_arg', '--not-my-optional-arg=456', '--this=goodbye', '--that=789'])
3993+
assert cfg.model_dump() == {'this': 'goodbye', 'that': 789}
3994+
3995+
39843996
@pytest.mark.parametrize('parser_type', [pytest.Parser, argparse.ArgumentParser, CliDummyParser])
39853997
@pytest.mark.parametrize('prefix', ['', 'cfg'])
39863998
def test_cli_user_settings_source(parser_type, prefix):

0 commit comments

Comments
 (0)