Small config loader supporting YAML, JSON and XML with env support.
Project description
tnm-config
Lightweight, well-typed config loader for Python projects with support for environment-based valuexample_config. Designed to be DI-friendly, testable, and easy to integrate into real apps (FastAPI, CLI tools, workers, etc.).
Features
-
Can be extended to load other file formats (INI, .conf, TOML, ...)
-
Resolve environment-backed values:
- YAML:
!env [VAR, type, default?] - JSON:
{"__env__": ["VAR", "type", default?]} - XML:
<env var="VAR" type="type" default="..." />
- YAML:
-
Supported env casts:
str,int,float,bool,url,list:str,list:url. -
Resolve project-relative paths:
- YAML:
!path [value, project_relative] - JSON:
{"__path__": {"value": "...", "project_relative": true}} - XML:
<path value="..." project_relative="true" />
- YAML:
-
Optional typed return: pass
schema_typetoload(...)to receive a validated instance (PydanticBaseModelor adataclass). -
Loader registry & plugin support:
- built-in loaders auto-register for
.yaml/.yml,.json,.xml, - register custom loaders at runtime with
tnm.config.loaders.register_loader(...), - optional entry-point discovery group:
tnm_config.loaders(so other packages can register loaders via packaging metadata).
- built-in loaders auto-register for
-
Minimal base deps; optional extras:
yaml,xml,pydantic.
Quick install
Base (JSON + .env support):
pip install tnm-config
Optional extras:
# YAML support
pip install "tnm-config[yaml]"
# XML support
pip install "tnm-config[xml]"
# Both + pydantic
pip install "tnm-config[all]"
If you intend to use schema_type with Pydantic models:
pip install pydantic
Quick API
Importing (the package publishes into the tnm namespace):
from pathlib import Path
from tnm.config import ConfigLoader, ConfigError
# or
from tnm.config.loader import ConfigLoader
Basic usage:
loader = ConfigLoader(Path("config/example_config.yaml"))
# Basic: returns raw resolved structure (dict/list/scalar)
cfg = loader.load("elasticsearch", project_root_callback=lambda: Path.cwd())
# With schema (pydantic model or dataclass), returns typed instance:
inst = loader.load("elasticsearch", project_root_callback=lambda: Path.cwd(), schema_type=MyPydanticModel)
Type hints: load is annotated with overloads so static type-checkers infer:
- no
schema_type→ return isdict | list | str | int | ... - with
schema_type: Type[T]→ return is staticallyT
File format examples
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 [ extras/templates, true ]
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": "extras/templates",
"project_relative": true
}
}
}
}
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="extras/templates" project_relative="true"/>
</templates_dir>
</elasticsearch>
All three examples are semantically equivalent for the loader.
project_root_callback (recommended)
The loader expects a zero-argument callable that returns a Path or str. This callable is called whenever the
config contains a project_relative path that needs resolution.
from pathlib import Path
def my_project_root() -> Path:
# compute lazily, read an env var, or return a known path
return Path("/home/my-client-app")
cfg = loader.load("elasticsearch", project_root_callback=my_project_root)
Loader registry & plugins
tnm-config includes a small loader registry to make adding new formats easy:
- Get a loader for a suffix:
from tnm.config.loaders import get_loader_for_suffix
loader = get_loader_for_suffix(".ini")
if loader is not None:
data = loader.load(Path("config.ini"), project_root_callback=lambda: Path.cwd())
- Register a loader at runtime:
from tnm.config.loaders import register_loader
register_loader(".ini", lambda: MyIniLoader())
- Entry-point discovery (optional): packages may register loaders via the
tnm_config.loadersentry-point group. Example inpyproject.tomlof a plugin:
[project.entry-points."tnm_config.loaders"]
".ini" = "mypkg.loaders:IniLoader"
This lets other distributions add loaders without editing tnm-config itself.
Using typed schemas
Pydantic model
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)
# cfg_obj is a Pydantic model instance (validated)
If validation fails, tnm.config.errors.ConfigError is raised (with the underlying validation error set as__cause__).
Dataclass
from dataclasses import dataclass
from tnm.config import ConfigLoader
@dataclass
class DCfg:
name: str
port: int
loader = ConfigLoader(Path("config.json"))
obj = loader.load("service", project_root_callback=lambda: Path.cwd(), schema_type=DCfg)
Dataclass instantiation is direct (if you want richer dataclass conversion, consider adding dacite as an optional
extra).
Errors & exceptions
All package-specific exceptions are subclasses of tnm.config.errors.ConfigError:
EnvVarNotSetError— an env var referenced by!env/__env__/<env/>is not set and no default was provided.InvalidURLError— an env var markedurlis invalid.ConfigError— generic parse/resolve/validation errors, including schema instantiation failures.
Handle them like:
from tnm.config import ConfigLoader, ConfigError
try:
cfg = ConfigLoader(Path("example_config.yaml")).load("elasticsearch", project_root_callback=lambda: Path.cwd())
except ConfigError as exc:
# exc.__cause__ holds the original error (if any)
print("Config failed:", exc)
Security & safety
- Do not process untrusted YAML content.
- Avoid printing or logging secrets from config valuexample_config.
ConfigErrorpreserves underlying exceptions as__cause__— inspect with care.
Contributing & roadmap
Possible next steps / features you might request:
- Add
dacitesupport to robustly instantiate nested dataclasses (optional extra). - Provide a CLI tool
tnm-config-validateto validate a config file against a schema. - Add caching of parsed config filexample_config.
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.2.0.tar.gz.
File metadata
- Download URL: tnm_config-1.2.0.tar.gz
- Upload date:
- Size: 16.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6588e979949afcc8f9fcc53bd72877f5592b413b38c8605e4fa9c9f8554aeb05
|
|
| MD5 |
63052d479e5c14c0e6cbfbb6948b61bb
|
|
| BLAKE2b-256 |
c358358c844aad1e50e622f2a140e4a009dde79a5fe4c31d7b7f553b0d867592
|
Provenance
The following attestation bundles were made for tnm_config-1.2.0.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.2.0.tar.gz -
Subject digest:
6588e979949afcc8f9fcc53bd72877f5592b413b38c8605e4fa9c9f8554aeb05 - Sigstore transparency entry: 586350205
- Sigstore integration time:
-
Permalink:
mpamba/tnm-config@bfd1e16100c359acefaaed99c2f6c0d58396fa38 -
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@bfd1e16100c359acefaaed99c2f6c0d58396fa38 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tnm_config-1.2.0-py3-none-any.whl.
File metadata
- Download URL: tnm_config-1.2.0-py3-none-any.whl
- Upload date:
- Size: 14.5 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 |
2b25c757eea8e5e25adbc042c0708a1a597046901ff171052601f591d8271c0b
|
|
| MD5 |
26fb62b5fd8f1fe9fe09f03c3f585374
|
|
| BLAKE2b-256 |
0e480074adcfb83ff493bf81d918f6fd53425dc3f7d3f690328cd6f87151bbf8
|
Provenance
The following attestation bundles were made for tnm_config-1.2.0-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.2.0-py3-none-any.whl -
Subject digest:
2b25c757eea8e5e25adbc042c0708a1a597046901ff171052601f591d8271c0b - Sigstore transparency entry: 586350244
- Sigstore integration time:
-
Permalink:
mpamba/tnm-config@bfd1e16100c359acefaaed99c2f6c0d58396fa38 -
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@bfd1e16100c359acefaaed99c2f6c0d58396fa38 -
Trigger Event:
push
-
Statement type: