Skip to main content

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_type supplied: return is schema_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, bool
  • url (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 caller project_root_callback().
  • %home%, %user_home%, {{ home }} the current OS user's home directory
  • %user%, %username%, {{ user }} same as home token

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 as url is 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_load and 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 dacite optional extra for full dataclass support (nested dataclasses).
  • Add tnm-config-validate CLI 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

tnm_config-1.3.1.tar.gz (16.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tnm_config-1.3.1-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

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

Hashes for tnm_config-1.3.1.tar.gz
Algorithm Hash digest
SHA256 ccb5a056c2efa9e73062817bdd95b212661abf6f1b6e030a1e3e66c944288671
MD5 8da450b93e6292b03b0b7344c308b2ea
BLAKE2b-256 e7946613f4cbee7e380d9c8f162b1d4f79713e3d6d5e782b6bc829401d2efbde

See more details on using hashes here.

Provenance

The following attestation bundles were made for tnm_config-1.3.1.tar.gz:

Publisher: publish-to-pypi.yml on mpamba/tnm-config

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for tnm_config-1.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ac7c6dd4f8c38e37b9d5a1c61088326272a8947a39a6020403111e31b2f6c52e
MD5 793b6dbc3c98f8a152cf0c2ea79cce48
BLAKE2b-256 ca62c90f5ddfe6c089cef1971ac4b674dbfb8f6ae9408188bfd14d556339733c

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page