Pydantic

Why do we have multiple classes for the same model? For example:

from pydantic import BaseModel

class UserBase(BaseModel):
    name: str
    email: str

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int

    class Config:
        orm_mode = True

Here’s the response from copilot:

In Pydantic, we often define multiple schemas like UserBase, UserCreate, and User to handle different use cases and ensure clear separation of concerns. Here’s why we do this:

  • UserBase: This schema typically includes the common fields that are shared across different operations. It serves as a base class for other schemas.
  • UserCreate: This schema extends UserBase and includes additional fields required for creating a new user, such as password. It ensures that only the necessary fields are included when creating a user.
  • User: This schema extends UserBase and includes additional fields that are returned in responses, such as id. It ensures that sensitive information like password is not included in the response.

Pydantic Settings

Pydantic Settings provides optional Pydantic features for loading a settings or config class from environment variables or secrets files.

Basic Usage

Example:

# config.py
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    database_url: str
    openai_api_key: str

    model_config = SettingsConfigDict(env_file=".env")

def load_settings():
    return Settings()

Key points:

  • The Settings class has two fields database_url and openai_api_key
  • The model_config is a member of BaseSettings, and we can customize the behavior of pydantic_settings by updating its attributes
    • For example, we can set the value of _env_file to .env
  • The settings is an instance of the Settings class, and we can access the environment variable via settings.database_url
  • The naming convention of these variables are lower case

Unit Tests

Here’s a simple unit test:

# test_config.py
import os
from app.config import load_settings

def test_settings_loading_from_env():
    # Create a temporary .env file
    with open(".env", "w") as f:
        f.write("database_url=sqlite:///test.db\n")
        f.write("openai_api_key=secret-key\n")

    # Load settings
    settings = load_settings()
    print(settings)

    # Assert that settings are loaded correctly
    assert settings.database_url == "sqlite:///test.db"
    assert settings.openai_api_key == "secret-key"

    # Remove the temporary .env file
    os.remove(".env")