Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
37 changes: 37 additions & 0 deletions pydantic_settings/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import inspect
import threading
import warnings
from argparse import Namespace
from collections.abc import Mapping
from types import SimpleNamespace
Expand All @@ -24,10 +25,14 @@
DotenvType,
EnvSettingsSource,
InitSettingsSource,
JsonConfigSettingsSource,
PathType,
PydanticBaseSettingsSource,
PydanticModel,
PyprojectTomlConfigSettingsSource,
SecretsSettingsSource,
TomlConfigSettingsSource,
YamlConfigSettingsSource,
get_subcommand,
)

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

self._settings_warn_unused_config_keys(sources, self.model_config)

if sources:
state: dict[str, Any] = {}
states: dict[str, dict[str, Any]] = {}
Expand All @@ -436,6 +443,36 @@ def _settings_build_values(
# to an informative error and much better than a confusing error
return {}

@staticmethod
def _settings_warn_unused_config_keys(sources: tuple[object, ...], model_config: SettingsConfigDict) -> None:
"""
Warns if any values in model_config were set but the corresponding settings source has not been initialised.

The list alternative sources and their config keys can be found here:
https://docs.pydantic.dev/latest/concepts/pydantic_settings/#other-settings-source

Args:
sources: The tuple of configured sources
"""

def warn_if_not_used(source_type: type[PydanticBaseSettingsSource], keys: tuple[str, ...]) -> None:
if not any(isinstance(source, source_type) for source in sources):
for key in keys:
if model_config.get(key) is not None:
warnings.warn(
f'Config key `{key}` is set in model_config but will be ignored because no '
f'{source_type.__name__} source is configured. To use this config key, add a '
f'{source_type.__name__} source to the settings sources via the '
'settings_customise_sources hook.',
UserWarning,
stacklevel=3,
)

warn_if_not_used(JsonConfigSettingsSource, ('json_file', 'json_file_encoding'))
warn_if_not_used(PyprojectTomlConfigSettingsSource, ('pyproject_toml_depth', 'pyproject_toml_table_header'))
warn_if_not_used(TomlConfigSettingsSource, ('toml_file',))
warn_if_not_used(YamlConfigSettingsSource, ('yaml_file', 'yaml_file_encoding', 'yaml_config_section'))

model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
extra='forbid',
arbitrary_types_allowed=True,
Expand Down
30 changes: 30 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3132,3 +3132,33 @@ class Settings(BaseSettings):

s = Settings()
assert s.model_dump() == {'a': ['one', 'two']}


def test_warns_if_config_keys_are_set_but_source_is_missing():
config = SettingsConfigDict(
json_file='config.json',
pyproject_toml_depth=2,
toml_file='config.toml',
yaml_file='config.yaml',
yaml_config_section='myapp',
)

sources = () # No sources configured

with pytest.warns() as record:
BaseSettings._settings_warn_unused_config_keys(sources, config)

assert len(record) == 5

key_class_pairs = [
('json_file', 'JsonConfigSettingsSource'),
('pyproject_toml_depth', 'PyprojectTomlConfigSettingsSource'),
('toml_file', 'TomlConfigSettingsSource'),
('yaml_file', 'YamlConfigSettingsSource'),
('yaml_config_section', 'YamlConfigSettingsSource'),
]

for warning in record:
assert warning.category is UserWarning
assert any(key in warning.message.args[0] for key, _ in key_class_pairs)
assert any(cls in warning.message.args[0] for _, cls in key_class_pairs)
Loading