Skip to Content
DocumentationFundamentalsConfigurationOverview

Configuration Models

Django-CFG uses Pydantic v2 models for type-safe, validated configuration.

Why Type-Safe Configuration? Type-safe configuration catches errors at startup instead of runtime, provides IDE autocomplete, and enables static type checking with mypy/pyright.

Quick Start

Python Config

config.py
from django_cfg import DjangoConfig from pydantic import BaseModel class DatabaseConfig(BaseModel): url: str = "sqlite:///db/default.sqlite3" class MyProjectConfig(DjangoConfig): # Core settings secret_key: str = "your-secret-key-here" debug: bool = True project_name: str = "My Project" # Database database: DatabaseConfig = DatabaseConfig() # Services (optional) # email: EmailConfig = EmailConfig() # telegram: TelegramConfig = TelegramConfig()

YAML Config

config.yaml
secret_key: "dev-secret-key" debug: true project_name: "My Project" database: url: "sqlite:///db/default.sqlite3" # Optional services # email: # backend: "console" # telegram: # bot_token: "your-token"
config.py
from django_cfg import DjangoConfig, load_config # Load from YAML config = load_config() # Reads config.yaml

Configuration Topics

Core Configuration

Infrastructure

  • Database - Database configuration
  • Cache - ✨ Auto Redis cache from redis_url (Redis, LocMem, Dummy)
  • Logging - Logging configuration

Deployment

Services

Django Integrations

  • Admin & UI - Unfold admin interface
  • API - DRF, Spectacular, JWT
  • Payments - Payment providers (NowPayments, Stripe)

Key Features

Type Safety

Automatic Type Validation Pydantic validates all types automatically at instantiation time, preventing runtime errors.

# Pydantic validates types automatically class MyConfig(DjangoConfig): debug: bool = True max_connections: int = 100 timeout: float = 30.0 # ✅ This works config = MyConfig(debug=True, max_connections=50) # ❌ This fails with validation error config = MyConfig(debug="yes", max_connections="fifty")

See validation error output

ValidationError: 2 validation errors for MyConfig debug Input should be a valid boolean [type=bool_type] max_connections Input should be a valid integer [type=int_type]

Environment Variables

Automatic Loading

from pydantic_settings import BaseSettings class DatabaseConfig(BaseSettings): url: str = "sqlite:///db/default.sqlite3" class Config: env_prefix = "DATABASE_" # Automatically reads DATABASE_URL from environment config = DatabaseConfig()

Manual Loading

import os class DatabaseConfig(BaseModel): url: str = os.getenv("DATABASE_URL", "sqlite:///db/default.sqlite3")

Environment Variables in Production Always use environment variables for secrets (API keys, passwords) in production. Never commit them to version control.

YAML Configuration

Basic YAML

config.yaml
secret_key: "dev-secret-key" debug: true project_name: "My Project" database: url: "postgresql://localhost/mydb" email: backend: "console"

With Environment Variables

config.yaml
# Use ${VAR_NAME} for environment variable substitution secret_key: "${SECRET_KEY}" debug: false project_name: "My Project" database: url: "${DATABASE_URL}" email: host: "${EMAIL_HOST}" password: "${EMAIL_PASSWORD}"
config.py
# Load from YAML from django_cfg import load_config config = load_config() # Reads config.yaml

YAML Best Practice Keep sensitive values in environment variables and reference them in YAML with ${VAR_NAME} syntax.

Validation

from pydantic import Field, field_validator class MyConfig(DjangoConfig): secret_key: str = Field(..., min_length=50) max_connections: int = Field(default=100, ge=1, le=1000) @field_validator('secret_key') def validate_secret_key(cls, v): if v == "changeme": raise ValueError("Please change the default secret key") return v

Pydantic Validators

Use field_validator for custom validation logic. Validators run automatically during configuration instantiation.

Configuration Patterns

Development vs Production

Using Properties

config.py
from django_cfg import DjangoConfig, Environment class MyConfig(DjangoConfig): @property def debug(self) -> bool: return Environment.current() == Environment.DEVELOPMENT @property def database_url(self) -> str: if Environment.is_production(): return "postgresql://prod-db/main" return "sqlite:///db/dev.sqlite3"

Separate Configs

config.py
class BaseConfig(DjangoConfig): project_name: str = "My Project" class DevConfig(BaseConfig): debug: bool = True database_url: str = "sqlite:///dev.db" class ProdConfig(BaseConfig): debug: bool = False database_url: str = "postgresql://prod-db/main" # Select based on environment import os config = ProdConfig() if os.getenv("ENV") == "production" else DevConfig()

Nested Configuration

class DatabaseConfig(BaseModel): url: str pool_size: int = 5 class CacheConfig(BaseModel): backend: str = "redis" location: str = "redis://localhost:6379/1" class MyConfig(DjangoConfig): database: DatabaseConfig cache: CacheConfig

Organize Complex Configs Use nested Pydantic models to organize complex configurations into logical groups.

Optional Features

class MyConfig(DjangoConfig): # Optional email configuration email: Optional[EmailConfig] = None # Enable optional features enable_telegram: bool = False enable_payments: bool = False

Example: Conditional Feature Enabling

class MyConfig(DjangoConfig): # Enable features based on environment enable_telegram: bool = Field( default_factory=lambda: os.getenv("ENABLE_TELEGRAM", "false") == "true" ) enable_payments: bool = Field( default_factory=lambda: os.getenv("ENABLE_PAYMENTS", "false") == "true" )

See Also

Configuration Deep Dive

Docker Deployment