SkillAgentSearch skills...

Varlord

Unified yet dead simple Python config management: defaults, CLI, env vars, .env, etcd. Type-safe, validated, with dynamic updates.

Install / Use

/learn @lzjever/Varlord

README

Varlord ⚙️

PyPI version Python 3.8+ License Documentation CI codecov

Stop wrestling with configuration chaos. Start with Varlord.

Varlord is a battle-tested Python configuration management library that eliminates the pain of managing configuration from multiple sources. Born from real-world production challenges, it provides a unified, type-safe, and elegant solution for configuration management.

🎯 The Problem We Solve

Real-World Configuration Nightmares

Every Python developer has faced these frustrating scenarios:

The Configuration Spaghetti

# Your code becomes a mess of conditionals and parsing
host = os.getenv("HOST", "127.0.0.1")
port = int(os.getenv("PORT", "8000"))  # What if PORT is not a number?
debug = os.getenv("DEBUG", "false").lower() == "true"  # Really?
if "--host" in sys.argv:
    host = sys.argv[sys.argv.index("--host") + 1]  # Error-prone parsing
# ... and it gets worse with nested configs, validation, etc.

Priority Confusion

"Does CLI override env? Or env overrides CLI? Wait, what about the config file? Which one wins?"

Type Conversion Hell

# String "true" vs boolean True vs "1" vs 1
# "8000" vs 8000
# Missing values, None handling, type errors at runtime...

The Restart Tax

"I just need to change one config value. Why do I have to restart the entire service?"

Silent Failures

"The config looks wrong, but the app starts anyway. Users report bugs 3 hours later."

Varlord's Solution

One unified interface. Multiple sources. Clear priority. Built-in diagnostics.

from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
from varlord import Config, sources

@dataclass(frozen=True)
class AppConfig:
    """Application configuration with clear structure and validation."""
    
    # Required field - must be provided
    api_key: str = field(metadata={"description": "API key for authentication"})
    
    # Optional fields with sensible defaults
    host: str = field(default="127.0.0.1", metadata={"description": "Server host address"})
    port: int = field(default=8000, metadata={"description": "Server port number"})
    debug: bool = field(default=False, metadata={"description": "Enable debug mode"})
    timeout: float = field(default=30.0, metadata={"description": "Request timeout in seconds"})
    hello_message: Optional[str] = field(
        default=None, metadata={"description": "Optional greeting message"}
    )

def main():
    # Define configuration sources with clear priority order
    # Priority: CLI (highest) > User Config > App Config > System Config > Env > Defaults (lowest)
    cfg = Config(
        model=AppConfig,
        sources=[
            # System-wide configuration (lowest priority, rarely overridden)
            sources.YAML("/etc/myapp/config.yaml"),  # System config
            
            # Application-level configuration
            sources.JSON(Path(__file__).parent / "config.json"),  # App directory
            
            # User-specific configuration (overrides system and app configs)
            sources.YAML(Path.home() / ".config" / "myapp" / "config.yaml"),  # User directory
            sources.TOML(Path.home() / ".myapp.toml"),  # Alternative user config
            
            # Environment variables (common in containers/CI)
            sources.Env(),
            sources.DotEnv(".env"),  # Local development
            
            # Command-line arguments (highest priority, for debugging/overrides)
            sources.CLI(),
        ],
    )
    
    # One line to add comprehensive CLI management: --help, --check-variables, etc.
    # This single call adds:
    #   - --help / -h: Auto-generated help from your model metadata
    #   - --check-variables / -cv: Complete configuration diagnostics
    #   - Automatic validation and error reporting
    #   - Exit handling (exits if help/cv is requested)
    cfg.handle_cli_commands()  # Handles --help, -cv automatically, exits if needed
    
    # Load configuration - type-safe, validated, ready to use
    app = cfg.load()
    
    # Your application code
    print(f"Starting server on {app.host}:{app.port}")
    print(f"Debug: {app.debug}, Timeout: {app.timeout}s")

if __name__ == "__main__":
    main()

What just happened?

  1. ✅ Multiple Sources, Unified Interface: System config, app config, user config, env vars, CLI - all handled the same way
  2. ✅ Clear Priority: Later sources override earlier ones - no confusion
  3. ✅ Automatic Type Conversion: Strings from files/env → proper types (int, bool, float)
  4. ✅ Model-Driven Filtering: Each source only reads fields defined in your model
  5. ✅ Built-in Diagnostics: --check-variables shows exactly what's loaded from where
  6. ✅ Zero Boilerplate: No parsing, no type conversion code, no priority logic

Try it:

# See comprehensive configuration diagnostics
python app.py --check-variables
# or short form
python app.py -cv

# See help with all sources and priority
python app.py --help

# Run normally
python app.py --api-key your_key

The --check-variables output shows everything:

When you run python app.py -cv, you get a comprehensive diagnostic report:

+---------------+----------+---------------+----------+-----------+
| Variable      | Required | Status        | Source   | Value     |
+---------------+----------+---------------+----------+-----------+
| api_key       | Required | Missing       | defaults | None      |
| host          | Optional | Loaded        | dotenv   | localhost |
| port          | Optional | Loaded        | dotenv   | 7000      |
| debug         | Optional | Loaded        | dotenv   | true      |
| timeout       | Optional | Loaded        | dotenv   | 20.0      |
| hello_message | Optional | Using Default | defaults | None      |
+---------------+----------+---------------+----------+-----------+

Configuration Source Priority and Details:

+------------+-------------+-----------+----------------------------------------+--------+----------------+---------------+-------------+
| Priority   | Source Name | Source ID | Instance                               | Status | Load Time (ms) | Watch Support | Last Update |
+------------+-------------+-----------+----------------------------------------+--------+----------------+---------------+-------------+
| 1 (lowest) | defaults    | defaults  | <Defaults(model=AppConfig)>            | Active | 0.00           | No            | N/A         |
| 2          | yaml        | yaml      | <YAML(/etc/myapp/config.yaml)>         | Active | 0.15           | No            | N/A         |
| 3          | json        | json      | <JSON(config.json)>                    | Active | 0.08           | No            | N/A         |
| 4          | yaml        | yaml      | <YAML(~/.config/myapp/config.yaml)>    | Active | 0.12           | No            | N/A         |
| 5          | toml        | toml      | <TOML(~/.myapp.toml)>                  | Active | 0.05           | No            | N/A         |
| 6          | env         | env       | <Env(model-based)>                     | Active | 0.05           | No            | N/A         |
| 7          | dotenv      | dotenv    | <DotEnv(.env)>                         | Active | 0.03           | No            | N/A         |
| 8 (highest)| cli         | cli       | <CLI()>                                | Active | 0.20           | No            | N/A         |
+------------+-------------+-----------+----------------------------------------+--------+----------------+---------------+-------------+


Note: Later sources override earlier ones (higher priority).

⚠️  Missing required fields: api_key
   Exiting with code 1. Please provide these fields and try again.
   For help, run: python app.py --help

What this tells you:

  • Variable Status: See which fields are required vs optional, loaded vs missing
  • Source Tracking: Know exactly which source (defaults/env/cli/file) provided each value
  • Priority Order: Understand the resolution chain - later sources override earlier
  • Performance: Load times for each source (useful for optimization)
  • Validation: Missing required fields are caught immediately with clear error messages

Key Benefits:

  • 🔍 Complete Visibility: See exactly which source provides each value - no more guessing where config comes from
  • 📊 Priority Visualization: Understand the resolution order at a glance - see which source wins for each field
  • ⚡ Performance Metrics: Load times for each source - identify slow config sources
  • 🛡️ Validation: Missing required fields are caught before app starts - fail fast with clear errors
  • 📝 Self-Documenting: Help text generated from your model metadata - no manual documentation needed
  • 🎯 Zero Configuration: handle_cli_commands() adds all this with one line - no boilerplate

Real-World Scenarios:

  • Debugging: "Why is my app using the wrong port?" → python app.py -cv shows port comes from env, not CLI
  • Onboarding: New team member runs python app.py --help → sees all config options with descriptions
  • CI/CD: Missing required field? → -cv shows exactly what's missing before deployment fails
  • Multi-Environment: See which config file (system/user/app)
View on GitHub
GitHub Stars28
CategoryDevelopment
Updated18d ago
Forks0

Languages

Python

Security Score

95/100

Audited on Mar 6, 2026

No findings