Reputation: 351
I am new to Pydantic and I am trying to use pydantic-settings to load my .env file.
Below is the code:
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
# Use BaseSettings instead of BaseModel for UserConfig
class UserConfig(BaseSettings):
user: str = Field(..., env="MY_USER")
user_pwd: str = Field(..., env="MY_USER_PWD")
# Explicitly specify config to load environment
model_config = SettingsConfigDict(env_file=".env.local")
class AppConfig(BaseSettings):
log_level: str = Field(..., env="LOG_LEVEL")
log_file: str = Field(..., env="LOG_FILE")
# Field for nested settings with default_factory
user_config: UserConfig = Field(default_factory=UserConfig)
model_config = SettingsConfigDict(env_file='.env.local')
if __name__ == "__main__":
config = AppConfig()
print(config.dict())
And below are the entries in my .env.local file:
MY_USER=abcd
MY_USER_PWD=system
LOG_LEVEL=INFO
LOG_FILE=app
However, I am not able to load the below variable (nested one):
user_config: UserConfig = Field(default_factory=UserConfig)
If I comment this and remove the corresponding key-value from .env file, it works fine.
Seems like, I am missing something here or it might be an pydantic-settings module issue. It might not work well in nested case. I am using 2.6.0 version of pydantic-settings
My goal was to declare the env file and different Pydantic model wrapped in AppConfig.
Could anyone help me here?
Thanks in advance.
Upvotes: 0
Views: 450
Reputation: 423
I see what you're trying to do, but you should keep in mind that .env
files have no substructures. So in order to create such a structure for you application, you'd have to do some extra work, to split the variables onto different classes.
Some further notes:
alias
instead of env
inside the field definitions. It's more "pydantic".BaseSettings
. The others can derive from BaseModel
..dict()
is from pydantic v1. Use .model_dump
instead.A mwe could look like this:
from pydantic import Field, BaseModel, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class UserConfig(BaseModel):
user: str = Field(..., alias="MY_USER")
user_pwd: str = Field(..., alias="MY_USER_PWD")
class LoggingConfig(BaseModel):
log_level: str = Field(..., alias="LOG_LEVEL")
log_file: str = Field(..., alias="LOG_FILE")
class AppConfig(BaseSettings):
user_config: UserConfig
logging_config: LoggingConfig
model_config = SettingsConfigDict(env_file=".env.local", case_sensitive=True)
@model_validator(mode="before")
@classmethod
def create_sub_structure(cls, values):
return {
"user_config": values,
"logging_config": values,
}
if __name__ == "__main__":
config = AppConfig()
print(config.model_dump(mode="json"))
You can simply put all values inside the sub-configs because pydantic will ignore any extra fields in this case. You need case_sensitive=True
here, because otherwise the field_names will be transformed to lowercase by the pydantic-settings
framework which will result in errors in the sub-configs.
Upvotes: 0