Description
CliApp.run() does not load the env_file specified in model_config. Direct instantiation of the same BaseSettings subclass correctly loads the .env file.
Root Cause
_settings_init_sources defaults _env_file to None, while BaseSettings.__init__ defaults it to ENV_FILE_SENTINEL.
The guard on line 311 of main.py:
env_file = _env_file if _env_file != ENV_FILE_SENTINEL else cls.model_config.get('env_file')
only falls through to model_config when _env_file == ENV_FILE_SENTINEL. Since _settings_init_sources defaults to None, and None != ENV_FILE_SENTINEL (Path('')) is True, the model_config value is never used.
CliApp.run() calls _settings_init_sources(**model_init_data) without including _env_file, so the method receives None and skips the .env file entirely.
Call chain
CliApp.run(MySettings, cli_args) builds model_init_data with only _cli_parse_args, _cli_exit_on_error, _cli_settings_source — no _env_file.
model_cls._settings_init_sources(**model_init_data) is called; _env_file falls back to its default None.
None != ENV_FILE_SENTINEL → True → env_file = None.
DotEnvSettingsSource(env_file=None) → no dotenv file loaded.
Minimal Reproducible Example
# .env file contains: APP_NAME=from_dotenv
from pydantic_settings import BaseSettings, CliApp, SettingsConfigDict
class MySettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="APP_", env_file=".env")
name: str = "default"
# Works: loads .env
s1 = MySettings()
assert s1.name == "from_dotenv"
# Bug: ignores .env, name stays "default"
s2 = CliApp.run(MySettings, [])
assert s2.name == "from_dotenv" # FAILS
Suggested Fix
Change the default of _env_file in _settings_init_sources from None to ENV_FILE_SENTINEL:
@classmethod
def _settings_init_sources(
cls,
...
- _env_file: DotenvType | None = None,
+ _env_file: DotenvType | None = ENV_FILE_SENTINEL,
...
)
This aligns the behavior with BaseSettings.__init__ where the default is already ENV_FILE_SENTINEL.
Versions
- pydantic-settings: 2.13.1
- pydantic: 2.13.0
- Python: 3.13.9
Description
CliApp.run()does not load theenv_filespecified inmodel_config. Direct instantiation of the sameBaseSettingssubclass correctly loads the.envfile.Root Cause
_settings_init_sourcesdefaults_env_filetoNone, whileBaseSettings.__init__defaults it toENV_FILE_SENTINEL.The guard on line 311 of
main.py:only falls through to
model_configwhen_env_file == ENV_FILE_SENTINEL. Since_settings_init_sourcesdefaults toNone, andNone != ENV_FILE_SENTINEL(Path('')) isTrue, themodel_configvalue is never used.CliApp.run()calls_settings_init_sources(**model_init_data)without including_env_file, so the method receivesNoneand skips the.envfile entirely.Call chain
CliApp.run(MySettings, cli_args)buildsmodel_init_datawith only_cli_parse_args,_cli_exit_on_error,_cli_settings_source— no_env_file.model_cls._settings_init_sources(**model_init_data)is called;_env_filefalls back to its defaultNone.None != ENV_FILE_SENTINEL→True→env_file = None.DotEnvSettingsSource(env_file=None)→ no dotenv file loaded.Minimal Reproducible Example
Suggested Fix
Change the default of
_env_filein_settings_init_sourcesfromNonetoENV_FILE_SENTINEL:@classmethod def _settings_init_sources( cls, ... - _env_file: DotenvType | None = None, + _env_file: DotenvType | None = ENV_FILE_SENTINEL, ... )This aligns the behavior with
BaseSettings.__init__where the default is alreadyENV_FILE_SENTINEL.Versions