Skip to content

Peaceful, type-safe Python configuration that just works. No stress, no complexity—just clean separation of config from code.

License

Notifications You must be signed in to change notification settings

Carrington-dev/dotzen

Repository files navigation

🧘 DotZen

Peaceful, type-safe Python configuration that just works.

PyPI version Python Support License: MIT

codecov Documentation Status

Package Downloads

Code style: black

Documentation | PyPI Package | GitHub | Changelog


🌟 Overview

DotZen brings zen to Python configuration management. Load settings from environment variables, .env files, JSON, YAML, or cloud secret managers with automatic type casting, validation, and a beautiful fluent API.

No more config chaos. Just pure zen. 🧘‍♂️✨

✨ Key Highlights

from dotzen import config

# Simple, elegant, type-safe
DEBUG = config('DEBUG', cast=bool, default=False)
PORT = config('PORT', cast=int, default=8000)
DATABASE_URL = config('DATABASE_URL')
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=list)

Why Choose DotZen?

Traditional Config 😫 DotZen
Scattered sources Multiple libraries needed Unified API One interface for all sources
String soup Manual type conversion everywhere Type Safety Automatic casting & validation
Runtime failures Errors only in production Early Validation Catch issues at startup
Copy-paste code Boilerplate in every project Design Patterns Elegant, reusable architecture
Hardcoded secrets Security nightmares Cloud-Native First-class secrets support

🚀 Quick Start

Installation

# Core library (zero dependencies)
pip install dotzen

# With cloud provider support
pip install dotzen[aws]        # AWS Secrets Manager
pip install dotzen[gcp]        # Google Cloud Secret Manager
pip install dotzen[azure]      # Azure Key Vault
pip install dotzen[cloud]      # All cloud providers

# Everything included
pip install dotzen[all]

Basic Usage

from dotzen import config

# Get configuration values with automatic type casting
API_KEY = config('API_KEY')
DEBUG = config('DEBUG', cast=bool, default=False)
MAX_CONNECTIONS = config('MAX_CONNECTIONS', cast=int, default=100)
ALLOWED_ORIGINS = config('ALLOWED_ORIGINS', cast=list)

Advanced Usage with Builder Pattern

from dotzen import ConfigBuilder

# Build a configuration with multiple sources
config = (ConfigBuilder()
    .add_environment('APP_')        # Env vars with prefix
    .add_dotenv('.env')             # .env file
    .add_json('config.json')        # JSON config
    .add_secrets('/run/secrets')    # Docker secrets
    .build())

# Type-safe access with convenience methods
debug = config.get_bool('DEBUG', default=False)
port = config.get_int('PORT', default=8000)
timeout = config.get_float('TIMEOUT', default=30.0)
hosts = config.get_list('ALLOWED_HOSTS')

🎯 Features

🔄 Multi-Source Configuration

DotZen implements a Chain of Responsibility pattern, checking sources in priority order:

config = (ConfigBuilder()
    .add_environment()           # 1️⃣ Highest priority
    .add_dotenv('.env')          # 2️⃣
    .add_json('config.json')     # 3️⃣
    .add_aws_secrets('prod')     # 4️⃣
    .build())                    # 5️⃣ Default values (lowest priority)

🛡️ Type Safety & Automatic Casting

# Automatic type conversion with validation
DEBUG = config.get_bool('DEBUG')           # str → bool
PORT = config.get_int('PORT')              # str → int
RATE_LIMIT = config.get_float('RATE')     # str → float
SERVERS = config.get_list('SERVERS')       # str → list

# Boolean casting supports multiple formats
# True:  "true", "yes", "1", "on", "t", "y"
# False: "false", "no", "0", "off", "f", "n"

✅ Built-in Validation

from dotzen import ConfigBuilder
from dotzen.validators import URLValidator, RangeValidator, RegexValidator

config = (ConfigBuilder()
    .add_environment()
    .with_validator(URLValidator('DATABASE_URL'))
    .with_validator(RangeValidator('PORT', 1024, 65535))
    .with_validator(RegexValidator('API_KEY', r'^[A-Za-z0-9]{32}$'))
    .build())

# Configuration is validated at build time
# Errors are caught before your app starts! 🎉

🌐 Cloud Secrets Support

# AWS Secrets Manager
config = (ConfigBuilder()
    .add_environment()
    .add_aws_secrets('prod/myapp', region='us-east-1')
    .build())

# Google Cloud Secret Manager
config = (ConfigBuilder()
    .add_environment()
    .add_gcp_secrets('projects/my-project/secrets')
    .build())

# Azure Key Vault
config = (ConfigBuilder()
    .add_environment()
    .add_azure_keyvault('https://myvault.vault.azure.net')
    .build())

# HashiCorp Vault
config = (ConfigBuilder()
    .add_environment()
    .add_vault_secrets('secret/myapp', url='https://vault.example.com')
    .build())

🎨 Fluent API Design

DotZen uses the Builder Pattern for an intuitive, chainable API:

config = (ConfigBuilder()
    .add_environment('MYAPP_')
    .add_dotenv('.env')
    .add_dotenv('.env.local', override=True)
    .add_json('config.json')
    .add_yaml('settings.yaml')
    .with_validator(URLValidator('DATABASE_URL'))
    .with_type('MAX_WORKERS', int)
    .build())

🏭 Auto-Detection Factory

Let DotZen automatically detect your configuration files:

from dotzen import ConfigFactory

# Automatically finds and loads:
# - .env
# - config.json
# - settings.json
config = ConfigFactory.auto_config()

📚 Real-World Examples

Django Settings

# settings.py
from dotzen import config

# Core settings
DEBUG = config('DEBUG', cast=bool, default=False)
SECRET_KEY = config('SECRET_KEY')
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=list, default=[])

# Database configuration
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config('DB_NAME'),
        'USER': config('DB_USER'),
        'PASSWORD': config('DB_PASSWORD'),
        'HOST': config('DB_HOST', default='localhost'),
        'PORT': config('DB_PORT', cast=int, default=5432),
    }
}

# Email configuration
EMAIL_HOST = config('EMAIL_HOST', default='smtp.gmail.com')
EMAIL_PORT = config('EMAIL_PORT', cast=int, default=587)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', cast=bool, default=True)
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')

# Redis cache
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': config('REDIS_URL', default='redis://127.0.0.1:6379/1'),
    }
}

FastAPI Application

from fastapi import FastAPI, Depends
from dotzen import ConfigBuilder

# Build configuration at startup
config = (ConfigBuilder()
    .add_environment()
    .add_dotenv()
    .add_json('config.json')
    .build())

app = FastAPI(
    title=config('APP_NAME', default='My API'),
    debug=config.get_bool('DEBUG', default=False),
    version=config('VERSION', default='1.0.0'),
)

# Dependency injection
def get_database_url():
    return config('DATABASE_URL')

@app.get("/health")
async def health_check():
    return {
        "status": "healthy",
        "environment": config('ENVIRONMENT', default='development'),
        "version": config('VERSION', default='1.0.0'),
    }

@app.get("/config")
async def get_config_info():
    return {
        "debug_mode": config.get_bool('DEBUG', False),
        "max_connections": config.get_int('MAX_CONNECTIONS', 100),
        "timeout": config.get_float('TIMEOUT', 30.0),
    }

Flask Application

from flask import Flask
from dotzen import config

app = Flask(__name__)

# Configure Flask from DotZen
app.config['DEBUG'] = config('DEBUG', cast=bool, default=False)
app.config['SECRET_KEY'] = config('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = config('DATABASE_URL')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Custom settings
app.config['MAX_CONTENT_LENGTH'] = config('MAX_UPLOAD_SIZE', cast=int, default=16 * 1024 * 1024)
app.config['UPLOAD_FOLDER'] = config('UPLOAD_FOLDER', default='uploads')

@app.route('/config')
def show_config():
    return {
        'debug': app.config['DEBUG'],
        'environment': config('ENVIRONMENT', default='development'),
    }

Microservices with Docker Secrets

# docker-compose.yml provides secrets in /run/secrets/
from dotzen import ConfigBuilder

config = (ConfigBuilder()
    .add_environment()
    .add_secrets('/run/secrets')  # Docker secrets
    .add_dotenv('.env')
    .build())

# Access secrets seamlessly
DB_PASSWORD = config('db_password')
API_KEY = config('api_key')
JWT_SECRET = config('jwt_secret')

# Regular config
SERVICE_NAME = config('SERVICE_NAME', default='my-service')
PORT = config.get_int('PORT', default=8000)

Multi-Environment Configuration

import os
from dotzen import ConfigBuilder

# Determine environment
ENVIRONMENT = os.getenv('ENVIRONMENT', 'development')

# Load environment-specific configuration
config = (ConfigBuilder()
    .add_environment()
    .add_dotenv(f'.env.{ENVIRONMENT}')      # .env.development, .env.production
    .add_json(f'config.{ENVIRONMENT}.json')
    .add_dotenv('.env.local', override=True)  # Local overrides
    .build())

# Configuration adapts to environment automatically
DEBUG = config.get_bool('DEBUG')
DATABASE_URL = config('DATABASE_URL')
LOG_LEVEL = config('LOG_LEVEL', default='INFO')

Pydantic Integration

from pydantic import BaseModel, Field
from dotzen importfig

class DatabaseSettings(BaseModel):
    host: str = Field(default_factory=lambda: config('DB_HOST', default='localhost'))
    port: int = Field(default_factory=lambda: config('DB_PORT', cast=int, default=5432))
    user: str = Field(default_factory=lambda: config('DB_USER'))
    password: str = Field(default_factory=lambda: config('DB_PASSWORD'))
    database: str = Field(default_factory=lambda: config('DB_NAME'))

class AppSettings(BaseModel):
    debug: bool = Field(default_factory=lambda: config('DEBUG', cast=bool, default=False))
    secret_key: str = Field(default_factory=lambda: config('SECRET_KEY'))
    database: DatabaseSettings = Field(default_factory=DatabaseSettings)
    allowed_hosts: list = Field(default_factory=lambda: config('ALLOWED_HOSTS', cast=list, default=[]))

# Create settings instance
settings = AppSettings()

🏗️ Architecture & Design Patterns

DotZen is built on proven Gang of Four design patterns:

Design Patterns Used

Pattern Purpose Implementation
Strategy Pluggable config sources ConfigSource abstract base class
Chain of Responsibility Priority-based resolution ConfigChain tries sources in order
Builder Fluent construction ConfigBuilder for chainable API
Factory Auto-detection ConfigFactory.auto_config()
Singleton Global instance ConfigSingleton for app-wide access
Facade Simple interface Config class hides complexity
Null Object Graceful defaults NullSource for missing configs
Sentinel Undefined values UNDEFINED marker object

Architecture Diagram

┌─────────────────────────────────────────────────────┐
│                   ConfigBuilder                      │
│              (Builder Pattern)                       │
└───────────────────┬─────────────────────────────────┘
                    │ builds
                    ▼
┌─────────────────────────────────────────────────────┐
│                     Config                           │
│                (Facade Pattern)                      │
└───────────────────┬─────────────────────────────────┘
                    │ uses
                    ▼
┌─────────────────────────────────────────────────────┐
│                  ConfigChain                         │
│        (Chain of Responsibility Pattern)             │
└───────────────────┬─────────────────────────────────┘
                    │ coordinates
                    ▼
┌─────────────────────────────────────────────────────┐
│              ConfigSource (Strategy)                 │
│  ┌──────────────────────────────────────────────┐  │
│  │ EnvironmentSource │ DotEnvSource │ JsonSource│  │
│  │ YamlSource │ SecretSource │ AwsSecretsSource │  │
│  └──────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

🆚 Comparison with Alternatives

Feature Comparison Matrix

Feature DotZen python-decouple pydantic-settings dynaconf python-dotenv
Type Safety ✅ Full ⚠️ Basic ✅ Full ✅ Full ❌ None
Cloud Secrets ✅ Native ❌ No ⚠️ Limited ✅ Yes ❌ No
Fluent API ✅ Yes ❌ No ❌ No ⚠️ Limited ❌ No
Validation ✅ Built-in ⚠️ Basic ✅ Pydantic ✅ Yes ❌ No
Multi-Source ✅ Yes ⚠️ Limited ⚠️ Limited ✅ Yes ❌ Env only
Zero Core Deps ✅ Yes ✅ Yes ❌ No ❌ No ✅ Yes
Design Patterns ✅ 8 patterns ❌ None ⚠️ Some ⚠️ Some ❌ None
Docker Secrets ✅ Native ❌ No ❌ No ❌ No ❌ No
Auto-Detection ✅ Yes ❌ No ⚠️ Limited ✅ Yes ❌ No
Custom Sources ✅ Easy ❌ No ⚠️ Complex ⚠️ Complex ❌ No

When to Use DotZen

Perfect for:

  • 🚀 Modern cloud-native applications
  • 🏢 Microservices architectures
  • 📦 12-factor app compliance
  • 🔐 Applications requiring cloud secrets
  • 👥 Teams valuing clean, maintainable code
  • 🎯 Projects needing validated configuration

Consider alternatives if:

  • You only need basic .env file parsing → python-dotenv
  • You're heavily invested in Pydantic → pydantic-settings
  • You need legacy Python 2.7 support → python-decouple

📖 Documentation

Core Concepts

Configuration Sources

from dotzen import ConfigBuilder

# Available sources
builder = ConfigBuilder()
builder.add_environment()              # Environment variables
builder.add_environment('APP_')        # With prefix
builder.add_dotenv('.env')             # .env file
builder.add_json('config.json')        # JSON file
builder.add_yaml('config.yaml')        # YAML file (requires dotzen[yaml])
builder.add_toml('config.toml')        # TOML file (requires dotzen[toml])
builder.add_secrets('/run/secrets')    # Docker secrets
builder.add_aws_secrets('prod/myapp')  # AWS Secrets Manager
builder.add_gcp_secrets('projects/x')  # GCP Secret Manager
builder.add_azure_keyvault('url')      # Azure Key Vault
builder.add_vault_secrets('path')      # HashiCorp Vault

Type Casting

# Automatic casting
value = config.get('KEY', cast=int)
value = config.get('KEY', cast=bool)
value = config.get('KEY', cast=float)
value = config.get('KEY', cast=list)

# Convenience methods
value = config.get_int('KEY', default=0)
value = config.get_bool('KEY', default=False)
value = config.get_float('KEY', default=0.0)
value = config.get_list('KEY', default=[])

# Custom casting
def parse_json(value):
    import json
    return json.loads(value)

value = config.get('JSON_DATA', cast=parse_json)

Error Handling

from dotzen import UndefinedValueError, ValidationError, ConfigError

try:
    api_key = config('API_KEY')
except UndefinedValueError:
    print("API_KEY not found in configuration")

try:
    port = config.get_int('PORT')
except ValidationError as e:
    print(f"Invalid PORT value: {e}")

# Or use defaults
api_key = config('API_KEY', default='dev-key')
port = config.get_int('PORT', default=8000)

Advanced Usage

Custom Configuration Sources

from dotzen import ConfigSource

class RedisSource(ConfigSource):
    """Load configuration from Redis"""
    
    def __init__(self, redis_client, prefix='config:'):
        self.redis = redis_client
        self.prefix = prefix
    
    def load(self) -> Dict[str, str]:
        keys = self.redis.keys(f'{self.prefix}*')
        data = {}
        for key in keys:
            clean_key = key.decode().replace(self.prefix, '')
            data[clean_key] = self.redis.get(key).decode()
        return data
    
    def exists(self) -> bool:
        return self.redis.ping()

# Use custom source
import redis
redis_client = redis.Redis(host='localhost', port=6379)

config = (ConfigBuilder()
    .add_custom_source(RedisSource(redis_client))
    .build())

Custom Validators

from dotzen import ConfigBuilder, ValidationError

def email_validator(key: str, value: str):
    """Validate email format"""
    if '@' not in value:
        raise ValidationError(f"{key} must be a valid email")

def port_validator(key: str, value: int):
    """Validate port number"""
    if not (1024 <= value <= 65535):
        raise ValidationError(f"{key} must be between 1024 and 65535")

config = (ConfigBuilder()
    .add_environment()
    .with_validator(email_validator)
    .with_type('PORT', int)
    .with_validator(port_validator)
    .build())

Testing Configuration

import pytest
from dotzen import ConfigBuilder, ConfigSingleton

def test_config():
    """Test configuration loading"""
    # Reset singleton for testing
    ConfigSingleton.reset()
    
    # Create test config
    config = (ConfigBuilder()
        .add_environment()
        .build())
    
    # Test values
    assert config.get_bool('DEBUG', False) == False
    assert config.get_int('PORT', 8000) == 8000

@pytest.fixture
def test_config():
    """Fixture providing test configuration"""
    ConfigSingleton.reset()
    return (ConfigBuilder()
        .add_dotenv('.env.test')
        .build())

🔧 Installation Extras

Available Extras

# Cloud providers
pip install dotzen[aws]        # AWS Secrets Manager (boto3)
pip install dotzen[gcp]        # Google Cloud Secret Manager
pip install dotzen[azure]      # Azure Key Vault
pip install dotzen[vault]      # HashiCorp Vault
pip install dotzen[cloud]      # All cloud providers

# File formats
pip install dotzen[yaml]       # YAML support (PyYAML)
pip install dotzen[toml]       # TOML support
pip install dotzen[json5]      # JSON5 support
pip install dotzen[formats]    # All formats

# Development
pip install dotzen[dev]        # Testing and dev tools
pip install dotzen[docs]       # Documentation building
pip install dotzen[test]       # Testing with cloud mocks

# Everything
pip install dotzen[all]        # All features

🤝 Contributing

We welcome contributions! Here's how to get started:

Development Setup

# Clone the repository
git clone https://github.com/carrington-dev/dotzen.git
cd dotzen

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev,test,all]"

# Install pre-commit hooks
pre-commit install

# Run tests
pytest

# Run type checking
mypy dotzen

# Format code
black dotzen tests
ruff check dotzen tests

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=dotzen --cov-report=html

# Run specific test file
pytest tests/test_dotzen.py

# Run with verbose output
pytest -v

# Run only fast tests (skip cloud integration)
pytest -m "not integration"

Pull Request Process

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass (pytest)
  6. Format code (black . and ruff check --fix .)
  7. Commit your changes (git commit -m 'Add amazing feature')
  8. Push to the branch (git push origin feature/amazing-feature)
  9. Open a Pull Request

Please use our PR templates for:


📝 License

DotZen is released under the MIT License. See LICENSE for details.

MIT License

Copyright (c) 2025 Carrington Muleya

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

🙏 Acknowledgments


📊 Project Stats

GitHub stars GitHub forks GitHub watchers GitHub contributors GitHub last commit GitHub issues GitHub pull requests


🔗 Links & Resources


💬 Community & Support


Made with 🧘 and ❤️ by Carrington Muleya

Find your configuration zen. Try DotZen today!

⬆ Back to Top

About

Peaceful, type-safe Python configuration that just works. No stress, no complexity—just clean separation of config from code.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages