From a6f6fa45454042b99f85b6de0e6a54ab6489a070 Mon Sep 17 00:00:00 2001 From: jithin Date: Thu, 25 Jan 2024 20:37:53 +0530 Subject: [PATCH] Ensure extra='forbid' is enforced in DotEnvSettingsSource when env_prefix is specified (#218) --- pydantic_settings/sources.py | 7 +++++- tests/test_settings.py | 49 +++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/pydantic_settings/sources.py b/pydantic_settings/sources.py index 335ba9dd..9c43e9a4 100644 --- a/pydantic_settings/sources.py +++ b/pydantic_settings/sources.py @@ -640,12 +640,17 @@ def __call__(self) -> dict[str, Any]: data: dict[str, Any] = super().__call__() data_lower_keys: list[str] = [] + is_extra_allowed = self.config.get('extra') != 'forbid' if not self.case_sensitive: data_lower_keys = [x.lower() for x in data.keys()] - # As `extra` config is allowed in dotenv settings source, We have to # update data with extra env variabels from dotenv file. for env_name, env_value in self.env_vars.items(): + if not is_extra_allowed and not env_name.startswith(self.env_prefix): + raise SettingsError( + "unable to load environment variables from dotenv file " + f"due to the presence of variables without the specified prefix - '{self.env_prefix}'" + ) if env_name.startswith(self.env_prefix) and env_value is not None: env_name_without_prefix = env_name[self.env_prefix_len :] first_key, *_ = env_name_without_prefix.split(self.env_nested_delimiter) diff --git a/tests/test_settings.py b/tests/test_settings.py index b2371953..4d8e6583 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -767,7 +767,6 @@ class Settings(BaseSettings): prefix_b='better string' prefix_c="best string" -f="random value" """ @@ -790,6 +789,54 @@ class Settings(BaseSettings): assert s.c == 'best string' +prefix_test_env_invalid_file = """\ +# this is a comment +prefix_A=good string +# another one, followed by whitespace + +prefix_b='better string' +prefix_c="best string" +f="random value" +""" + + +def test_env_file_with_env_prefix_invalid(tmp_path): + p = tmp_path / '.env' + p.write_text(prefix_test_env_invalid_file) + + class Settings(BaseSettings): + a: str + b: str + c: str + + model_config = SettingsConfigDict(env_file=p, env_prefix='prefix_') + + err_msg = ( + "unable to load environment variables from dotenv file " + "due to the presence of variables without the specified prefix - 'prefix_'" + ) + with pytest.raises(SettingsError, match=err_msg): + Settings() + + +def test_ignore_env_file_with_env_prefix_invalid(tmp_path): + p = tmp_path / '.env' + p.write_text(prefix_test_env_invalid_file) + + class Settings(BaseSettings): + a: str + b: str + c: str + + model_config = SettingsConfigDict(env_file=p, env_prefix='prefix_', extra='ignore') + + s = Settings() + + assert s.a == 'good string' + assert s.b == 'better string' + assert s.c == 'best string' + + def test_env_file_config_case_sensitive(tmp_path): p = tmp_path / '.env' p.write_text(test_env_file)