Python Configuration Management

- Migrating from hardcoded values to environment variables

Python Configuration Management

What Is This?

Python Configuration Management is the practice of externalizing all environment-specific settings from your Python application code, replacing hardcoded values with environment variables and typed configuration objects. This skill, as described in the Happycapy Skills platform, focuses on managing your application's configuration using best practices such as environment variables, typed settings (with tools like pydantic-settings), and robust validation. By adopting this approach, you can ensure that your Python code remains environment-agnostic, secure, and maintainable.

Why Use It?

Hardcoding configuration values, such as database URLs, API keys, feature flags, or credentials, directly in your codebase leads to several problems:

  • Lack of flexibility: Code changes are required for every environment-specific tweak.
  • Security risks: Secrets and sensitive values are exposed in source control.
  • Difficult deployments: Dev, staging, and production environments cannot be managed cleanly.
  • Fragile validation: Missing or invalid configuration is only discovered at runtime, often deep into execution.

Configuration management via environment variables and typed settings addresses these issues. Your code can be deployed to any environment (local, CI, staging, production) without modification. Centralizing configuration validation at startup allows your application to fail fast if something is misconfigured, providing clear error messages and reducing debugging time.

How to Use It

1. Identify Configuration

Values

Start by listing all environment-specific values in your application. Typical examples include:

  • Database connection strings
  • API keys and secrets
  • External service URLs
  • Feature flags
  • Debug or logging settings

2. Remove Hardcoded

Values

Replace any hardcoded settings in your code with references to environment variables. In Python, you can use the os module, but for robust, type-safe configuration, use pydantic-settings.

3. Define Typed Settings with

Pydantic

Create a configuration class using pydantic.BaseSettings to define all your settings, including type annotations, default values, and environment variable mapping.

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    database_url: str = Field(..., env="DATABASE_URL")
    debug: bool = Field(default=False)
    secret_key: str = Field(..., env="SECRET_KEY")
    feature_x_enabled: bool = Field(default=False, env="FEATURE_X_ENABLED")

    class Config:
        env_file = ".env"  # Optional: Load from a .env file for local development

settings = Settings()  # Validates at startup

4. Set Environment

Variables

Environment variables can be managed in multiple ways:

  • Directly in the shell:
    export DATABASE_URL="postgresql://user:pass@host/db"
    export SECRET_KEY="supersecret"
  • In a .env file (useful for development or Docker):
    DATABASE_URL=postgresql://user:pass@host/db
    SECRET_KEY=supersecret
    FEATURE_X_ENABLED=true

5. Validate at Application

Startup

When you instantiate the Settings class, pydantic will automatically parse and validate all configuration values. If any required value is missing or invalid, an exception is raised immediately and the application will not start.

Example error on missing variable:

pydantic.error_wrappers.ValidationError: 1 validation error for Settings
database_url
  field required (type=value_error.missing)

6. Use Settings Throughout Your

Code

Pass the settings object to any part of your application that needs configuration, rather than scattering environment variable lookups everywhere.

def connect_database(settings: Settings):
    conn = connect(settings.database_url)
    # ...

When to Use It

The Python Configuration Management skill is applicable in several situations:

  • Setting up a new project: Establish a configuration management system from the start.
  • Migrating legacy code: Replace hardcoded values with environment variables and typed settings.
  • Implementing pydantic-settings: Adopt type-checked, validated configuration in modern Python applications.
  • Managing secrets: Store sensitive values securely outside the codebase, using environment variables or secret managers.
  • Supporting multiple environments: Define explicit settings for development, staging, and production.
  • Fail-fast validation: Ensure configuration errors are caught immediately at startup.

Important Notes

  • Fail Fast Principle: Always validate configuration at application boot. It is better for your application to crash immediately with a clear error than to run with invalid or missing settings.
  • Sensible Defaults: Provide non-sensitive defaults (such as debug=False) to facilitate local development, but always require explicit values for secrets and production-critical settings.
  • Typed Settings: Use pydantic-settings or similar libraries to ensure your configuration is parsed and validated into strongly typed objects.
  • Environment-specific Behavior: Store only environment-specific values outside the code. Application logic should not depend on environment variables directly, but rather on the typed settings object.
  • Security Considerations: Never commit secrets to version control. Use environment variables, secret managers, or encrypted configuration files for sensitive values.

By using the Python Configuration Management skill, you ensure your applications are portable, secure, and easy to operate across all environments. This approach is essential for modern Python development, enabling robust deployments and simplifying the management of complex settings.