TEL
TEL, the Tesserae Expression Language, is a small, safe expression language and a Python library that parses and evaluates it. It lets plain YAML (or Python code) express conditions, repeaters and live values.
Status: alpha, package 0.1.0, language version 0.1. The API may change before 1.0 (a minor version may break it; see
docs/versioning.md). The design is indocs/language.md; what is protected, how it is checked and what is not guaranteed is indocs/security-review.md.
TEL needs Python 3.14 or later and has no dependencies:
pip install tesserae-el
It evaluates expressions and {{ }} templates (with limits and positions in errors), tracks the names
and attribute paths an evaluation read, takes host functions and methods, reads conditions
(compile_condition, select) and repeaters (compile_repeater, reconcile) for a host that
builds the tree, and builds expression text from Python values (fill, canonical).
import tel
expr = tel.compile_expression("'low' if battery < 20 else f'{battery}%'", language="0.1")
expr.evaluate(tel.MapScope({"battery": 63})) # '63%'
expr.evaluate(tel.MapScope({"battery": 12})) # 'low'
A conditional can also be written cond ? a : b: the same as a if cond else b, with the same
precedence (lowest, right-associative), and it may not be mixed with if/else without brackets.
In YAML, quote a value that contains :: a colon followed by a space ends a plain scalar, so
x: a ? b : c is a YAML error and - a ? b : c is read as a mapping.
A condition reads an expression as yes or no. The host owns the if:, elif: and else: keys;
select gives the index of the first condition that holds, or None for the else, and
select_tracked also gives the paths read by every condition tested up to the winner.
chain = [tel.compile_condition(t, language="0.1") for t in ("unread > 0", "muted")]
tel.select(chain, tel.MapScope({"unread": 0, "muted": True})) # 1
In YAML, give a condition or a key as text: quote a value with : in it, and quote a bare true
or 1, which YAML loads as a bool or an int (TEL refuses a non-text value with a TypeError).
To build an expression in Python, fill placeholders with values: each $name (outside strings and
comments) becomes a checked literal, so a value can only be data and a string is quoted. The result is
text for any compile_* function.
text = tel.fill("battery < $limit or name in $names", {"limit": 20, "names": ["a", "b"]})
# "battery < 20 or name in ['a', 'b']"
tel.fill_template("Hello {{ $who }}!", {"who": "Ada"}) # "Hello {{ 'Ada' }}!"
Two spellings of one expression have one canonical text, for comparing an expression built in Python with one written as text (compare within one run; do not store it):
tel.compile_expression("a<20", language="0.1").canonical # 'a < 20'
tel.compile_expression("c ? a : b", language="0.1").canonical # 'a if c else b'
A repeater reads a header (item in items, or i, item in enumerate(items)) and a key expression,
and gives the list's items with their keys. The host owns the for: and key: keys and builds the
nodes; each item's scope(parent) is the scope for its children.
rep = tel.compile_repeater("w in workspaces", key="w.id", name="Workspace", language="0.1")
items = rep.items(tel.MapScope({"workspaces": [{"id": 7}, {"id": 9}]}))
[(item.index, item.key) for item in items] # [(0, 7), (1, 9)]
# keep the state between calls: entries are new or kept, and keys can be removed
both = tel.MapScope({"workspaces": [{"id": 7}, {"id": 9}]})
result = rep.reconcile(both, tel.RepeaterState())
[e.name for e in result.entries] # ['Workspace_0001', 'Workspace_0002']
result = rep.reconcile(tel.MapScope({"workspaces": [{"id": 9}]}), result.state)
(result.removed, result.entries[0].name) # ((7,), 'Workspace_0002')
A file that holds the loop variable, the list and the key apart (the Shell's TDL) passes them separately, each with its own origin; the result is the same repeater:
rep = tel.compile_repeater_parts(
"w",
"workspaces",
key="w.id",
name="Workspace",
language="0.1",
names_origin=tel.Origin("A.tdl", 12, 9),
items_origin=tel.Origin("A.tdl", 12, 15),
key_origin=tel.Origin("A.tdl", 12, 35),
)
template = tel.compile_template("Battery {{ battery }}%, {{ state }}", language="0.1")
template.evaluate(
tel.MapScope({"battery": 63, "state": "discharging"})
) # 'Battery 63%, discharging'
A host adds its own functions and methods when it compiles. TEL calls only what the host listed:
expr = tel.compile_expression(
"format_number(total, 1) + ' ' + price.formatted()",
language="0.1",
functions={"format_number": lambda x, digits=0: f"{x:,.{digits}f}"},
methods={Price: {"formatted": Price.formatted}},
)
To know which names an evaluation actually read, so a host re-runs it only when one of them changes:
expr = tel.compile_expression("'low' if battery < 20 else state", language="0.1")
value, reads = expr.evaluate_tracked(tel.MapScope({"battery": 63, "state": "ok"}))
# value == 'ok', reads == {'battery', 'state'}; with battery=12 it reads only {'battery'}
A read is a name, or an attribute chain on a name: RootShell.DP1.Top_Bar.Width reports that whole
path, so an expression depends only on what it reads. Tracked.names gives the top-level names, and
expr.paths every path in the text, before any evaluation.
A scope is anything with lookup(name) (the value, or KeyError) and optionally names(). What TEL
may ask of it, and what it must do in return (quick, no effects, plain values), is written clause by
clause in docs/language.md section 12.2 and tested in
tests/test_scope_contract.py.
A template that is exactly one {{ }} part gives that part's value with its type; any other text with
parts gives text. Text with no part is a template that evaluates to itself (Template.is_text).
A bad expression is an error that says where (tel.TelError, with a stable code, a line and a
column), never a hang: every size and every loop is limited (tel.Limits). An expression can read
names from the scope you give it and call a few built-in functions and methods; it cannot reach
files, the process, the network, or an object's private attributes.
PyPI name: tesserae-el (not yet published). Import name: tel.
Development
uv pip install -e . --group dev # into the active venv (Python 3.14+)
python tools/check.py # ruff, format check, mypy (strict), pytest: what CI runs
python tools/wrapup.py # what is still behind before work is handed over
python tools/removal_check.py # break each rule of the language on purpose: a test must fail
tools/status.sh # branch, unpushed work, version, mailbox, board
Work is tracked on the TEL (tess-el) board
(python tools/board.py status). graphify update . rebuilds the local code graph.
Benchmarks: docs/benchmarks.md (the expr.py baseline). Behaviour reference: docs/reference-cases.md.
Agent instructions are in AGENTS.md; project skills are in .agents/skills/.
Versioning
See docs/versioning.md.
Licence
Metadata
Release files for tesserae-el 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| tesserae_el-0.1.0.tar.gz | 89.2 kB | With provenance | Details for tesserae_el-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tesserae_el-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for tesserae_el-0.1.0-py3-none-any.whl |
Total release size: 154.3 kB
Release files / tesserae_el-0.1.0.tar.gz
| Download URL | tesserae_el-0.1.0.tar.gz |
|---|---|
| Size | 89.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5547cd9a3658d1e6ca3b3b141d3168cf2df056e9b395eb3099c0c82265b32950
|
|
BLAKE2b-256 checksum How to use checksums |
3d26a1869806351ce789569c839b7d16e35a246a9417b85850c9c938285cf7a6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / tesserae_el-0.1.0-py3-none-any.whl
| Download URL | tesserae_el-0.1.0-py3-none-any.whl |
|---|---|
| Size | 65.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0c0ded88b48afccb8fd9478ccbe4301a7762d201c0d66595815c08c717dc1d7f
|
|
BLAKE2b-256 checksum How to use checksums |
9c2c7da8234d5df6bd4c5242af8d328c408b39c8e8983bd67d06ff804cbbd9ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for tesserae-el 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for tesserae_el-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.