Teptris — the TOML package v0.2.23
A native C-extension binding (no ctypes, no fallback) over libteptris v0.1.27. The API shape mirrors tomllib/tomli — loads/load raising TOMLDecodeError — plus a dumps writer in the tomli_w spirit. Datetimes follow the tomllib mapping.
Installation
pip install teptrispip resolves a wheel for your platform — manylinux/musllinux x86_64, aarch64 and armv7l (32-bit ARM, built under qemu), macOS arm64 and x86_64, Windows AMD64 and ARM64. CPython 3.10–3.14 gets a version-specific wheel (PGO-built, with the datetime C-API fast paths); everything else resolves thecp39-abi3 wheel, which covers every CPython ≥ 3.9 including future minors. The extension statically links the engine, so each wheel is a single self-contained module. Every other platform installs the sdist, which vendors the engine and compiles it with the installing interpreter’s compiler — a C compiler is required, and the build fails loudly without one.
Usage
import teptris
doc = teptris.loads('title = "teptris"\n[owner]\nname = "t"\n')
# => {'title': 'teptris', 'owner': {'name': 't'}}
teptris.dumps(doc) # canonical TOML — the engine emitter is the
# single formatting source (3.5-14.2x tomli_w)
try:
teptris.loads("a = [1,")
except teptris.TOMLDecodeError as e:
e.lineno, e.colno # the tomllib error attributesBoth directions are native. End to end (parse + materialize into Python objects) teptris is 4.8–6× rtoml (the rust-backed incumbent), 19–42× the stdlib’s tomllib, and hundreds of times tomlkit per shape.
The value contract
| TOML | Python |
|---|---|
| offset datetime | datetime.datetime (aware) |
| local datetime | datetime.datetime (naive) |
| local date | datetime.date |
| local time | datetime.time |
| integer / float / string / bool / array / table | the obvious Python types |
On the version-specific wheels (CPython 3.10–3.14, and the free-threaded cp314t wheels) datetimes materialize through the C datetime API directly — no generic-call machinery — with one cached timezone per offset; measured −56% on datetime-heavy loads and−72% on dumps against the abi3 path.
Many documents — loads_batch
docs = teptris.loads_batch([toml_a, toml_b, toml_c])
# => [dict, dict, dict] — same datetime contract as loads()
# the first failing document raises TOMLDecodeError
# carrying its line/columnLazy loading — loads_lazy
When most of the parsed tree is going to be ignored,loads_lazy returns a LazyNode wrapper that materializes host objects only along the paths actually accessed:
doc = teptris.loads_lazy(big_toml)
# one parse, no dicts/lists materialized yet
sku = doc["items"][0]["sku"].value() # scalars materialize at .value()
for item in doc["items"]: # arrays yield LazyNodes
print(item["sku"].value())
flat = doc.to_dict() # flatten eagerly, loads()-shapedLazyNode exposes kind(),value() (scalars only), len(), iteration (tables yield (key, LazyNode) pairs), subscripting, andto_dict()/to_list() for eager flattening. Datetimes materialize under the same contract as loads. The eager path is the floor for flatten-everything workloads; the lazy path wins for parse-once-touch-a-subset.
Planned-key materialization — Descriptor
teptris.Descriptor compiles a plan tree once, then materializes a whole document against it in one native pass — unplanned keys are never materialized (the twin of teptris-ruby's Teptris::Descriptor):
desc = teptris.Descriptor.build({"children": [
{"name": "name", "kind": "scalar"},
{"name": "port", "kind": "scalar"},
{"name": "hosts", "kind": "collection"}, # array of scalars
{"name": "items", "kind": "nested", "plan": { # recurses; spans
"children": [{"name": "id", "kind": "scalar"}]}, # arrays of tables
{"name": "everything_else", "kind": "raw"}, # untouched subtree
]})
doc = desc.walk(toml_string) # rows absent from the doc read as NoneKeys not in the plan never appear;walk raises TOMLDecodeError withline/column like every other public path.
Typed from 0.2.26
The package ships py.typed (PEP 561) with a_native.pyi stub for the C surface — strict mypy/pyright consumers type-check out of the box, and a mypy gate runs in CI. teptris.engine_version() reports the linked libteptris version; teptris.loads_lazy_batch([...])is the lazy twin of loads_batch. The Ruby gem carries the matching contract as RBS signatures with a steep gate.
Where to go next
- leptris/teptris-py — the canonical repository, with the per-shape language-tier tables.
- The C engine under the package
- The Ruby sibling gem
This is the curated guide. The repositories are canonical: when this page and the repo disagree, the repo wins. Full documentation lives with the source.