amu61
amu61

Reputation: 351

Pydatic-Settings 2.6.0 - declare the env file and different Pydantic model wrapped in AppConfig

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

Answers (1)

lord_haffi
lord_haffi

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:

  • You can use alias instead of env inside the field definitions. It's more "pydantic".
  • Only the top class, the class loading the environment, needs to inherit from 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

Related Questions