Skip to main content

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="..." />
  • 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" />
  • Optional typed return: pass schema_type to load(...) to receive a validated instance (Pydantic BaseModel or a dataclass).

  • 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).
  • 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 is dict | list | str | int | ...
  • with schema_type: Type[T] → return is statically T

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.loaders entry-point group. Example in pyproject.toml of 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 marked url is 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. ConfigError preserves underlying exceptions as __cause__ — inspect with care.

Contributing & roadmap

Possible next steps / features you might request:

  • Add dacite support to robustly instantiate nested dataclasses (optional extra).
  • Provide a CLI tool tnm-config-validate to 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

tnm_config-1.2.0.tar.gz (16.1 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.2.0-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

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

Hashes for tnm_config-1.2.0.tar.gz
Algorithm Hash digest
SHA256 6588e979949afcc8f9fcc53bd72877f5592b413b38c8605e4fa9c9f8554aeb05
MD5 63052d479e5c14c0e6cbfbb6948b61bb
BLAKE2b-256 c358358c844aad1e50e622f2a140e4a009dde79a5fe4c31d7b7f553b0d867592

See more details on using hashes here.

Provenance

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

Hashes for tnm_config-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2b25c757eea8e5e25adbc042c0708a1a597046901ff171052601f591d8271c0b
MD5 26fb62b5fd8f1fe9fe09f03c3f585374
BLAKE2b-256 0e480074adcfb83ff493bf81d918f6fd53425dc3f7d3f690328cd6f87151bbf8

See more details on using hashes here.

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

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