Question: I have an app that is largely configured by environment variables. I use Pydantic Settings to define the settings available, and validate them. I have an initial set of settings, and the regular app settings. The initial settings are ones that should not fail validation, and contain essential settings for starting the app. For example, when my app starts up, if the regular Settings() can't be initialized because something in them failed validation, I still want to be able to send the error to Sentry. For that, I need SENTRY_DSN to configure Sentry. SENTRY_DSN can't be part of the regular settings, because if something unrelated in Settings fails validation, I won't have access to SENTRY_DNS either. Right now, my settings look like this: class InitialSettings(BaseSettings): model_config = SettingsConfigDict( env_file="settings.env", env_file_encoding="utf-8", extra="ignore", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) SENTRY_DSN: Annotated[ Optional[str], Field(None), ] class Settings(BaseSettings): model_config = SettingsConfigDict( env_file="settings.env", env_file_encoding="utf-8", extra="ignore", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) STORAGE: Annotated[ LocalStorageSettings | S3StorageSettings, Field(..., discriminator="STORAGE_TYPE"), ] DEBUG: Annotated[DebugSettings, Field(default_factory=DebugSettings)] ...  This works. When my app starts up, I first initialize InitialSettings(), and then try to initialize Settings(). If Settings() fails, I can still use the SENTRY_DSN setting to send the error to Sentry. The issues comes when I try to have both settings use the same env file (settings.env), AND enable the extra="forbid" feature on Settings(). I like the idea of having extra="forbid" enabled, but that also means that if I enable it on Settings(), it will always fail, because the env file will contain an entry for SENTRY_DSN, which Settings doesn't know about. To fix, this I tried to add InitialSettings to Settings like this: class Settings(BaseSettings): model_config = SettingsConfigDict( env_file="settings.env", env_file_encoding="utf-8", extra="forbid", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) STORAGE: Annotated[ LocalStorageSettings | S3StorageSettings, Field(..., discriminator="STORAGE_TYPE"), ] DEBUG: Annotated[DebugSettings, Field(default_factory=DebugSettings)] INIT: Annotated[InitialSettings, Field(default_factory=InitialSettings)] ...  Now Settings should know about all the settings defined in InitialSettings, and if there's any extra settings in the env file that aren't defined in either class, it should fail. This almost works. The problem is that when you call InitialSettings, the SENTRY_DSN in the env file is expected to just be called SENTRY_DSN. When you call InitialSettings, because InitialSettings is nested under INIT, it expects the sentry variable to be called INIT__SENTRY_DSN. How do I configure Pydantic Settings so that all settings under InitialSettings always look for SENTRY_DSN, no matter if they are initialized using InitialSettings(), or Settings()? Note: I still want the other nested settings classes under Settings, like STORAGE, to work the same - be prefixed with STORAGE__ in the env file.

Answer: The simplest solution is to use two different .env files: one for InitialSettings and one for Settings: # initial.env SENTRY_DSN=...  # settings.env # your other `Settings` envs here without `SENTRY_DSN`  class InitialSettings(BaseSettings): model_config = SettingsConfigDict( env_file="initial.env", env_file_encoding="utf-8", extra="ignore", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) SENTRY_DSN: Annotated[ Optional[str], Field(None), ] class Settings(CustomSettings): model_config = SettingsConfigDict( env_file="settings.env", env_file_encoding="utf-8", extra="forbid", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) STORAGE: Annotated[ LocalStorageSettings | S3StorageSettings, Field(..., discriminator="STORAGE_TYPE"), ] DEBUG: Annotated[DebugSettings, Field(default_factory=DebugSettings)] INIT: Annotated[InitialSettings, Field(default_factory=InitialSettings)] ...  Using two separate files with different environment variables in your case seems logical. You have two independent classes of settings, with different extra settings, so different validation will be performed. In Settings extra environment variables will throw a validation error, while InitialSettings will not. But, most importantly, using a separate file with environment variables will allow InitialSettings to initialize correctly both inside the Settings class and separately.  Further, I'm assuming that you can't use multiple .env files, for whatever reason, and that you plan to leave the setting - extra=“forbid” in Settings. Based on this, I can suggest two solutions. The first solution is to use the INIT prefix in your settings.env for SENTRY_DSN. This would look something like this: # setting.env INIT__SENTRY_DSN=... # other envs  Then your settings classes will look like this: class InitialSettings(BaseSettings): model_config = SettingsConfigDict( env_prefix="INIT__", env_file="settings.env", env_file_encoding="utf-8", extra="ignore", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) SENTRY_DSN: Annotated[ Optional[str], Field(None), ] class Settings(BaseSettings): model_config = SettingsConfigDict( env_file="settings.env", env_file_encoding="utf-8", extra="forbid", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) STORAGE: Annotated[ LocalStorageSettings | S3StorageSettings, Field(..., discriminator="STORAGE_TYPE"), ] DEBUG: Annotated[DebugSettings, Field(default_factory=DebugSettings)] INIT: Annotated[InitialSettings, Field(default_factory=InitialSettings)] ...  In this case, because we set SettingsConfigDict(env_prefix=“INIT__”, ...) in InitialSettings, both classes of settings: will look for one environment variable INIT__SENTRY_DSN in the settings.env file. Both classes will properly initialize SENTRY_DSN from INIT__SENTRY_DSN.  Another possible solution could be to override settings_customize_sources by creating a subclass of BaseSettings and add its own DotEnvSettingsSource handler: # setting.env SENTRY_DSN=... # other envs  from typing import Annotated, Optional from pydantic import Field from pydantic_settings import ( BaseSettings, DotEnvSettingsSource, PydanticBaseSettingsSource, SettingsConfigDict, ) class CustomDotEnvSettingsSource(DotEnvSettingsSource): def __call__(self): # This method may actually look simpler, since `InitialSettings` # can be initialized using an `settings.env` file # (but I like the option below better, it's more explicit): # def __call__(self): # result = super().__call__() # result.pop('SENTRY_DSN', None) # return result result = super().__call__() if 'SENTRY_DSN' in result: value = result.pop('SENTRY_DSN') result.setdefault('INIT', {})['SENTRY_DSN'] = value return result class CustomSettings(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 super().settings_customise_sources( settings_cls=settings_cls, init_settings=init_settings, env_settings=env_settings, # register custom `DotEnvSettingsSource` handler dotenv_settings=CustomDotEnvSettingsSource(settings_cls), file_secret_settings=file_secret_settings, ) class InitialSettings(BaseSettings): model_config = SettingsConfigDict( env_file="settings.env", env_file_encoding="utf-8", extra="ignore", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) SENTRY_DSN: Annotated[ Optional[str], Field(None), ] class Settings(CustomSettings): model_config = SettingsConfigDict( env_file="settings.env", env_file_encoding="utf-8", extra="forbid", env_ignore_empty=True, env_nested_delimiter="__", case_sensitive=True, ) STORAGE: Annotated[ LocalStorageSettings | S3StorageSettings, Field(..., discriminator="STORAGE_TYPE"), ] DEBUG: Annotated[DebugSettings, Field(default_factory=DebugSettings)] INIT: Annotated[InitialSettings, Field(default_factory=InitialSettings)] ...  All three methods described in this answer have been tested and should work for you.