Skip to content

Commit 284f88e

Browse files
Remove parsing of command line arguments from CliSettingsSource.__init__. (#656)
1 parent 8215eac commit 284f88e

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

pydantic_settings/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,8 @@ def _settings_build_values(
391391
dotenv_settings=dotenv_settings,
392392
file_secret_settings=file_secret_settings,
393393
) + (default_settings,)
394-
if not any([source for source in sources if isinstance(source, CliSettingsSource)]):
394+
custom_cli_sources = [source for source in sources if isinstance(source, CliSettingsSource)]
395+
if not any(custom_cli_sources):
395396
if isinstance(cli_settings_source, CliSettingsSource):
396397
sources = (cli_settings_source,) + sources
397398
elif cli_parse_args is not None:
@@ -414,6 +415,10 @@ def _settings_build_values(
414415
case_sensitive=case_sensitive,
415416
)
416417
sources = (cli_settings,) + sources
418+
# We ensure that if command line arguments haven't been parsed yet, we do so.
419+
elif cli_parse_args and not custom_cli_sources[0].env_vars:
420+
custom_cli_sources[0](args=cli_parse_args)
421+
417422
if sources:
418423
state: dict[str, Any] = {}
419424
states: dict[str, dict[str, Any]] = {}

tests/test_source_cli.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2531,6 +2531,33 @@ class Options(BaseSettings):
25312531
assert CliApp.run(Options, cli_args=['--nested.foo=5']).model_dump() == {'nested': {'foo': 5, 'bar': 2}}
25322532

25332533

2534+
def test_cli_parse_args_from_model_config_is_respected_with_settings_customise_sources(
2535+
monkeypatch: pytest.MonkeyPatch,
2536+
):
2537+
class MySettings(BaseSettings):
2538+
model_config = SettingsConfigDict(cli_parse_args=True)
2539+
2540+
foo: str
2541+
2542+
@classmethod
2543+
def settings_customise_sources(
2544+
cls,
2545+
settings_cls: type[BaseSettings],
2546+
init_settings: PydanticBaseSettingsSource,
2547+
env_settings: PydanticBaseSettingsSource,
2548+
dotenv_settings: PydanticBaseSettingsSource,
2549+
file_secret_settings: PydanticBaseSettingsSource,
2550+
) -> tuple[PydanticBaseSettingsSource, ...]:
2551+
return (CliSettingsSource(settings_cls),)
2552+
2553+
with monkeypatch.context() as m:
2554+
m.setattr(sys, 'argv', ['example.py', '--foo', 'bar'])
2555+
2556+
cfg = CliApp.run(MySettings)
2557+
2558+
assert cfg.model_dump() == {'foo': 'bar'}
2559+
2560+
25342561
def test_cli_shortcuts_on_flat_object():
25352562
class Settings(BaseSettings):
25362563
option: str = Field(default='foo')
@@ -2612,3 +2639,25 @@ class Cfg(BaseSettings):
26122639
serialized_cli_args = CliApp.serialize(cfg)
26132640
assert serialized_cli_args == ['0', '1', '2', '3', '4', '5']
26142641
assert CliApp.run(Cfg, cli_args=serialized_cli_args).model_dump() == cfg.model_dump()
2642+
2643+
2644+
def test_cli_app_with_separate_parser(monkeypatch):
2645+
class Cfg(BaseSettings):
2646+
model_config = SettingsConfigDict(cli_parse_args=True)
2647+
pet: Literal['dog', 'cat', 'bird']
2648+
2649+
parser = argparse.ArgumentParser()
2650+
2651+
# The actual parsing of command line argument should not happen here.
2652+
cli_settings = CliSettingsSource(Cfg, root_parser=parser)
2653+
2654+
parser.add_argument('-e', '--extra', dest='extra', default=0, action='count')
2655+
2656+
with monkeypatch.context() as m:
2657+
m.setattr(sys, 'argv', ['example.py', '--pet', 'dog', '-eeee'])
2658+
2659+
parsed_args = parser.parse_args()
2660+
2661+
assert parsed_args.extra == 4
2662+
# With parsed arguments passed to CliApp.run, the parser should not need to be called again.
2663+
assert CliApp.run(Cfg, cli_args=parsed_args, cli_settings_source=cli_settings).model_dump() == {'pet': 'dog'}

0 commit comments

Comments
 (0)