Lightweight layered configuration loader with environment-aware overrides.
Project description
LiteConf
liteconf is a lightweight configuration loader focused on deterministic override rules, explicit layering, and ergonomic access patterns. It embraces boring tooling (plain YAML/JSON/TOML) while solving the painful parts typical projects hit:
- predictable ordering across
base/,local/, environment, and runtime overrides - structure-preserving deep merge (lists replace, dicts merge)
- attribute and dict access via a single
ConfigView - environment interpolation with type coercion
- opt-in validation hooks without mandating a heavyweight framework
Why another config loader?
Existing Python options shine in specific niches but often trade simplicity for power:
dynaconf,hydra,omegaconfship large abstraction layers, CLIs, or plugin registriespydantic-settingsandenvironscenter on.envfiles and type validation, not layered YAMLconfigparserandConfigObjstruggle with nested structures
liteconf keeps the learning curve flat while covering the 90% case of layered application configs.
Install
pip install liteconf
Quick Start (Practical)
Consider this minimal layout (two folders + one JSON):
conf/
base/
service.yml
local/
service.yml
extra.json
conf/base/service.yml
service:
url: https://api.base
retries: 2
features:
cache: false
logging: warn # defined only in base
conf/local/service.yml
service:
url: https://api.local # overrides base
retries: 3 # overrides base; later overridden by extra.json
features:
cache: true # overrides base
tracing: true # added by local
webhook: "${SERVICE_WEBHOOK:https://hooks.local/notify}" # env placeholder with default
extra.json
{
"service": {
"retries": 5,
"timeout": 10,
"endpoint": { "health": "/health" }
}
}
Load everything with explicit source order (later wins):
from pathlib import Path
from liteconf import ConfigManager, DirectorySource, FileSource, EnvSource
manager = ConfigManager([
DirectorySource(Path("conf/base"), optional=False),
DirectorySource(Path("conf/local"), optional=True),
FileSource(Path("extra.json"), optional=True),
# Optional: environment overlay, e.g. APP__SERVICE__TIMEOUT=30
EnvSource(prefix="APP", delimiter="__", infer_types=True),
])
cfg = manager.load()
# Value defined only in base
assert cfg.get("service.features.logging") == "warn"
# Values overridden by local
assert cfg.service.url == "https://api.local"
assert cfg.service.features.cache is True
# Value added by local
assert cfg.get("service.features.tracing") is True
# Overridden by local, then by extra.json (final)
assert cfg.service.retries == 5 # base=2 -> local=3 -> extra.json=5
# Values provided only by extra.json
assert cfg.service.timeout == 10
assert cfg.service.endpoint.health == "/health"
# You can use attribute-style or dotted lookups interchangeably
assert cfg.service.url == cfg.get("service.url")
# Save a copy if useful for debugging
cfg.save("debug_config.yml")
Notes:
- The filename forms the first key:
service.ymlbecomes top-level keyservice. - Attribute access (
cfg.service.url) and dotted lookups (cfg.get("service.url")) both work. - Source order matters: items from later sources override earlier ones.
Features
- deterministic source ordering; later sources override earlier ones
- automatic parsing for
.yml/.yaml,.json, and.toml ${ENV_VAR:default}interpolation inside string values- environment overlays with case-insensitive prefix matching
ConfigViewoffering.get()with dotted paths,.to_dict(),.as_dataclass()- stateless loader: creating a new manager or calling
reload()rereads from disk
Environment Overrides (Two Ways)
- Inline placeholders inside files (resolved at load time):
# in conf/local/service.yml
service:
webhook: "${SERVICE_WEBHOOK:https://hooks.local/notify}"
- If
SERVICE_WEBHOOKis set in the environment, its value is used. - Otherwise, the default
https://hooks.local/notifyis used.
- Environment overlay with a prefix (no file edits needed):
# Let’s suppose you want to raise the timeout via an env var
export APP__SERVICE__TIMEOUT=30
pytest # or run your app; the manager will pick it up
With EnvSource(prefix="APP", delimiter="__", infer_types=True), keys map as:
APP__SERVICE__TIMEOUT=30->service.timeout = 30(int)APP__SERVICE__FEATURES__CACHE=false->service.features.cache = False(bool)
Loading: One-Call vs Explicit Sources
-
Quick one-call loader for folders only:
from liteconf import load cfg = load(layers=[Path("conf/base"), Path("conf/local")]) # Recursively loads .yml/.yaml/.json/.toml; later folders override earlier
-
Explicit sources for full control (files, env, dict overlays, optional flags):
from liteconf import ConfigManager, DirectorySource, FileSource, EnvSource, DictOverlay manager = ConfigManager([ DirectorySource(Path("conf/base")), DirectorySource(Path("conf/local")), FileSource(Path("extra.json")), EnvSource(prefix="APP"), DictOverlay({"runtime": {"feature_flag": True}}), ]) cfg = manager.load()
Use load(...) when you just need layered folders; use ConfigManager when you want to precisely choose and order different kinds of sources.
File Precedence and Duplicate Stems
- Across sources: later sources override earlier ones.
- Within a single folder: files are applied in lexicographic filename order.
- If both
messaging.jsonandmessaging.ymlexist in the same folder:- One-call
load(...)(LayeredConfigLoader): both are merged; later filename wins for overlapping keys (usually YAML after JSON). - Explicit
DirectorySource(ConfigManager): later filename replaces the earlier file’s subtree at that key entirely.
- One-call
Recommendation: avoid keeping two files with the same stem (e.g., messaging.*) in the same folder. Prefer a single file per logical key per layer to keep intent crystal clear.
Project layout
liteconf/
|-- pyproject.toml
|-- README.md
|-- src/
| \-- liteconf/
| |-- __init__.py
| |-- core.py
| |-- errors.py
| |-- exceptions.py
| |-- loader.py
| |-- manager.py
| |-- merger.py
| |-- namespaces.py
| \-- sources.py
\-- tests/
|-- conftest.py
|-- fixtures/
| |-- base/
| | \-- messaging.yml
| |-- local/
| | \-- messaging.yml
| \-- prod/
| |-- messaging.json
| \-- messaging.yml
|-- test_loader.py
\-- test_manager.py
License
MIT — do anything you want, just keep the notice.
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 liteconf-1.2.4.tar.gz.
File metadata
- Download URL: liteconf-1.2.4.tar.gz
- Upload date:
- Size: 20.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da4f4c5b5f39ec12b0a5bd2056a2428e6be8b0f556cadc7cbc471cfeac2562e4
|
|
| MD5 |
9d4a7046cd5b446b318bba3143a9ae63
|
|
| BLAKE2b-256 |
e2de7468727a772599014cb9efc146ed389a796b88dd5b0631ba19c5aca506b0
|
File details
Details for the file liteconf-1.2.4-py3-none-any.whl.
File metadata
- Download URL: liteconf-1.2.4-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04edb7b3e76e89337cd1b7057cfdcea1f9ac1f039baf2fe0317a00defd0221e1
|
|
| MD5 |
400ccc8cf1a763a29e3c6343fd5a5355
|
|
| BLAKE2b-256 |
7b355c3363919e575cc75e3cb7a33b99370c688ad2e505926cb86df2a99c9767
|