SprigConfig is a lightweight, opinionated configuration system for Python applications. It provides layered YAML loading, profile overlays, environment variable expansion, recursive imports, safe secret handling, and detailed metadata tracking designed for clarity, reproducibility, and debuggability.
This updated README reflects the current, expanded architecture of SprigConfig —
including its test infrastructure, .env handling model, and secret‑management APIs.
⭐ Key Features
✔️ Profile Injection (Runtime-Driven)
Profiles are never taken from files. The active profile comes from:
load_config(profile=...)APP_PROFILEpytest→"test"- Otherwise →
"dev"
Injected into final config as:
app:
profile: <active>
If a YAML file contains app.profile, it is ignored with a warning.
✔️ Layered YAML Merging (Deep Merge)
SprigConfig merges:
application.ymlapplication-<profile>.ymlimports: [file1.yml, file2.yml, …]
Features include:
- Recursive dictionary merging
- Override collision warnings
- Partial merge clarity
- Preservation of source metadata
✔️ Environment Variable Expansion
Patterns:
${VAR}
${VAR:default}
Expanded at load time. Missing variables fall back to defaults.
✔️ Secure Secret Handling
Values formatted as:
ENC(<ciphertext>)
are mapped to LazySecret objects.
- Decryption is lazy (on
.get()) - Uses global Fernet key via
APP_SECRET_KEY - Supports global key providers
- Secrets redacted during dumps unless explicitly allowed
✔️ Dependency Injection
Spring Boot-style dependency injection for cleaner code. Bind config values using descriptors and decorators instead of explicit Config.get() calls:
Field-Level Binding:
from sprigconfig import ConfigValue
class MyService:
db_url: str = ConfigValue("database.url")
timeout: int = ConfigValue("service.timeout", default=30)
api_key: str = ConfigValue("api.key", decrypt=True)
service = MyService()
print(service.db_url) # Resolved from config
Class-Level Binding:
from sprigconfig import ConfigurationProperties
@ConfigurationProperties(prefix="database")
class DatabaseConfig:
url: str
port: int
username: str
db = DatabaseConfig()
print(db.url) # Auto-bound from config["database"]["url"]
Function Parameter Injection:
from sprigconfig import config_inject
@config_inject
def connect_db(
host: str = ConfigValue("database.host"),
port: int = ConfigValue("database.port", default=5432)
):
return connect(host, port)
connect_db() # Uses config values
connect_db(host="override") # Override specific params
Features:
- Type conversion based on type hints (int, float, bool, str, list, dict)
- LazySecret handling with configurable
decryptparameter - Nested object auto-binding
- Clear error messages with full context
See Dependency Injection Explained for implementation details.
✔️ Dynamic Class Instantiation
Hydra-style _target_ support for instantiating classes directly from configuration. Perfect for hexagonal architecture with swappable adapters:
Config (YAML):
adapters:
database:
_target_: myapp.adapters.PostgresAdapter
host: localhost
port: 5432
pool_size: 10
Python:
from sprigconfig import ConfigSingleton, instantiate
cfg = ConfigSingleton.get()
db = instantiate(cfg["adapters"]["database"])
# Returns: PostgresAdapter(host="localhost", port=5432, pool_size=10)
Features:
- Automatic parameter extraction from
__init__signature - Type conversion based on type hints (int, float, bool, str, list, dict)
- Recursive instantiation for nested
_target_objects - Seamless integration with
@config_injectdecorator - Control flags:
_recursive_=False,_convert_types_=False
See instantiate() Documentation for full details.
✔️ Import Chains
Inside any YAML file:
imports:
- features.yml
- security.yml
SprigConfig resolves imports relative to APP_CONFIG_DIR or the config root
and detects circular imports.
✔️ Metadata Injection
Every loaded config includes:
sprigconfig._meta:
profile: <active>
sources: [list of resolved files]
import_trace: <graph of import relationships>
This helps debugging and auditing.
✔️ BOM-Safe YAML Reads
UTF‑8 with BOM (utf-8-sig) is automatically sanitized so Windows-created
files don’t introduce odd keys like server.
📦 Installation
pip install sprig-config
# or
poetry add sprig-config
📁 Project Structure
sprigconfig/
config_loader.py
config.py
lazy_secret.py
deepmerge.py
exceptions.py
...
docs/
README_AI_Info.md
(future docs go here)
tests/
conftest.py
conftest.md
test_*.py
test_*.md
config/
Documentation Strategy
- docs/ → Project-wide documentation (AI disclosure, architecture notes)
- tests/ → Each test module has matching
.mdexplaining its purpose - conftest.md → Documentation for the test framework itself
This ensures the entire system is self-explaining.
📂 Configuration Layout Example
config/
application.yml
application-dev.yml
application-test.yml
application-prod.yml
features.yml
override.yml
application.yml
server:
port: 8080
logging:
level: INFO
application-dev.yml
server:
port: 9090
imports:
- features.yml
- override.yml
override.yml
server:
port: 9999
features:
auth:
methods: ["password", "oauth"]
⚙️ Runtime Selection & Profile Behavior
SprigConfig determines profile → merges → injects profile → processes imports.
from sprigconfig import load_config
cfg = load_config(profile="dev")
print(cfg["server"]["port"]) # 9999
print(cfg["app"]["profile"]) # dev
🔐 Secret Handling with LazySecret
secrets:
db_user: ENC(gAAAAA...)
db_pass: ENC(gAAAAA...)
val = cfg["secrets"]["db_pass"]
assert isinstance(val, LazySecret)
print(val.get()) # plaintext
LazySecrets are:
- Safe by default
- Not decrypted unless
.get()is called - Redacted in dumps
📜 .env Resolution Model
SprigConfig supports configuration directory override via:
load_config(config_dir=...)APP_CONFIG_DIR.envin the project root- Test overrides (
--env-path) - Default:
./config
.env example:
APP_CONFIG_DIR=/opt/myapp/config
APP_SECRET_KEY=AbCdEf123...
🧪 Test Suite Overview
SprigConfig has a documented, extensible test architecture.
Test categories:
- Config mechanics
- Metadata & import tracing
- Deep merge
- Profile overlay behavior
- LazySecret & crypto handling
- CLI serialization tests
- Integration tests with full directory copies
Documentation-per-test:
Every test module includes a paired .md file explaining its purpose and architecture.
🧰 Test CLI Flags (from conftest.py)
| Flag | Purpose |
|---|---|
--env-path |
Use a custom .env file during tests |
--dump-config |
Print merged config for debugging |
| `--dump-config-format yaml | json` |
--dump-config-secrets |
Resolve LazySecrets |
--dump-config-no-redact |
Show plaintext secrets |
--debug-dump=file.yml |
Write merged config snapshot |
RUN_CRYPTO=true |
Enable crypto-heavy tests |
These make the test suite extremely reproducible and transparent.
🛡️ Production Guardrails
When profile = prod:
- Missing
logging.level→ default toINFO logging.level: DEBUGblocked unlessallow_debug_in_prod: true- Missing
application-prod.yml→ error - Missing
application-test.yml(when test) → error
🔗 Programmatic Access
from pathlib import Path
from sprigconfig import ConfigLoader
loader = ConfigLoader(config_dir=Path("config"), profile="dev")
cfg = loader.load()
print(cfg.get("server.port"))
print(cfg.to_dict())
🧭 Migration Notes
- Remove
app.profilefrom YAML files; runtime decides profile - Use imports for modularizing config trees
- Secrets should always be stored as encrypted
ENC(...)values
Versioning
SprigConfig follows Semantic Versioning:
- MAJOR versions introduce breaking changes
- MINOR versions add backward-compatible functionality
- PATCH versions contain backward-compatible bug fixes
Pre-release versions (e.g. rc1) indicate a release candidate.
They are feature-complete but may receive final fixes before a stable release
and are not recommended for production use unless explicitly intended for testing.
📚 Additional Documentation
Developer-focused documentation is available under the docs/ directory:
- 📘 Developer Guide
- 🧭 Roadmap
- 📝 Changelog
These documents cover contributor workflows, test mechanics, Git usage, CI/release processes, and internal design notes.
📄 License
MIT
Release files for sprig-config 1.4.13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sprig_config-1.4.13.tar.gz | 184.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sprig_config-1.4.13-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 320.9 kB
Release files / sprig_config-1.4.13.tar.gz
| Download URL | sprig_config-1.4.13.tar.gz |
|---|---|
| Size | 184.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
05c9d4e0d774731ca32931527431c66da21d3b548083576cd2d810fd96a87d73
|
|
BLAKE2b-256 checksum How to use checksums |
f3b8d9efb34ad09e76e79019bb8516513e8aac19bfd0551b1cdb12bfd2559eb6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency logRelease files / sprig_config-1.4.13-py3-none-any.whl
| Download URL | sprig_config-1.4.13-py3-none-any.whl |
|---|---|
| Size | 136.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f17af6ce0c08b766ff2db526f80fcd81508b210c70ec4b1b50df7d26237ba7a2
|
|
BLAKE2b-256 checksum How to use checksums |
f61e46115765bb812df7daccfd81a8675784c4d5053873e4e67495c4d7e70bc5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency log