diff --git a/pydantic_settings/sources.py b/pydantic_settings/sources.py index 919f836..e0e0809 100644 --- a/pydantic_settings/sources.py +++ b/pydantic_settings/sources.py @@ -445,10 +445,12 @@ def _extract_field_info(self, field: FieldInfo, field_name: str) -> list[tuple[s ) else: # string validation alias field_info.append((v_alias, self._apply_case_sensitive(v_alias), False)) - elif origin_is_union(get_origin(field.annotation)) and _union_is_complex(field.annotation, field.metadata): - field_info.append((field_name, self._apply_case_sensitive(self.env_prefix + field_name), True)) - else: - field_info.append((field_name, self._apply_case_sensitive(self.env_prefix + field_name), False)) + + if not v_alias or self.config.get('populate_by_name', False): + if origin_is_union(get_origin(field.annotation)) and _union_is_complex(field.annotation, field.metadata): + field_info.append((field_name, self._apply_case_sensitive(self.env_prefix + field_name), True)) + else: + field_info.append((field_name, self._apply_case_sensitive(self.env_prefix + field_name), False)) return field_info diff --git a/tests/test_settings.py b/tests/test_settings.py index f7ea7c2..e6a1857 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -64,6 +64,12 @@ class SettingWithIgnoreEmpty(BaseSettings): model_config = SettingsConfigDict(env_ignore_empty=True) +class SettingWithPopulateByName(BaseSettings): + apple: str = Field('default', alias='pomo') + + model_config = SettingsConfigDict(populate_by_name=True) + + def test_sub_env(env): env.set('apple', 'hello') s = SimpleSettings() @@ -127,6 +133,110 @@ class Settings(BaseSettings): assert s.a == 'b' +def test_populate_by_name_when_using_alias(env): + env.set('pomo', 'bongusta') + s = SettingWithPopulateByName() + assert s.apple == 'bongusta' + + +def test_populate_by_name_when_using_name(env): + env.set('apple', 'honeycrisp') + s = SettingWithPopulateByName() + assert s.apple == 'honeycrisp' + + +def test_populate_by_name_when_using_both(env): + env.set('apple', 'honeycrisp') + env.set('pomo', 'bongusta') + s = SettingWithPopulateByName() + assert s.apple == 'bongusta', 'Expected alias value to be prioritized.' + + +def test_populate_by_name_with_alias_path_when_using_alias(env): + env.set('fruits', '["empire", "honeycrisp"]') + + class Settings(BaseSettings): + apple: str = Field('default', validation_alias=AliasPath('fruits', 0)) + model_config = SettingsConfigDict(populate_by_name=True) + + s = Settings() + assert s.apple == 'empire' + + +def test_populate_by_name_with_alias_path_when_using_name(env): + env.set('apple', 'jonathan gold') + + class Settings(BaseSettings): + apple: str = Field('default', validation_alias=AliasPath('fruits', 0)) + model_config = SettingsConfigDict(populate_by_name=True) + + s = Settings() + assert s.apple == 'jonathan gold' + + +@pytest.mark.parametrize( + 'env_vars, expected_value', + [ + pytest.param({'pomo': 'pomo-chosen'}, 'pomo-chosen', id='pomo'), + pytest.param({'pomme': 'pomme-chosen'}, 'pomme-chosen', id='pomme'), + pytest.param({'manzano': 'manzano-chosen'}, 'manzano-chosen', id='manzano'), + pytest.param( + {'pomo': 'pomo-chosen', 'pomme': 'pomme-chosen', 'manzano': 'manzano-chosen'}, + 'pomo-chosen', + id='pomo-priority', + ), + pytest.param({'pomme': 'pomme-chosen', 'manzano': 'manzano-chosen'}, 'pomme-chosen', id='pomme-priority'), + ], +) +def test_populate_by_name_with_alias_choices_when_using_alias(env, env_vars: Dict[str, str], expected_value: str): + for k, v in env_vars.items(): + env.set(k, v) + + class Settings(BaseSettings): + apple: str = Field('default', validation_alias=AliasChoices('pomo', 'pomme', 'manzano')) + model_config = SettingsConfigDict(populate_by_name=True) + + s = Settings() + assert s.apple == expected_value + + +def test_populate_by_name_with_dotenv_when_using_alias(tmp_path): + p = tmp_path / '.env' + p.write_text('pomo=bongusta') + + class Settings(BaseSettings): + apple: str = Field('default', alias='pomo') + model_config = SettingsConfigDict(env_file=p, populate_by_name=True) + + s = Settings() + assert s.apple == 'bongusta' + + +def test_populate_by_name_with_dotenv_when_using_name(tmp_path): + p = tmp_path / '.env' + p.write_text('apple=honeycrisp') + + class Settings(BaseSettings): + apple: str = Field('default', alias='pomo') + model_config = SettingsConfigDict(env_file=p, populate_by_name=True) + + s = Settings() + assert s.apple == 'honeycrisp' + + +def test_populate_by_name_with_dotenv_when_using_both(tmp_path): + p = tmp_path / '.env' + p.write_text('apple=honeycrisp') + p.write_text('pomo=bongusta') + + class Settings(BaseSettings): + apple: str = Field('default', alias='pomo') + model_config = SettingsConfigDict(env_file=p, populate_by_name=True) + + s = Settings() + assert s.apple == 'bongusta', 'Expected alias value to be prioritized.' + + def test_with_prefix(env): class Settings(BaseSettings): apple: str