Small config loader supporting YAML, JSON and XML with env support.
Project description
tnm-config
A lightweight configuration loader for Python. Supports YAML, JSON, XML (and is pluggable for other formats). Features environment-backed values, filesystem paths, optional typed validation (Pydantic models or dataclasses), and a loader registry for plugins.
Install
Basic
pip install tnm-config
Optional extras:
pip install "tnm-config[yaml]" # PyYAML support
pip install "tnm-config[xml]" # xmltodict support
pip install "tnm-config[all]" # yaml + xml + pydantic extras (if provided)
If you want schema_type validation with Pydantic models:
pip install pydantic
Quick API
Import:
from pathlib import Path
from tnm.config import ConfigLoader
from tnm.config.errors import ConfigError
Basic usage:
loader = ConfigLoader(Path("config/example_config.yaml"))
# Raw resolved dict/list/scalar
cfg = loader.load("elasticsearch", project_root_callback=lambda: Path.cwd())
# With schema (pydantic model or dataclass):
cfg_obj = loader.load("elasticsearch", project_root_callback=lambda: Path.cwd(), schema_type=MyModel)
Type hint behaviour:
- If
schema_type is None: return is a parsed JSON-like type (dict | list | str | int | ...). - If
schema_typesupplied: return isschema_type.
Supported file marker syntax
Environment-backed values
These let you source values from environment variables with a type hint.
YAML:
hosts: !env [ ES_HOSTS, list:url, http://127.0.0.1:9200 ]
JSON:
"hosts": { "__env__": ["ES_HOSTS", "list:url", "http://127.0.0.1:9200"] }
XML:
<hosts>
<env var="ES_HOSTS" type="list:url" default="http://127.0.0.1:9200"/>
</hosts>
Supported casts:
str,int,float,boolurl(validates that parsed URL has scheme + host)list:str,list:url(comma separated values)
If env var not set and no default provided, a EnvVarNotSetError is raised.
Project-relative paths & preserved tokens
YAML !path and JSON/XML mapping variants are supported.
YAML:
templates_dir: !path extras/templates
JSON:
"templates_dir": { "__path__": { "value": "extras/templates" } }
XML:
<templates_dir>
<path value="extras/templates"/>
</templates_dir>
Preserved tokens inside strings are supported in both %token% and {{ token }} forms:
%project_root%,%project_dir%,{{ project_root }}resolved via callerproject_root_callback().%home%,%user_home%,{{ home }}the current OS user's home directory%user%,%username%,{{ user }}same ashometoken
You can mix tokens with path fragments:
templates_dir: "%project_root%/extras/templates"
local_cache: "{{ home }}/.myapp/cache"
You may also use an explicit mapping form (recommended for JSON/XML):
"templates_dir": {
"__path__": {
"value": "{{project_root}}/data/json/es_queries"
}
}
Important: when resolving tokens that require the project's root (e.g. %project_root%) you must pass a zero-argument callable project_root_callback to load(...). Example:
cfg = loader.load("elasticsearch", project_root_callback=lambda: Path.cwd())
If a token requires the project root but you didn't provide the callback, ConfigError will be raised with a helpful message.
Example config files
YAML (example_config.yaml):
elasticsearch:
hosts: !env [ ES_HOSTS, list:url, http://127.0.0.1:9200 ]
username: !env [ ES_USERNAME, str, demo_user ]
password: !env [ ES_PASSWORD, str, demo_pass ]
templates_dir: !path "{{project_root}}/extras/templates"
timeout: 10
version: 8.15
JSON (example_config.json):
{
"elasticsearch": {
"hosts": { "__env__": ["ES_HOSTS", "list:url", "http://127.0.0.1:9200"] },
"username": { "__env__": ["ES_USERNAME", "str", "demo_user"] },
"password": { "__env__": ["ES_PASSWORD", "str", "demo_pass"] },
"templates_dir": { "__path__": { "value": "{{project_root}}/extras/templates" } },
"timeout": 10,
"version": 8.15
}
}
XML (example_config.xml):
<?xml version="1.0" encoding="utf-8"?>
<elasticsearch>
<hosts>
<env var="ES_HOSTS" type="list:url" default="http://127.0.0.1:9200"/>
</hosts>
<username>
<env var="ES_USERNAME" type="str" default="demo_user"/>
</username>
<password>
<env var="ES_PASSWORD" type="str" default="demo_pass"/>
</password>
<templates_dir>
<path value="{{project_root}}/extras/templates"/>
</templates_dir>
<timeout>10</timeout>
<version>8.15</version>
</elasticsearch>
All three examples are semantically equivalent. Scalar values (numbers, strings, booleans) may be present anywhere and will be returned as-is.
Typed schemas (validation)
You can pass a schema_type to load(...). Supported schema types:
- Pydantic
BaseModel. - Plain Python
dataclass. - Any callable/class that accepts the resolved dict or scalar in its constructor.
from pydantic import BaseModel
from tnm.config import ConfigLoader
class ElasticCfg(BaseModel):
hosts: list[str]
username: str | None = None
password: str | None = None
templates_dir: str | None = None
loader = ConfigLoader(Path("config/example_config.yaml"))
cfg_obj = loader.load("elasticsearch", project_root_callback=lambda: Path.cwd(), schema_type=ElasticCfg)
If validation fails, a ConfigError is raised (with the underlying validation exception as __cause__).
Loader registry & plugin entry-point
There is a loader registry so additional formats can be added.
Runtime registration:
from tnm.config.loaders import register_loader, get_loader_for_suffix
register_loader(".ini", lambda: MyIniLoader())
loader = get_loader_for_suffix(".ini")
data = loader.load(Path("config.ini"), project_root_callback=lambda: Path.cwd())
Errors & exceptions
All package-specific exceptions are subclasses of tnm.config.errors.ConfigError.
Key exceptions:
ConfigError– generic parse/resolve/validation error (base).EnvVarNotSetError– env var referenced by!env/__env__/<env/>is not set and no default provided.InvalidURLError– env var declared asurlis invalid.
Usage:
from tnm.config import ConfigLoader, ConfigError
try:
cfg = ConfigLoader(Path("config.yaml")).load("elasticsearch", project_root_callback=my_cb)
except ConfigError as exc:
# inspect exc.__cause__ for underlying error details (if any)
print("Config failed:", exc)
Security & safety
- YAML parsing uses
safe_loadand custom safe constructors. Do not parse untrusted YAML. - Be cautious with logging or printing secrets from config (passwords, tokens).
- When using
schema_type, underlying validation errors are preserved in__cause__to help debugging — avoid leaking them to users.
Using preserved tokens:
es.yaml:
elasticsearch:
templates_dir: "%project_root%/extras/templates"
backup_dir: "{{ home }}/.cache/myapp/backups"
Load:
cfg = ConfigLoader(Path("es.yaml")).load("elasticsearch", project_root_callback=lambda: Path("/srv/myclient"))
Contributing & roadmap
Possible next steps:
- Add
daciteoptional extra for full dataclass support (nested dataclasses). - Add
tnm-config-validateCLI to validate a config against a schema. - Add caching for parsed config and file watch invalidation.
- Add small JSON Schema adapter for validating raw config shapes.
Contributions via PRs are welcome.
License
MIT
Project details
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 tnm_config-1.3.1.tar.gz.
File metadata
- Download URL: tnm_config-1.3.1.tar.gz
- Upload date:
- Size: 16.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ccb5a056c2efa9e73062817bdd95b212661abf6f1b6e030a1e3e66c944288671
|
|
| MD5 |
8da450b93e6292b03b0b7344c308b2ea
|
|
| BLAKE2b-256 |
e7946613f4cbee7e380d9c8f162b1d4f79713e3d6d5e782b6bc829401d2efbde
|
Provenance
The following attestation bundles were made for tnm_config-1.3.1.tar.gz:
Publisher:
publish-to-pypi.yml on mpamba/tnm-config
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tnm_config-1.3.1.tar.gz -
Subject digest:
ccb5a056c2efa9e73062817bdd95b212661abf6f1b6e030a1e3e66c944288671 - Sigstore transparency entry: 621382944
- Sigstore integration time:
-
Permalink:
mpamba/tnm-config@9ef779f42b405ae8c66069c3f18759c4392aa97a -
Branch / Tag:
refs/heads/releases - Owner: https://github.com/mpamba
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@9ef779f42b405ae8c66069c3f18759c4392aa97a -
Trigger Event:
push
-
Statement type:
File details
Details for the file tnm_config-1.3.1-py3-none-any.whl.
File metadata
- Download URL: tnm_config-1.3.1-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac7c6dd4f8c38e37b9d5a1c61088326272a8947a39a6020403111e31b2f6c52e
|
|
| MD5 |
793b6dbc3c98f8a152cf0c2ea79cce48
|
|
| BLAKE2b-256 |
ca62c90f5ddfe6c089cef1971ac4b674dbfb8f6ae9408188bfd14d556339733c
|
Provenance
The following attestation bundles were made for tnm_config-1.3.1-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on mpamba/tnm-config
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tnm_config-1.3.1-py3-none-any.whl -
Subject digest:
ac7c6dd4f8c38e37b9d5a1c61088326272a8947a39a6020403111e31b2f6c52e - Sigstore transparency entry: 621382947
- Sigstore integration time:
-
Permalink:
mpamba/tnm-config@9ef779f42b405ae8c66069c3f18759c4392aa97a -
Branch / Tag:
refs/heads/releases - Owner: https://github.com/mpamba
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@9ef779f42b405ae8c66069c3f18759c4392aa97a -
Trigger Event:
push
-
Statement type: