Skip to content

Commit 1967d6f

Browse files
authored
Warn if model_config sets unused keys for missing settings sources (#663)
1 parent 1fc2087 commit 1967d6f

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

pydantic_settings/main.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
import inspect
55
import threading
6+
import warnings
67
from argparse import Namespace
78
from collections.abc import Mapping
89
from types import SimpleNamespace
@@ -24,10 +25,14 @@
2425
DotenvType,
2526
EnvSettingsSource,
2627
InitSettingsSource,
28+
JsonConfigSettingsSource,
2729
PathType,
2830
PydanticBaseSettingsSource,
2931
PydanticModel,
32+
PyprojectTomlConfigSettingsSource,
3033
SecretsSettingsSource,
34+
TomlConfigSettingsSource,
35+
YamlConfigSettingsSource,
3136
get_subcommand,
3237
)
3338

@@ -417,6 +422,8 @@ def _settings_build_values(
417422
elif cli_parse_args not in (None, False) and not custom_cli_sources[0].env_vars:
418423
custom_cli_sources[0](args=cli_parse_args) # type: ignore
419424

425+
self._settings_warn_unused_config_keys(sources, self.model_config)
426+
420427
if sources:
421428
state: dict[str, Any] = {}
422429
states: dict[str, dict[str, Any]] = {}
@@ -436,6 +443,37 @@ def _settings_build_values(
436443
# to an informative error and much better than a confusing error
437444
return {}
438445

446+
@staticmethod
447+
def _settings_warn_unused_config_keys(sources: tuple[object, ...], model_config: SettingsConfigDict) -> None:
448+
"""
449+
Warns if any values in model_config were set but the corresponding settings source has not been initialised.
450+
451+
The list alternative sources and their config keys can be found here:
452+
https://docs.pydantic.dev/latest/concepts/pydantic_settings/#other-settings-source
453+
454+
Args:
455+
sources: The tuple of configured sources
456+
model_config: The model config to check for unused config keys
457+
"""
458+
459+
def warn_if_not_used(source_type: type[PydanticBaseSettingsSource], keys: tuple[str, ...]) -> None:
460+
if not any(isinstance(source, source_type) for source in sources):
461+
for key in keys:
462+
if model_config.get(key) is not None:
463+
warnings.warn(
464+
f'Config key `{key}` is set in model_config but will be ignored because no '
465+
f'{source_type.__name__} source is configured. To use this config key, add a '
466+
f'{source_type.__name__} source to the settings sources via the '
467+
'settings_customise_sources hook.',
468+
UserWarning,
469+
stacklevel=3,
470+
)
471+
472+
warn_if_not_used(JsonConfigSettingsSource, ('json_file', 'json_file_encoding'))
473+
warn_if_not_used(PyprojectTomlConfigSettingsSource, ('pyproject_toml_depth', 'pyproject_toml_table_header'))
474+
warn_if_not_used(TomlConfigSettingsSource, ('toml_file',))
475+
warn_if_not_used(YamlConfigSettingsSource, ('yaml_file', 'yaml_file_encoding', 'yaml_config_section'))
476+
439477
model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
440478
extra='forbid',
441479
arbitrary_types_allowed=True,

tests/test_settings.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3132,3 +3132,36 @@ class Settings(BaseSettings):
31323132

31333133
s = Settings()
31343134
assert s.model_dump() == {'a': ['one', 'two']}
3135+
3136+
3137+
def test_warns_if_config_keys_are_set_but_source_is_missing():
3138+
class Settings(BaseSettings):
3139+
model_config = SettingsConfigDict(
3140+
json_file='config.json',
3141+
pyproject_toml_depth=2,
3142+
toml_file='config.toml',
3143+
yaml_file='config.yaml',
3144+
yaml_config_section='myapp',
3145+
)
3146+
3147+
with pytest.warns() as record:
3148+
Settings()
3149+
3150+
assert len(record) == 5
3151+
3152+
key_class_pairs = [
3153+
('json_file', 'JsonConfigSettingsSource'),
3154+
('pyproject_toml_depth', 'PyprojectTomlConfigSettingsSource'),
3155+
('toml_file', 'TomlConfigSettingsSource'),
3156+
('yaml_file', 'YamlConfigSettingsSource'),
3157+
('yaml_config_section', 'YamlConfigSettingsSource'),
3158+
]
3159+
3160+
for warning, key_class_pair in zip(record, key_class_pairs):
3161+
assert warning.category is UserWarning
3162+
expected_message = (
3163+
f'Config key `{key_class_pair[0]}` is set in model_config but will be ignored because no '
3164+
f'{key_class_pair[1]} source is configured. To use this config key, add a {key_class_pair[1]} '
3165+
f'source to the settings sources via the settings_customise_sources hook.'
3166+
)
3167+
assert warning.message.args[0] == expected_message

0 commit comments

Comments
 (0)