pydantic / pydantic-settings

Settings management using pydantic
https://docs.pydantic.dev/latest/usage/pydantic_settings/
MIT License
502 stars 47 forks source link

Env does not override nested value when other sources are used #320

Closed rusmux closed 6 days ago

rusmux commented 2 weeks ago

In this example, If I use YAML source, I cannot override sub_value field with an environment variable.

In main.py:

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

class PydanticBaseSettings(BaseSettings):

    @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,
                YamlConfigSettingsSource(settings_cls), file_secret_settings)

class SubSettings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix="sub_")
    value: str

class Settings(PydanticBaseSettings):
    model_config = SettingsConfigDict(yaml_file="config.yaml")

    value: str
    sub: SubSettings = Field(default_factory=SubSettings)

print(Settings())

In config.yaml:

value: a

sub:
  value: a

In terminal: value=b sub_value=b python -m main

Gives:

value='b' sub=SubSettings(value='a')

The first value is overridden, but the second is not. If I do not use YAML source everything works as expected. I tried using PydanticBaseSettings for SubSettings, but it doesn't help.

hramezani commented 1 week ago

Thanks @rusmux for reporting this.

Your example code has some problems:

So, at the end you can collect values like you want by changing your code to:

from pydantic import Field, BaseModel
from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
    YamlConfigSettingsSource,
)

class PydanticBaseSettings(BaseSettings):

    @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,
                YamlConfigSettingsSource(settings_cls), file_secret_settings)

class SubSettings(BaseModel):
    value: str

class Settings(PydanticBaseSettings):
    model_config = SettingsConfigDict(yaml_file="config.yaml", env_nested_delimiter="__")

    value: str
    sub: SubSettings

print(Settings())

You need to call your script by value=b sub__value=c python ...

rusmux commented 6 days ago

I see, thank you!