Skip to content

Allow env vars to be use global alias scoped within nested configs #714

@inickt

Description

@inickt

We have a case where we would like to leverage validation_alias in our configs to be the value of the environment variable it reads from, ignoring any nesting for the configuration object.

Similar request threads:
pydantic/pydantic#8989
pydantic/pydantic#8221

Originally this issue was filed as a regression with 2.12, but this was due to me using 2.11 incorrectly, so keeping the original issue below:


Hi! We seem to be hitting a regression with config overrides when updated from 2.11 to 2.12. We have a few layers of config sources that we use to build our config objects.

We set our order to init > env > dotenv > multiple toml files.

We also use validation_alias to make our env variables more explicit (especially when deeply nested). So we expect our env/dotenv sources to use the validation_alias, but all other configs would use the normal field name.

For the regression, if a value is set in a nested object (top level seems to work as expected) in our toml source, we are now unable to override it with environment variables. However, init overrides still work. This leads me to think it is a regression in #688 for fixing #180 (fyi @chbndrhnns @hramezani).

The following is a simple repro for the regression:

from typing import Annotated, ClassVar, override

from pydantic import Field
from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
    TomlConfigSettingsSource,
)

with open(".env", "w") as f:
    _ = f.write("""\
CONFIG_API_KEY=dotenv-api-key
""")

with open("config.toml", "w") as f:
    _ = f.write("""\
[api]
api_key = "api-key-from-toml"
""")


class ApiConfig(BaseSettings):
    model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
        env_file=".env",
        populate_by_name=True,
        extra="ignore",
    )

    api_key: Annotated[str, Field(validation_alias="CONFIG_API_KEY")]


class Config(BaseSettings):
    model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
        env_file=".env",
        populate_by_name=True,
        extra="ignore",
    )

    api: ApiConfig

    @override
    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (
            init_settings,
            env_settings,
            dotenv_settings,
            TomlConfigSettingsSource(settings_cls, "config.toml"),
        )


print(Config())
# 2.11: api=ApiConfig(api_key='dotenv-api-key') [correct]
# 2.12: api=ApiConfig(api_key='api-key-from-toml') [wrong]

print(Config(api=ApiConfig(api_key="init-api")))
# 2.11: api=ApiConfig(api_key='api-key-from-toml') [wrong]
# 2.12: api=ApiConfig(api_key='init-api') [correct]

Thanks for the help!

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions