Spring Boot-style configuration for Python applications
Profiles allow you to maintain environment-specific configuration without code changes. SprigConfig’s profile system is runtime-driven—profiles are never read from configuration files.
A profile determines which overlay file is loaded on top of the base configuration:
| Profile | Overlay File |
|---|---|
dev |
application-dev.yml |
test |
application-test.yml |
prod |
application-prod.yml |
staging |
application-staging.yml |
<custom> |
application-<custom>.yml |
The base application.yml is always loaded first, then the profile overlay is merged on top.
Profiles are determined at runtime in this order:
cfg = load_config(profile="prod")
export APP_PROFILE=prod
cfg = load_config() # Uses "prod" from environment
When running under pytest, SprigConfig automatically uses the test profile:
# In test files, profile defaults to "test"
def test_something():
cfg = load_config() # Uses "test" profile
assert cfg["app.profile"] == "test"
If nothing else is specified, SprigConfig uses dev.
The active profile is always injected into the final configuration at app.profile:
cfg = load_config(profile="prod")
print(cfg["app.profile"]) # prod
This allows your application to know which profile is active at runtime.
Important: Do not set app.profile in your YAML files. SprigConfig ignores it with a warning:
# DON'T DO THIS - will be ignored
app:
profile: dev
The runtime always determines the profile, ensuring consistency between what’s loaded and what’s reported.
While you can use any profile name, these are the conventional profiles:
dev — DevelopmentLocal development environment with debug settings:
# application-dev.yml
server:
port: 9090
logging:
level: DEBUG
features:
hot_reload: true
debug_toolbar: true
test — TestingIsolated configuration for automated tests:
# application-test.yml
database:
url: sqlite:///:memory:
logging:
level: WARNING
features:
email: false # Don't send real emails in tests
prod — ProductionProduction-hardened configuration:
# application-prod.yml
server:
host: 0.0.0.0
database:
pool_size: 20
ssl: true
logging:
level: INFO
features:
debug_toolbar: false
SprigConfig includes safety checks for production:
When using the prod profile, application-prod.yml must exist. This prevents accidentally running with dev settings in production.
# Raises ConfigLoadError if application-prod.yml is missing
cfg = load_config(profile="prod")
DEBUG logging in production is blocked by default:
# application-prod.yml
logging:
level: DEBUG # Blocked unless allow_debug_in_prod is set
To allow DEBUG in production (not recommended):
# application-prod.yml
allow_debug_in_prod: true
logging:
level: DEBUG
If no logging level is specified in production, SprigConfig defaults to INFO.
You can create profiles for any environment:
# application-staging.yml
database:
host: staging-db.example.com
# application-qa.yml
features:
test_accounts: true
# application-local.yml
server:
port: 3000
Load them by name:
cfg = load_config(profile="staging")
cfg = load_config(profile="qa")
cfg = load_config(profile="local")
Each profile can have its own imports:
# application.yml
server:
port: 8080
imports:
- common/logging.yml
- common/security.yml
# application-dev.yml
imports:
- dev/mock-services.yml
- dev/debug-settings.yml
# application-prod.yml
imports:
- prod/monitoring.yml
- prod/security-hardening.yml
Import order:
See Merge Order for details.
Use the injected app.profile to make runtime decisions:
cfg = load_config()
if cfg["app.profile"] == "prod":
# Production-specific initialization
setup_monitoring()
enable_rate_limiting()
elif cfg["app.profile"] == "dev":
# Development helpers
enable_debug_toolbar()
Or access from metadata:
profile = cfg["sprigconfig._meta.profile"]
def test_production_config():
cfg = load_config(profile="prod", config_dir=Path("tests/config"))
assert cfg["database.ssl"] == True
import pytest
from sprigconfig import load_config
@pytest.fixture
def prod_config():
return load_config(profile="prod")
def test_pool_size(prod_config):
assert prod_config["database.pool_size"] >= 10
import os
def test_with_custom_profile(monkeypatch):
monkeypatch.setenv("APP_PROFILE", "staging")
cfg = load_config()
assert cfg["app.profile"] == "staging"
Your application.yml should define all keys with sensible defaults. Profiles should only override what’s different.
# application.yml - complete defaults
server:
host: localhost
port: 8080
timeout: 30
max_connections: 100
# application-prod.yml - only overrides
server:
host: 0.0.0.0
max_connections: 1000
If a value is the same in dev and test, keep it in the base file.
Don’t hardcode credentials in profile files:
# application-prod.yml
database:
password: ${DB_PASSWORD} # Or use ENC() for encrypted values
Ensure each profile loads successfully:
@pytest.mark.parametrize("profile", ["dev", "test", "prod"])
def test_profile_loads(profile):
cfg = load_config(profile=profile)
assert cfg["app.profile"] == profile