pydantic / pydantic-settings

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

2.6.0 breaks sub-modules inheriting from `BaseSettings` for independent environment variables #449

Open rra opened 1 week ago

rra commented 1 week ago

We are (in a pretty large number of places) intentionally using the pre-2.6 behavior of submodels that inherit from BaseSettings instead of BaseModel as a way to use environment variables to configure those portions of the settings directly without having to use the more complex convention for environment variables that specify the full path down to the field. This allows us to use standardized environment variables for that specific block of settings, regardless of what application settings they're embedded in.

For example, this code works as desired with pydantic-settings 2.5.2, but breaks with 2.6.0:

import os

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict

class GitHubConfig(BaseSettings):
    client_id: str
    client_secret: str = Field(validation_alias="GITHUB_CLIENT_SECRET")

class Config(BaseSettings):
    realm: str
    github: GitHubConfig

    model_config = SettingsConfigDict(extra="forbid", env_prefix="APP_")

os.environ["GITHUB_CLIENT_SECRET"] = "some-secret"
os.environ["APP_REALM"] = "example.com"
config = Config.model_validate({"github": {"client_id": "client-id"}})
print(config.model_dump())
# {'realm': 'example.com', 'github': {'client_id': 'client-id', 'client_secret': 'some-secret'}}

With pydantic-settings 2.6.0, it produces the following error:

pydantic_core._pydantic_core.ValidationError: 2 validation errors for Config
realm
  Field required [type=missing, input_value={'github': {'client_id': 'client-id'}}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing
github.GITHUB_CLIENT_SECRET
  Field required [type=missing, input_value={'client_id': 'client-id'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing

We use this configuration pattern very heavily for Kubernetes applications, since most of the application configuration is injected from the values.yaml file as YAML, but secrets, which have to come from Kubernetes secrets, are then injected via environment variables.

I think this may partially duplicate other issues (specifically #447 and #445), but the descriptions there aren't quite the same and involve more complex functionality that we weren't using, so I thought the separate report may still be useful.

rra commented 1 week ago

I should have added explicitly that this doesn't work in pydantic-settings 2.5.2 if, in the example, GitHubConfig inherits from BaseModel instead. That results in the following error:

github.GITHUB_CLIENT_SECRET
  Field required [type=missing, input_value={'client_id': 'client-id'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing