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.0.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.0-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

File details

Details for the file tnm_config-1.3.0.tar.gz.

File metadata

  • Download URL: tnm_config-1.3.0.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.0.tar.gz
Algorithm Hash digest
SHA256 261eda228c8472badafc933e6d5faf0b04f7e8f4d90b664d9d028d72bd7fd371
MD5 8fdd2be01301ca170f000ffddb506aa1
BLAKE2b-256 a7cc410539d7dc063aab45795ddaae816dfd5792cffb3107ec0792d7fdd9fcd3

See more details on using hashes here.

Provenance

The following attestation bundles were made for tnm_config-1.3.0.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.0-py3-none-any.whl.

File metadata

  • Download URL: tnm_config-1.3.0-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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ef6a118ddfbaaa7f46b360cc3782bf5464c5a2e30eb2069312894282a0d2cb3
MD5 2fd62e40842c764ee43d7f2a0b0d4604
BLAKE2b-256 32e037b73c03476487a0be47c9da77b053f48dbd49fbc77844a575ac8baa2d5b

See more details on using hashes here.

Provenance

The following attestation bundles were made for tnm_config-1.3.0-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