Metadata-Version: 2.4
Name: tomlstack
Version: 0.2.0rc1
Summary: TOML configuration loader with include/merge/interpolation
Project-URL: Homepage, https://github.com/wxzhao7/tomlstack
Project-URL: Repository, https://github.com/wxzhao7/tomlstack
Project-URL: Issues, https://github.com/wxzhao7/tomlstack/issues
Author: wxzhao7
License: MIT
License-File: LICENSE
Keywords: config,configuration,include,interpolation,toml
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Requires-Dist: twine>=5.1; extra == 'dev'
Provides-Extra: lint
Requires-Dist: ruff>=0.8; extra == 'lint'
Provides-Extra: test
Requires-Dist: pytest-cov>=5.0; extra == 'test'
Requires-Dist: pytest>=8.3; extra == 'test'
Provides-Extra: typing
Requires-Dist: mypy>=1.11; extra == 'typing'
Description-Content-Type: text/markdown

# tomlstack

`tomlstack` is a lightweight TOML config loader for Python 3.11+ with:

- top-level `include` loading
- include-tree inspection with raw and resolved paths
- deterministic merge by include order
- `${path}` interpolation with cycle/undefined checks
- node-level provenance (`origin`, `history`, `dependencies`)

tomlstack does not try to be a configuration framework.
It addresses two missing pieces in TOML: file composition and safe interpolation,
while keeping files self-contained and explainable.

## Install

```bash
pip install tomlstack
```

## Quick Start

`main.toml`:

```toml
include = [
    "./base.toml",
    "./prod.toml",
]

[db]
url = "postgres://${db.user}:${db.pass}@${db.host}:${db.port}"
```

`base.toml`:

```toml
[db]
user = "alice"
pass = "secret"
host = "localhost"
port = 5432
```

Python:

```python
from tomlstack import TomlNode, load

cfg = load("main.toml")
print(cfg["db"]["url"].raw)    # raw interpolation string
print(cfg["db"]["url"].value)  # resolved value
print(cfg["db"]["url"].origin)
print(cfg["db"]["url"].history)
print(cfg["db"]["url"].dependencies)
print(cfg["db"]["url"].explain())
print(cfg.raw)                   # raw configuration snapshot
print(cfg.to_dict())             # resolved configuration snapshot
```

## Include Semantics

- top-level `include` only; nested `include` is treated as normal data
- syntax: string or list of strings
- valid include path forms:
  - `./...` or `../...`
  - `@label/...` (label from `tomlstack.anchors`)
  - absolute path
- any other form raises an error with a path-format hint

### Meta Include Directives

```toml
[tomlstack.anchors]
proj = "./shared"
```

- anchors are local to the file that declares them
- anchor path values must be absolute or start with `./` or `../`

## Merge Rules

Load order for current file:

1. merge first include
2. merge second include
3. ...
4. merge current file (highest priority)

Conflict behavior:

- dict: recursive merge, later wins on key conflict
- list: later value replaces whole list
- scalar: later value replaces earlier

## Interpolation Semantics

- interpolation is resolved lazily by `cfg.resolve()`, `cfg.to_dict()`, or
  `node.value`
- path syntax supports dot and list index: `${db.apps[0]}`
- full-string interpolation (`"${db.port}"`) keeps source type
- embedded interpolation (`"postgres://${db.host}:${db.port}"`) allows only:
  - `str`, `int`, `float`, `bool`, `date`, `time`, `datetime`
- formatting syntax: `${path:spec}`
- for `date/time/datetime`, formatting uses `strftime`
- otherwise uses Python `format(value, spec)`
- invalid interpolation syntax and formatting failures raise `InterpolationError`
- undefined references raise `InterpolationUndefinedError`
- interpolation cycles raise `InterpolationCycleError`

Invalid TOML raises `TomlFormatError`; invalid include paths and anchors raise
`IncludeError`.

## Public API

- `cfg = load("f.toml")`
- `cfg.raw` — raw configuration snapshot
- `cfg.resolve()`
- `cfg.to_dict()` — resolved configuration snapshot
- `cfg.include_tree` — `IncludeNode` load-occurrence tree
- `cfg.include_tree.render()` — render raw include references
- `cfg.include_tree.render(absolute=True)` — include references with resolved paths
- `node: TomlNode = cfg["proj"][0]["path"]["foo"]`
- `node.raw`
- `node.value`
- `node.origin`
- `node.history`
- `node.dependencies` — direct interpolation dependencies
- `node.explain()` — transitive interpolation trace
- `node.preview()`
- `cfg.to_toml()` -> `NotImplementedError`

`cfg.raw`, `cfg.to_dict()`, `node.raw`, and `node.value` return independent data
snapshots; mutating their dictionaries or lists does not modify the loaded
configuration. `TomlNode` instances are created by configuration navigation and are
not constructed directly.

History records definitions of the same data path from lowest to highest priority.
When a list or value type is replaced, its old child paths are discarded. Resolving an
interpolation does not change the history of the node containing the expression.
Each history entry is a `TomlFile` with its raw `reference` and resolved absolute
`path`.

Dependencies describe interpolation separately from merge history. A full replacement
such as `target = "${source}"` leaves `target.history` at the file that defined
`target`, while `target.dependencies` records the source path and its history.

## Current Limitations

- interpolation path parser supports unquoted dot keys and numeric list indices
- no nested interpolation expressions
- `to_toml()` is not implemented yet

## Release To PyPI

Build package:

```bash
uv run --with build python -m build
```

Upload to TestPyPI first:

```bash
uv run --with twine python -m twine upload --repository testpypi dist/*
```

Verify install from TestPyPI:

```bash
python -m pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple tomlstack
```

Upload to PyPI:

```bash
uv run --with twine python -m twine upload dist/*
```
