Flexible configuration management with Pydantic validation, YAML/JSON/TOML support, and fluent API
Project description
🚀 PyConfigre
Configuration management for Python applications. Elegant, type-safe, and built for real-world needs.
PyConfigre bridges the gap between manual configuration management and heavyweight tools. It combines Pydantic's robust validation with support for multiple formats (YAML, JSON, TOML), environment variables, and a fluent API that reads like natural Python. Need the pipeline without a schema? PyConfigre has you covered with RawConfigBuilder. Whether you're building microservices, web applications, or CLI tools, PyConfigre handles configuration without getting in the way.
✨ Why PyConfigre?
Configuration shouldn't be complicated. Most tools either do too much or too little. PyConfigre fits the middle ground:
- For teams who want type safety without sacrificing simplicity
- For projects that need to merge configurations from multiple sources
- For developers who prefer explicit, readable code over magic configuration files
🎯 Core Features
- 📋 Multi-Format Loading: YAML, JSON, TOML, environment variables, or Python dictionaries
- ✓ Type-Safe Validation: Full Pydantic integration for bulletproof configuration
- 🔀 Smart Priority Handling: Effortlessly merge multiple sources (file → env → dict)
- ⛓️ Fluent API: Chainable methods that read naturally
- 🎯 Format Auto-Detection: Automatically identify and parse files
- 🐍 Modern Python: Full type hints and Python 3.10+ syntax
- ** Schema-Free Option**: Use
RawConfigBuilderwhen you just need a mergeddict— no Pydantic required - 🪶 Lightweight: Only
pydanticandpyyamlrequired for core functionality
🚀 Getting Started
Installation
# Basic installation (JSON & YAML support)
pip install pyconfigre
# Add TOML support
pip install pyconfigre[toml]
# With uv support
uv add pyconfigre
30-Second Example
from pydantic import BaseModel
from pyconfigre import ConfigBuilder
class AppConfig(BaseModel):
"""Your application configuration schema."""
debug: bool = False
port: int = 8000
host: str = "0.0.0.0"
database_url: str
# Load from file → environment → runtime overrides
config = (
ConfigBuilder(AppConfig)
.from_file("config.yaml")
.from_env("MYAPP_")
.from_dict({"debug": True})
.build()
)
# Type-safe access with IDE autocomplete
print(f"Server: {config.host}:{config.port}")
That's it. Three lines of configuration loading. Three sources merged intelligently. Full type safety throughout.
Schema-Free Alternative
Don't need typed validation? Use RawConfigBuilder to get a plain dictionary:
from pyconfigre import RawConfigBuilder
raw_config = (
RawConfigBuilder()
.from-file("config.yaml")
.from_env("MYAPP_")
.set("debug", True) # Explicit override
.build_dict()
)
# config is a plain dict — no schema needed
print(f"Port: {raw_config['port']}")
Same pipeline, same priority system, same multi-format support — just without the Pydantic model.
� Common Patterns
Pattern 1: File + Environment + Overrides
The most common real-world pattern:
config = (
ConfigBuilder(AppConfig)
.from_file("config/default.yaml") # Base configuration
.from_env("MYAPP_") # Environment overrides
.from_dict(runtime_overrides) # Runtime values (highest priority)
.build()
)
Priority order (lowest to highest): File → Environment → Dict → Explicit .set() method
Pattern 2: Environment-Specific Configuration
Load different configurations per environment:
import os
environment = os.getenv("ENV", "development")
config = (
ConfigBuilder(AppConfig)
.from_file(f"config/{environment}.yaml") # Environment-specific base
.from_file("config/local.yaml", optional=True) # Local dev overrides
.from_env("MYAPP_")
.build()
)
Pattern 3: Configuration with Nested Models
Structure complex configurations with nested Pydantic models:
from pydantic import BaseModel, Field
class DatabaseConfig(BaseModel):
host: str
port: int = 5432
username: str
password: str
database: str
class CacheConfig(BaseModel):
enabled: bool = True
ttl_seconds: int = 3600
backend: str = "redis"
class AppConfig(BaseModel):
app_name: str
debug: bool = False
database: DatabaseConfig
cache: CacheConfig = Field(default_factory=CacheConfig)
config = ConfigBuilder(AppConfig).from_file("config.yaml").build()
# Access nested values with full type safety
print(f"DB: {config.database.host}:{config.database.port}")
print(f"Cache TTL: {config.cache.ttl_seconds}s")
Pattern 4: Schema-Free Configuration
use RawConfigBuilder when you need the pipeline but not the validation — scripts, CLI tools, migrations, or quick prototypes:
from pyconfigre import RawConfigBuilder
# Full pipeline, plain dict output
raw_config = (
RawConfigBuilder()
.from_file("config/default.yaml")
.from_env("MYAPP_")
.from_dict({"timeout": 30})
.build_dict()
)
# inspect mid-chain without finalising
raw_builder = RawConfigBuilder().from_file("config.yaml")
print(raw_builder.peek()) # snapshot of current state
raw_builder.set("port", 9000)
raw_config = raw_builder.build_dict()
RawConfigBuilder shares the exact same loading, merging, and priority logic as ConfigBuilder — it just skips the Pydantic validation step.
Pattern 5: Validation with Constraints
Let Pydantic enforce your business rules:
from pydantic import BaseModel, Field
class AppConfig(BaseModel):
port: int = Field(default=8000, ge=1, le=65535) # Port range
workers: int = Field(default=4, ge=1, le=256) # Worker count
timeout: float = Field(default=30.0, gt=0) # Positive timeout
environment: str = Field(default="production", pattern="^(dev|staging|production)$")
# Invalid config raises validation error
config = ConfigBuilder(AppConfig).from_dict({"port": 99999}).build() # ❌ Fails validation
🎯 Configuration Priority
PyConfigre implements a simple, predictable priority system. Later sources override earlier ones:
File → Environment Variables → Dictionary → Explicit .set() method
↑ ↑
Lowest Priority Highest Priority
Example:
# YAML file has: debug: false, port: 8000
# ENV var: MYAPP_DEBUG=true
# Dict: {"port": 3000}
config = (
ConfigBuilder(AppConfig)
.from_file("config.yaml") # debug=false, port=8000
.from_env("MYAPP_") # debug=true (overrides file)
.from_dict({"port": 3000}) # port=3000 (overrides both)
.build()
)
# Result: debug=true, port=3000
This pattern matches how most systems expect configuration to work—sensible defaults in files, environment-specific overrides via env vars, and runtime tweaks via dictionaries.
📦 Supported Formats
| Format | File Extension | Dependency | Notes |
|---|---|---|---|
| YAML | .yaml, .yml |
pyyaml |
Human-readable, widely used |
| JSON | .json |
Built-in (stdlib) | Strict, universally supported |
| TOML | .toml |
tomllib (3.11+) or tomli | Clean syntax, configuration standard |
| Environment Variables | — | Built-in | Perfect for secrets and overrides |
| Python Dicts | — | Built-in | Runtime configuration |
🏗️ Real-World Example: API Service
Here's a complete, production-like example:
from pydantic import BaseModel, Field
from typing import Optional
from pyconfigre import ConfigBuilder
# Configuration schema
class DatabaseConfig(BaseModel):
host: str = "localhost"
port: int = Field(default=5432, ge=1, le=65535)
username: str
password: str
database: str
class LoggingConfig(BaseModel):
level: str = Field(default="INFO", pattern="^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$")
format: str = "json"
output: str = "stdout"
class AppConfig(BaseModel):
app_name: str = "my-api"
version: str = "1.0.0"
debug: bool = False
host: str = "0.0.0.0"
port: int = Field(default=8000, ge=1, le=65535)
database: DatabaseConfig
logging: LoggingConfig = Field(default_factory=LoggingConfig)
secret_key: Optional[str] = None
# Load configuration in your application startup
def setup_config() -> AppConfig:
"""Load and validate application configuration."""
return (
ConfigBuilder(AppConfig)
.from_file("config/default.yaml") # Defaults
.from_file(f"config/{os.getenv('ENV')}.yaml", optional=True)
.from_env("MYAPI_") # Override with env vars
.build()
)
# Usage
config = setup_config()
print(f"Starting {config.app_name} v{config.version}")
print(f"Listening on {config.host}:{config.port}")
print(f"Database: {config.database.host}")
Configuration file (config/default.yaml):
app_name: my-api
version: 1.0.0
debug: false
database:
host: localhost
username: app_user
password: ${DB_PASSWORD} # Can use env vars or load separately
database: app_db
logging:
level: INFO
Environment override (config/production.yaml):
debug: false
database:
host: prod-db.example.com
username: prod_user
logging:
level: WARNING
This approach gives you:
- ✅ Type-safe configuration with validation
- ✅ Environment-specific configurations
- ✅ Secrets via environment variables
- ✅ Sensible defaults that developers can override locally
- ✅ Production ready
🧪 Testing Configuration
import pytest
from pydantic import BaseModel, ValidationError
from pyconfigre import ConfigBuilder
class TestConfig(BaseModel):
feature_enabled: bool = False
api_key: str
def test_config_validation():
"""Test that invalid config raises ValidationError."""
with pytest.raises(ValidationError):
ConfigBuilder(TestConfig).from_dict({}).build() # Missing required api_key
def test_config_building():
"""Test successful configuration building."""
config = (
ConfigBuilder(TestConfig)
.from_dict({"api_key": "test-key"})
.set("feature_enabled", True)
.build()
)
assert config.feature_enabled is True
assert config.api_key == "test-key"
def test_config_priority():
"""Test configuration priority (file < env < dict)."""
config = (
ConfigBuilder(TestConfig)
.from_dict({"api_key": "from-dict", "feature_enabled": False})
.from_dict({"api_key": "from-second-dict"}) # Overrides
.build()
)
assert config.api_key == "from-second-dict" # Later dict wins
✅ Best Practices
- Define configuration schemas early – Use Pydantic models as your configuration contract
- Use environment variables for secrets – Never hardcode API keys, passwords, or tokens
- Provide sensible defaults – Make common values optional so developers have fewer things to configure
- Fail fast on invalid config – Call
.build()early in your app startup, not lazily - Test your configuration – Write tests that verify config loading from different sources
- Version your schema – Document breaking changes to your config structure
- Use type hints everywhere – Let IDE autocomplete guide developers using your app
🔐 Security Considerations
Never hardcode secrets:
# ❌ DON'T DO THIS
config = ConfigBuilder(AppConfig).from_dict({
"api_key": "sk-1234567890abcdef"
}).build()
# ✅ DO THIS
config = (
ConfigBuilder(AppConfig)
.from_file("config.yaml") # No secrets in file
.from_env("MYAPP_") # Get secrets from environment
.build()
)
Use strong validation:
from pydantic import BaseModel, Field
class SecureConfig(BaseModel):
api_key: str = Field(min_length=32) # Enforce minimum length
timeout: float = Field(gt=0) # Must be positive
environment: str = Field(pattern="^(dev|staging|prod)$") # Whitelist values
# Invalid config is rejected before it can cause problems
config = ConfigBuilder(SecureConfig).from_env("APP_").build() # Fails fast if invalid
Restrict file permissions:
# Ensure config files with sensitive data are readable only by your app
chmod 600 config/production.yaml
📊 Project Status
| Aspect | Status |
|---|---|
| Version | 0.2.0 |
| Python Support | 3.10, 3.11, 3.12, 3.13, 3.14 |
| Test Coverage | ~100% (200 tests) |
| Type Checking | Full MyPy strict compliance |
| CI/CD | GitHub Actions (multi-version testing) |
🤝 Contributing
Contributions are welcome! Whether it's:
- Bug reports or feature requests (open an issue)
- Documentation improvements
- Code contributions (fork and submit a PR)
Please follow the existing code style and include tests for new features.
📄 License
MIT License - see LICENSE file for details.
👨💻 Author
👤 Aditya Barik
Questions or feedback? Open an issue on GitHub.
Built with ❤️ for teams who care about clean, maintainable, type-safe Python code.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyconfigre-0.2.0.tar.gz.
File metadata
- Download URL: pyconfigre-0.2.0.tar.gz
- Upload date:
- Size: 24.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b24a2a2c1362400265cf8705a7593a4c01d25e28e34172a2e096dc6ed08b2804
|
|
| MD5 |
80fbaa831233df6476bdb1850a0fc0c1
|
|
| BLAKE2b-256 |
4017d3c3964ccbaa776003b0fcc4392c2b630c5a55b30db0b4124165e25bc571
|
Provenance
The following attestation bundles were made for pyconfigre-0.2.0.tar.gz:
Publisher:
pypi-publish.yml on aditya-barik/PyConfigre
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyconfigre-0.2.0.tar.gz -
Subject digest:
b24a2a2c1362400265cf8705a7593a4c01d25e28e34172a2e096dc6ed08b2804 - Sigstore transparency entry: 1633390401
- Sigstore integration time:
-
Permalink:
aditya-barik/PyConfigre@461cce5140f44f1ee8d2e71c0f021b59bb99981f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/aditya-barik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@461cce5140f44f1ee8d2e71c0f021b59bb99981f -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyconfigre-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pyconfigre-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
508391f490b901f16fe94f4e8fbc28831de3e73126cd34dfd5ad4c5ff51f28ef
|
|
| MD5 |
95be67bc9672ba4514573695c50464c2
|
|
| BLAKE2b-256 |
ffa3bbc18693a0b4288d09f751485647dbfba18522492e3e3e80187cfe2bd2c2
|
Provenance
The following attestation bundles were made for pyconfigre-0.2.0-py3-none-any.whl:
Publisher:
pypi-publish.yml on aditya-barik/PyConfigre
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyconfigre-0.2.0-py3-none-any.whl -
Subject digest:
508391f490b901f16fe94f4e8fbc28831de3e73126cd34dfd5ad4c5ff51f28ef - Sigstore transparency entry: 1633390410
- Sigstore integration time:
-
Permalink:
aditya-barik/PyConfigre@461cce5140f44f1ee8d2e71c0f021b59bb99981f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/aditya-barik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@461cce5140f44f1ee8d2e71c0f021b59bb99981f -
Trigger Event:
release
-
Statement type: