Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cli_ignore_unknown_args config option. #405

Merged
merged 5 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
20 changes: 20 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,26 @@ class ImplicitSettings(BaseSettings, cli_parse_args=True, cli_implicit_flags=Tru
"""
```

#### Ignore Unknown Arguments

Change whether to ignore unknown CLI arguments and only parse known ones using `cli_ignore_unknown_args`. By default, the CLI
kschwab marked this conversation as resolved.
Show resolved Hide resolved
does not ignore any args.

```py
import sys

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True, cli_ignore_unknown_args=True):
good_arg: str


sys.argv = ['example.py', '--bad-arg=bad', 'ANOTHER_BAD_ARG', '--good_arg=hello world']
print(Settings().model_dump())
#> {'good_arg': 'hello world'}
```

#### Change Whether CLI Should Exit on Error

Change whether the CLI internal parser will exit on error or raise a `SettingsError` exception by using
Expand Down
12 changes: 12 additions & 0 deletions pydantic_settings/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class SettingsConfigDict(ConfigDict, total=False):
cli_exit_on_error: bool
cli_prefix: str
cli_implicit_flags: bool | None
cli_ignore_unknown_args: bool | None
secrets_dir: PathType | None
json_file: PathType | None
json_file_encoding: str | None
Expand Down Expand Up @@ -120,6 +121,7 @@ class BaseSettings(BaseModel):
_cli_prefix: The root parser command line arguments prefix. Defaults to "".
_cli_implicit_flags: Whether `bool` fields should be implicitly converted into CLI boolean flags.
(e.g. --flag, --no-flag). Defaults to `False`.
_cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`.
_secrets_dir: The secret files directory or a sequence of directories. Defaults to `None`.
"""

Expand All @@ -145,6 +147,7 @@ def __init__(
_cli_exit_on_error: bool | None = None,
_cli_prefix: str | None = None,
_cli_implicit_flags: bool | None = None,
_cli_ignore_unknown_args: bool | None = None,
_secrets_dir: PathType | None = None,
**values: Any,
) -> None:
Expand Down Expand Up @@ -172,6 +175,7 @@ def __init__(
_cli_exit_on_error=_cli_exit_on_error,
_cli_prefix=_cli_prefix,
_cli_implicit_flags=_cli_implicit_flags,
_cli_ignore_unknown_args=_cli_ignore_unknown_args,
_secrets_dir=_secrets_dir,
)
)
Expand Down Expand Up @@ -223,6 +227,7 @@ def _settings_build_values(
_cli_exit_on_error: bool | None = None,
_cli_prefix: str | None = None,
_cli_implicit_flags: bool | None = None,
_cli_ignore_unknown_args: bool | None = None,
_secrets_dir: PathType | None = None,
) -> dict[str, Any]:
# Determine settings config values
Expand Down Expand Up @@ -280,6 +285,11 @@ def _settings_build_values(
cli_implicit_flags = (
_cli_implicit_flags if _cli_implicit_flags is not None else self.model_config.get('cli_implicit_flags')
)
cli_ignore_unknown_args = (
_cli_ignore_unknown_args
if _cli_ignore_unknown_args is not None
else self.model_config.get('cli_ignore_unknown_args')
)

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

Expand Down Expand Up @@ -339,6 +349,7 @@ def _settings_build_values(
cli_exit_on_error=cli_exit_on_error,
cli_prefix=cli_prefix,
cli_implicit_flags=cli_implicit_flags,
cli_ignore_unknown_args=cli_ignore_unknown_args,
case_sensitive=case_sensitive,
)
if cli_settings_source is None
Expand Down Expand Up @@ -388,6 +399,7 @@ def _settings_build_values(
cli_exit_on_error=True,
cli_prefix='',
cli_implicit_flags=False,
cli_ignore_unknown_args=False,
json_file=None,
json_file_encoding=None,
yaml_file=None,
Expand Down
16 changes: 14 additions & 2 deletions pydantic_settings/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@ class CliSettingsSource(EnvSettingsSource, Generic[T]):
cli_prefix: Prefix for command line arguments added under the root parser. Defaults to "".
cli_implicit_flags: Whether `bool` fields should be implicitly converted into CLI boolean flags.
(e.g. --flag, --no-flag). Defaults to `False`.
cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to `False`.
case_sensitive: Whether CLI "--arg" names should be read with case-sensitivity. Defaults to `True`.
Note: Case-insensitive matching is only supported on the internal root parser and does not apply to CLI
subcommands.
Expand Down Expand Up @@ -1058,9 +1059,10 @@ def __init__(
cli_exit_on_error: bool | None = None,
cli_prefix: str | None = None,
cli_implicit_flags: bool | None = None,
cli_ignore_unknown_args: bool | None = None,
case_sensitive: bool | None = True,
root_parser: Any = None,
parse_args_method: Callable[..., Any] | None = ArgumentParser.parse_args,
parse_args_method: Callable[..., Any] | None = None,
add_argument_method: Callable[..., Any] | None = ArgumentParser.add_argument,
add_argument_group_method: Callable[..., Any] | None = ArgumentParser.add_argument_group,
add_parser_method: Callable[..., Any] | None = _SubParsersAction.add_parser,
Expand Down Expand Up @@ -1106,6 +1108,11 @@ def __init__(
if cli_implicit_flags is not None
else settings_cls.model_config.get('cli_implicit_flags', False)
)
self.cli_ignore_unknown_args = (
cli_ignore_unknown_args
if cli_ignore_unknown_args is not None
else settings_cls.model_config.get('cli_ignore_unknown_args', False)
)

case_sensitive = case_sensitive if case_sensitive is not None else True
if not case_sensitive and root_parser is not None:
Expand Down Expand Up @@ -1521,14 +1528,19 @@ def none_parser_method(*args: Any, **kwargs: Any) -> Any:
def _connect_root_parser(
self,
root_parser: T,
parse_args_method: Callable[..., Any] | None = ArgumentParser.parse_args,
parse_args_method: Callable[..., Any] | None,
add_argument_method: Callable[..., Any] | None = ArgumentParser.add_argument,
add_argument_group_method: Callable[..., Any] | None = ArgumentParser.add_argument_group,
add_parser_method: Callable[..., Any] | None = _SubParsersAction.add_parser,
add_subparsers_method: Callable[..., Any] | None = ArgumentParser.add_subparsers,
formatter_class: Any = RawDescriptionHelpFormatter,
) -> None:
def _parse_known_args(*args: Any, **kwargs: Any) -> Namespace:
return ArgumentParser.parse_known_args(*args, **kwargs)[0]

self._root_parser = root_parser
if parse_args_method is None:
parse_args_method = _parse_known_args if self.cli_ignore_unknown_args else ArgumentParser.parse_args
self._parse_args = self._connect_parser_method(parse_args_method, 'parsed_args_method')
self._add_argument = self._connect_parser_method(add_argument_method, 'add_argument_method')
self._add_argument_group = self._connect_parser_method(add_argument_group_method, 'add_argument_group_method')
Expand Down
12 changes: 12 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3981,6 +3981,18 @@ class Settings(BaseSettings, cli_parse_args=True): ...
Settings(_cli_exit_on_error=False)


def test_cli_ignore_unknown_args():
class Cfg(BaseSettings, cli_ignore_unknown_args=True):
this: str = 'hello'
that: int = 123

cfg = Cfg(_cli_parse_args=['not_my_positional_arg', '--not-my-optional-arg=456'])
assert cfg.model_dump() == {'this': 'hello', 'that': 123}

cfg = Cfg(_cli_parse_args=['not_my_positional_arg', '--not-my-optional-arg=456', '--this=goodbye', '--that=789'])
assert cfg.model_dump() == {'this': 'goodbye', 'that': 789}


@pytest.mark.parametrize('parser_type', [pytest.Parser, argparse.ArgumentParser, CliDummyParser])
@pytest.mark.parametrize('prefix', ['', 'cfg'])
def test_cli_user_settings_source(parser_type, prefix):
Expand Down
Loading