Skip to main content

Zero Config 🚀

Smart configuration management with layered overrides and type-aware defaults.

🎯 Core Concept

Zero Config provides layered configuration where each layer can override the previous one:

  1. Application Defaults → 2. Environment Variables → 3. Environment Files
from zero_config import setup_environment, get_config

# 1. Define application defaults
default_config = {
    'openai_api_key': '',           # Will be overridden by env var
    'llm.temperature': 0.0,         # Will be overridden by .env file
    'database.host': 'localhost',   # Will stay as default
}

# 2. Set environment variable
# export OPENAI_API_KEY="sk-your-key-here"

# 3. Create .env.zero_config file
# llm.temperature=0.7
# database.port=5432

setup_environment(default_config=default_config)
config = get_config()

# Final configuration:
print(config.get('openai_api_key'))    # "sk-your-key-here" (from env var)
print(config.get('llm.temperature'))   # 0.7 (from .env file, converted to float)
print(config.get('database.host'))     # "localhost" (from defaults)
print(config.get('database.port'))     # 5432 (from .env file, new key as int)

🏗️ Why Layered Configuration?

  • Defaults in Code: Your app defines the schema and sensible defaults
  • Environment Variables: Perfect for deployment-specific overrides (Docker, CI/CD) - only loaded if defaults exist
  • Environment Files: Great for local development and secrets management - loads all variables
  • Type Safety: Environment strings are automatically converted to match your default types
  • Multi-Level Support: Handle complex nested configurations with ease
  • Package Conflict Prevention: Automatic protection when multiple packages use zero-config

🔧 Configuration Sources

🔄 Environment Variable Filtering (NEW in v0.1.5+)

Zero Config uses smart environment variable filtering to prevent configuration pollution:

  • OS Environment Variables: Only loaded if they have corresponding defaults in your configuration
  • Environment Files: Load ALL variables regardless of defaults
# Example: You have these OS environment variables set
# OPENAI_API_KEY=sk-test123        ← Will be loaded (has default)
# DATABASE_HOST=localhost          ← Will be loaded (has default)
# RANDOM_SYSTEM_VAR=some_value     ← Will be IGNORED (no default)
# PATH=/usr/bin:/bin               ← Will be IGNORED (no default)

default_config = {
    'openai_api_key': '',      # OS env OPENAI_API_KEY will override this
    'database.host': 'local',  # OS env DATABASE__HOST will override this
    # No default for RANDOM_SYSTEM_VAR, so it's ignored
}

setup_environment(default_config=default_config)
config = get_config()

print(config.get('openai_api_key'))    # "sk-test123" (from OS env)
print(config.get('database.host'))     # "localhost" (from OS env)
print(config.get('random_system_var')) # None (filtered out)

This prevents your application configuration from being polluted by unrelated system environment variables.

Environment Variables

# Uppercase env vars automatically override config keys
export OPENAI_API_KEY="sk-your-key-here"    # Becomes: openai_api_key
export DEBUG="true"                         # Becomes: debug (bool)
export MODELS='["gpt-4", "claude-3"]'       # JSON arrays for lists
export DATABASE_URL="host1,host2,host3"     # Strings with commas stay safe

# Section headers with double underscore (multi-level support)
export LLM__TEMPERATURE="0.7"               # Becomes: llm.temperature
export DATABASE__HOST="remote.db.com"       # Becomes: database.host

# Multi-level configuration (NEW in v0.1.5+)
export DATABASE__DEVELOPE__DB_URL="postgresql://localhost:5432/dev"  # Becomes: database.develope.db_url
export DATABASE__DEVELOPE__POOL_SIZE="10"   # Becomes: database.develope.pool_size (converted to int)
export LLM__OPENAI__API_KEY="sk-test123"    # Becomes: llm.openai.api_key
export LLM__ANTHROPIC__MODEL="claude-3"     # Becomes: llm.anthropic.model

Important: OS environment variables are only loaded if they have corresponding defaults in your configuration. This prevents random environment variables from polluting your config.

Environment Files

# .env.zero_config (default) or custom files
openai_api_key=sk-your-local-key
llm.temperature=0.7
database.port=5432
models=["gpt-4", "claude-3"]

# Multi-level configuration in env files (NEW in v0.1.5+)
DATABASE__DEVELOPE__DB_URL=postgresql://localhost:5432/dev
DATABASE__DEVELOPE__POOL_SIZE=10
LLM__OPENAI__API_KEY=sk-env-key
LLM__OPENAI__MODEL=gpt-4

Important: Environment files load ALL variables regardless of whether they have defaults. This allows you to add new configuration keys via env files.

Custom Environment Files

setup_environment(
    default_config=default_config,
    env_files="config/production.env"          # Single file
)

setup_environment(
    default_config=default_config,
    env_files=["base.env", "production.env"]   # Multiple files (later wins)
)

📁 Project Root Detection

Critical: Environment files are loaded relative to your project root.

# Auto-detection (looks for .git, pyproject.toml, setup.py, etc.)
setup_environment(default_config=config)

# Override via environment variable
# export PROJECT_ROOT="/path/to/project"
setup_environment(default_config=config)

Why it matters: Zero Config needs to know your project root to:

  • Load .env.zero_config from the correct location
  • Resolve relative paths in env_files parameter
  • Provide accurate dynamic path helpers (config.data_path(), etc.)

🛠️ Advanced Features

Dynamic Path Helpers

config = get_config()

# Any directory name + '_path' works (Ruby on Rails style)
config.data_path('database.db')      # /project/data/database.db
config.logs_path('app.log')          # /project/logs/app.log
config.cache_path('session.json')    # /project/cache/session.json
config.models_path('gpt4.bin')       # /project/models/gpt4.bin

Section Configuration & Multi-Level Support

# Define sections with dot notation OR nested dictionaries (NEW in v0.1.5+)
default_config = {
    # Flat dot notation (traditional)
    'llm.models': ['gpt-4'],
    'llm.temperature': 0.0,
    'database.host': 'localhost',
    'database.port': 5432,

    # Nested dictionary format (NEW)
    'database': {
        'develope': {
            'db_url': 'sqlite:///dev.db',
            'pool_size': 5
        },
        'production': {
            'db_url': 'postgresql://prod.db.com:5432/prod'
        }
    },

    # Mixed formats work together!
    'llm.openai.api_key': 'default-key',  # Flat notation
    'cache': {                            # Nested format
        'enabled': True,
        'ttl': 3600
    }
}

config = get_config()

# Access sections (returns all keys within that section)
llm_config = config.get('llm')      # {'models': [...], 'temperature': 0.0, 'openai': {'api_key': '...'}}
db_config = config.get('database')  # {'host': 'localhost', 'port': 5432, 'develope': {...}, 'production': {...}}

# Access multi-level keys directly with dot notation
db_url = config.get('database.develope.db_url')     # 'sqlite:///dev.db'
api_key = config.get('llm.openai.api_key')          # 'default-key'

# Access subsections
develope_config = config.get('database.develope')   # {'db_url': '...', 'pool_size': 5}
openai_config = config.get('llm.openai')            # {'api_key': 'default-key'}

Type Conversion

Environment variables are automatically converted to match your default types:

  • Numbers: "8000" → 8000 (int), "0.7" → 0.7 (float)
  • Booleans: "true" → True, "false" → False
  • Lists: '["a","b"]' → ['a','b'] (JSON only - comma strings stay safe)
  • Strings: Always preserved as-is (safe for URLs, CSVs, etc.)

📦 Installation

pip install zero-config
# Or install specific version
pip install zero-config==0.1.6

🛡️ Package Conflict Prevention

Critical for libraries: Zero Config prevents configuration conflicts when both your main project and its dependencies use zero-config.

The Problem

Without protection, packages can accidentally overwrite your main project's configuration:

# ❌ Without protection (old behavior)
# Main project sets up config
setup_environment(default_config={'app_name': 'news_app', 'llm.api_key': 'main-key'})

# Package dependency overwrites everything!
setup_environment(default_config={'package_name': 'united_llm'})

# Main project's config is lost 😱
config = get_config()
print(config.get('app_name'))  # None - lost!

The Solution

Zero Config now automatically prevents this:

# ✅ With protection (new behavior)
# Main project initializes first
setup_environment(default_config={'app_name': 'news_app', 'llm.api_key': 'main-key'})

# Package dependency tries to initialize (safely ignored)
setup_environment(default_config={'package_name': 'united_llm'})  # ← Ignored!

# Main project's config is preserved 🎉
config = get_config()
print(config.get('app_name'))      # "news_app" (preserved)
print(config.get('package_name'))  # None (package config ignored)

How It Works

  1. First Call Wins: The first setup_environment() call initializes the global configuration
  2. Automatic Protection: Subsequent calls are automatically ignored with helpful logging
  3. Shared Access: Packages can still access the main project's configuration
  4. Override Available: Use force_reinit=True only for testing or special cases

Best Practices

For Main Applications:

# Initialize early in your main application
def main():
    setup_environment(default_config=your_app_config)
    # ... rest of your app

For Package Libraries:

# Packages should call setup_environment but expect it might be ignored
def initialize_package():
    # This will be ignored if main app already initialized
    setup_environment(default_config=package_defaults)

    # Always access config this way
    config = get_config()
    return config.get('llm')  # Access main app's LLM config

🔗 API Reference

# Setup
setup_environment(
    default_config={...},           # Your app's defaults (supports nested dicts and flat dot notation)
    env_files="custom.env",         # Optional: custom env file(s)
    force_reinit=False              # Force re-init (use with caution)
)

# Access
config = get_config()
config.get('key', default)         # Safe access with fallback
config['key']                      # Direct access (raises KeyError if missing)
config.get('llm')                  # Get all llm.* keys as dict (section access)
config.get('database.develope')    # Get subsection as dict (NEW: multi-level)
config.get('database.develope.db_url')  # Direct multi-level access (NEW)
config.to_dict()                   # Get all config as nested dict
config.to_flat_dict()              # Get all config as flat dict with dot notation keys

# Initialization status
is_initialized()                   # Check if already initialized
get_initialization_info()          # Get info about who initialized

# Dynamic paths (Ruby on Rails style)
config.data_path('file.db')        # /project/data/file.db
config.logs_path('app.log')        # /project/logs/app.log
config.any_name_path('file')       # /project/any_name/file

# Multi-level configuration examples (NEW in v0.1.5+)
config.get('database')             # Returns entire database section
config.get('database.develope')    # Returns develope subsection
config.get('llm.openai')          # Returns OpenAI LLM subsection

🔍 Debugging & Troubleshooting

Check Initialization Status

from zero_config import is_initialized, get_initialization_info

# Check if zero-config has been initialized
if is_initialized():
    print(f"Initialized by: {get_initialization_info()}")
else:
    print("Not yet initialized")

Common Issues

Issue: RuntimeError: Configuration not initialized

# ❌ Trying to get config before setup
config = get_config()  # Error!

# ✅ Always call setup_environment first
setup_environment()
config = get_config()  # Works!

Issue: Package config not working

# ❌ Package trying to override main config
setup_environment(default_config=package_config)  # Ignored!

# ✅ Package accessing main config
setup_environment(default_config=package_config)  # Ignored (expected)
config = get_config()  # Access main app's config
llm_config = config.get('llm')  # Get LLM section from main app

Issue: Need to reset for testing

# ✅ For testing only
from zero_config.config import _reset_for_testing

def test_something():
    _reset_for_testing()  # Reset global state
    setup_environment(test_config)
    # ... test code

Logging

Zero Config provides helpful logging. Enable it to see what's happening:

import logging
logging.basicConfig(level=logging.INFO)

setup_environment(default_config=config)
# INFO: Auto-detected project root: /path/to/project
# INFO: 🚀 Environment setup complete

📚 Documentation

For comprehensive guides and advanced usage, see the docs/ directory:

🚀 Migration Guide

From v0.1.5 to v0.1.6+

No breaking changes! This is a documentation enhancement release. All v0.1.5 features remain unchanged.

  • ✅ Enhanced documentation: Comprehensive guides and examples for all features
  • ✅ Documentation index: New docs/README.md with complete overview
  • ✅ Improved examples: Better multi-level configuration examples in README

From v0.1.4 to v0.1.5+

No breaking changes! Your existing code continues to work. New features:

  • ✅ Multi-level configuration support: DATABASE__DEVELOPE__DB_URL → database.develope.db_url
  • ✅ Enhanced DotDict implementation: Native dot notation support for nested access
  • ✅ Improved environment variable filtering: OS env vars only loaded if defaults exist
  • ✅ Mixed configuration formats: Support both nested dicts and flat dot notation in defaults

From v0.1.0 to v0.1.1+

No breaking changes! Your existing code continues to work. New features:

  • ✅ Automatic package conflict prevention
  • ✅ New is_initialized() and get_initialization_info() functions
  • ✅ New force_reinit=True parameter for special cases

Upgrading Your Package

If you maintain a package that uses zero-config:

# Before (still works)
def initialize():
    setup_environment(default_config=defaults)

# After (recommended - more explicit)
def initialize():
    if not is_initialized():
        setup_environment(default_config=defaults)
    return get_config()

Metadata

Release files for zero-config 0.1.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zero-config 0.1.6
File Size Uploaded Provenance
zero_config-0.1.6.tar.gz 53.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for zero-config 0.1.6
File Interpreter ABI Platform
zero_config-0.1.6-py3-none-any.whl Python 3 none any Details

Total release size: 66.7 kB

Release files / zero_config-0.1.6.tar.gz

Download URL zero_config-0.1.6.tar.gz
Size 53.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c47431d3d0f0eeaff268dc1759a0495f7dd5c531ddcddc18ba012698e8221cac
BLAKE2b-256 checksum
How to use checksums
4237464eb295d2a667a3e9743127946a46e916e435ac45c35c20eebdb056d763
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.3

Release files / zero_config-0.1.6-py3-none-any.whl

Download URL zero_config-0.1.6-py3-none-any.whl
Size 13.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0a2726255b83424315bf09af7c09e9b8c675e9b40fb60ec504ddbe233ef1740d
BLAKE2b-256 checksum
How to use checksums
6ef52a65c542894aef0fc4a6730473cea2217ae39b26251d1c16f2e610f6f356
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.3

Security for zero-config 0.1.6

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page