Skip to content

Configuration Concepts

Understand how DBWarden configuration works under the hood.

What is Configuration?

Configuration tells DBWarden: - Where your databases are (connection URLs) - What kind of databases they are (PostgreSQL, SQLite, etc.) - Where your SQLAlchemy models live (for migration generation) - Where to store migrations (directories)

Why Python Configuration?

Type Safety

Your IDE can help you:

database_config(
    database_name="primary",  # ← IDE suggests parameter names
    default=True,             # ← IDE knows this is boolean
    database_type="sqlite",   # ← IDE can validate enum values
    database_url="...",
)

Dynamic Configuration

You can use Python logic:

import os

# Different config per environment
environment = os.getenv("ENV", "dev")

if environment == "production":
    database_url = "postgresql://prod-host/myapp"
else:
    database_url = "sqlite:///./dev.db"

database_config(
    database_name="primary",
    default=True,
    database_type="postgresql" if environment == "production" else "sqlite",
    database_url=database_url,
)

Multiple Databases

Easy to configure multiple databases:

DATABASES = {
    "primary": "postgresql://localhost/main",
    "analytics": "postgresql://localhost/analytics",
    "logging": "postgresql://localhost/logs",
}

for name, url in DATABASES.items():
    database_config(
        database_name=name,
        default=(name == "primary"),
        database_type="postgresql",
        database_url=url,
        model_paths=[f"app.models.{name}"],
    )

Configuration Loading

Discovery Order

DBWarden searches for configuration in this order:

1. dbwarden.py in current directory
      not found
2. dbwarden.py in parent directories
      not found
3. Full scan for files with database_config()
      not found
4. DBWARDEN_CONFIG_MODULE environment variable
      not found
Error: No configuration found

When Configuration Loads

Configuration loads when you run any DBWarden command:

dbwarden migrate    # ← Config loads here
dbwarden status     # ← Config loads here
dbwarden history    # ← Config loads here

Load process: 1. Python imports your config module 2. database_config() calls execute 3. Databases register in internal registry 4. Validation runs 5. Command executes with loaded config

Validation Rules

DBWarden validates configuration at load time:

Rule Why It Matters
Exactly one default=True CLI needs to know which DB to use when --database is omitted
Unique database_name Commands target databases by name
Unique database_url Prevents accidental duplicate configurations
Unique physical targets Prevents two configs pointing to same DB with different credentials
Required model_paths in multi-DB Keeps model discovery boundaries clear
No overlapping model_paths Prevents ambiguous model ownership (unless overlap_models=True)

Validation Timing

# dbwarden.py
database_config(
    database_name="primary",
    default=True,
    database_type="postgresql",
    database_url="postgresql://localhost/myapp",
)

database_config(
    database_name="primary",  # ← Duplicate!
    default=True,
    database_type="postgresql",
    database_url="postgresql://localhost/other",
)

When you run dbwarden migrate:

Error: Duplicate database_name 'primary'

Validation happens before any commands execute.

The default Database

Why default=True Exists

Consider these commands:

# Explicit database
dbwarden migrate --database primary

# Implicit database (uses default)
dbwarden migrate

Without default=True, DBWarden wouldn't know which database to use for the second command.

Only One Default

# ✅ Good
database_config(database_name="primary", default=True, ...)
database_config(database_name="analytics", default=False, ...)  # or omit default

# ❌ Bad - two defaults
database_config(database_name="primary", default=True, ...)
database_config(database_name="analytics", default=True, ...)  # Error!

Default Affects CLI Behavior

# These are equivalent when primary is default:
dbwarden migrate
dbwarden migrate --database primary

# These are NOT equivalent:
dbwarden migrate
dbwarden migrate --database analytics  # Targets analytics, not primary

Model Discovery

What Are model_paths?

model_paths tells DBWarden where your SQLAlchemy models live:

database_config(
    database_name="primary",
    default=True,
    database_type="postgresql",
    database_url="postgresql://localhost/myapp",
    model_paths=["app.models"],  # ← Look here for models
)

How Discovery Works

1. Import each module in model_paths
     
2. Find all classes inheriting from DeclarativeBase
     
3. Extract table metadata (__tablename__, columns, etc.)
     
4. Build internal representation for migration generation

When Is It Required?

Single database: Optional (DBWarden scans entire codebase)

# This works
database_config(
    database_name="primary",
    default=True,
    database_type="sqlite",
    database_url="sqlite:///./app.db",
    # No model_paths needed
)

Multiple databases: Required for each database

# This is required
database_config(
    database_name="primary",
    default=True,
    database_type="postgresql",
    database_url="postgresql://localhost/main",
    model_paths=["app.models.primary"],  # ← Required
)

database_config(
    database_name="analytics",
    database_type="postgresql",
    database_url="postgresql://localhost/analytics",
    model_paths=["app.models.analytics"],  # ← Required
)

Why? To prevent ambiguity about which models belong to which database.

Dev Mode

What Is Dev Mode?

Dev mode lets you use a different database for local development:

database_config(
    database_name="primary",
    default=True,
    database_type="postgresql",              # Production
    database_url="postgresql://prod/myapp",
    dev_database_type="sqlite",              # Development
    dev_database_url="sqlite:///./dev.db",
)

Run commands with --dev:

dbwarden --dev migrate  # Uses SQLite
dbwarden migrate        # Uses PostgreSQL

How It Works

Command: dbwarden --dev migrate
    
Check for --dev flag
    
Swap database_type  dev_database_type
Swap database_url  dev_database_url
    
Connect to dev database
    
Execute command

Why Use It?

Speed: - SQLite is faster than PostgreSQL for local iteration - No network latency - No server setup

Safety: - Can't accidentally affect production - Each developer has their own isolated database - Easy to reset (just delete the file)

Simplicity: - No Docker containers needed - No database server installation - Works on all platforms

Multi-Database Configuration

Why Multiple Databases?

Common scenarios: - Separation of concerns - Transactions vs analytics - Performance - Offload reporting to separate database - Compliance - Audit logs in separate database - Legacy systems - New and old databases coexist

How It Works

Each database_config() call registers an independent database:

database_config(
    database_name="primary",
    default=True,
    database_type="postgresql",
    database_url="postgresql://localhost/main",
    model_paths=["app.models.primary"],
)

database_config(
    database_name="analytics",
    database_type="postgresql",
    database_url="postgresql://localhost/analytics",
    model_paths=["app.models.analytics"],
)

They're completely independent: - Separate migration histories - Separate migration directories - Separate model sets - Can use different database types

Model Path Boundaries

app/
  models/
    primary/
      user.py         # ← Goes to primary database
      order.py
    analytics/
      event.py        # ← Goes to analytics database
      metric.py

Configuration:

database_config(
    database_name="primary",
    model_paths=["app.models.primary"],  # ← Only primary models
    ...
)

database_config(
    database_name="analytics",
    model_paths=["app.models.analytics"],  # ← Only analytics models
    ...
)

Secure Values

What Is secure_values?

Prevents credentials from appearing in terminal output:

import os

DATABASE_URL = os.getenv("DATABASE_URL")

database_config(
    database_name="primary",
    default=True,
    database_type="postgresql",
    database_url=DATABASE_URL,
    secure_values=True,  # ← Hide credentials
)

Without secure_values:

$ dbwarden database
Database: primary
  URL: postgresql://user:SECRET_PASSWORD@prod-host/myapp

With secure_values:

$ dbwarden database
Database: primary
  URL: DATABASE_URL (expression)

Shows the variable name instead of resolved value.

Configuration vs Runtime

Configuration Time

When config loads: - database_config() calls execute - Validation runs - Internal registry populates - No database connections made

Runtime

When commands run: - DBWarden reads from registry - Connects to database - Executes command logic

Key point: Configuration errors are caught early, before any database operations.

Recap

You learned:

✅ Why Python configuration is powerful
✅ How configuration discovery works
✅ When validation runs
✅ Why default=True is required
✅ How model discovery works
✅ What dev mode does
✅ How multi-database configuration works
✅ When to use secure_values

What's Next?