Skip to main content

PyPI version Security Scan

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:

  1. load_config(profile=...)
  2. APP_PROFILE
  3. pytest"test"
  4. 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:

  1. application.yml
  2. application-<profile>.yml
  3. imports: [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 decrypt parameter
  • 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_inject decorator
  • 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 .md explaining 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:

  1. load_config(config_dir=...)
  2. APP_CONFIG_DIR
  3. .env in the project root
  4. Test overrides (--env-path)
  5. 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 to INFO
  • logging.level: DEBUG blocked unless
    allow_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.profile from 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:

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)

Source distribution for sprig-config 1.4.13
File Size Uploaded
sprig_config-1.4.13.tar.gz 184.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sprig-config 1.4.13
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.4.13 This release

2 release files

1.4.12

2 release files

1.4.11

2 release files

1.4.10

2 release files

1.4.9

2 release files

1.4.8

2 release files

1.4.7

2 release files

1.4.6

2 release files

1.4.5

2 release files

1.4.4

2 release files

1.4.3

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.5

2 release files

1.2.4

2 release files

1.2.3

2 release files

1.2.2

2 release files

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