pythonpydantic

Combined Pydantic settings class from multiple settings classes with individual environment files


Let's say I have 2 simple BaseSettings classes that, each, load values from their individual environment files. Let's also say that there is a combined settings class that is based on the first 2 classes.

Settings = DatabaseSettigns + AuthSettings

How can I create the combined settings class with a flat namespace without having to do dynamic class creation or dynamic attribute value assignments, i.e., I want my IDE to be able to do auto-complete, etc by knowing what the attributes are.

When I use the example below, I ONLY get the values from the LAST class, i.e, AuthSettings, instead of BOTH of them.

How can I have it such that ALL component settings classes are properly read / initialized from their respective files?

import logging
log = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO,
                    format='%(message)s')

from pydantic import BaseModel
from pydantic_settings import BaseSettings, SettingsConfigDict

class DatabaseSettings(BaseSettings):
  model_config = SettingsConfigDict(
    env_prefix="APP_",
    env_file=".env.database",
    env_file_encoding="utf-8",
    extra="ignore",
  )
  db_host: str = "localhost"

class AuthSettings(BaseSettings):
  model_config = SettingsConfigDict(
    env_prefix="APP_",
    env_file=".env.auth",
    env_file_encoding="utf-8",
    extra="ignore",
  )
  auth_secret_key: str = "change-me"

class Settings(DatabaseSettings, AuthSettings):
  model_config = SettingsConfigDict(
    env_prefix='APP_',
    extra='ignore'
  )
  pass

settings = Settings()
log.info(f"Settings: {settings}")

# .env.database
APP_DB_HOST=db.example.com
# .env.auth
APP_AUTH_SECRET_KEY=secret-from-env-file

Current Result

Settings: auth_secret_key='secret-from-env-file' db_host='localhost'

Desired Result

Settings: auth_secret_key='secret-from-env-file' db_host='db.example.com'
# notice how the db_host value is the one read from the file instead of the default "localhost"

Solution

  • You can use composition instead of inheritance, which is more explicit, avoids conflicts between multiple BaseSettings classes, and preserves IDE auto-complete. It works like this:

    class Settings(BaseModel):  # Doesn't inherit BaseSettings
        database: DatabaseSettings = DatabaseSettings()
        auth: AuthSettings = AuthSettings()
    

    Why prefer composition?