tomlrange
Parse and validate { from = …, to = … } tables. Not a TOML parser —
feed it the dicts tomllib already produced.
The four spellings below are alternatives, not one document — a
[table] header owns every following key until the next header.
After tomllib, they collapse to two Python values. Bound accepts
a single table. Bounds accepts a table or a list of tables.
# one table
months = { from = 1, to = 12 }
[months]
from = 1
to = 12
# many tables
windows = [
{ from = 1, to = 4 },
{ from = 6, to = 10 },
]
[[windows]]
from = 1
to = 4
[[windows]]
from = 6
to = 10
import tomllib
from tomlrange import Domain
Month = Domain(int, lo=1, hi=12, name="month")
data = tomllib.loads(config)
year = Month.bound(data["months"]) # 1..12
windows = Month.bounds(data["windows"]) # 1..4 and 6..10
assert list(year) == list(range(1, 13))
assert 5 not in windows
No runtime dependencies. Endpoints stay the type tomllib gave you.
Domain as schema
A Domain is the constraint object. Bound / Bounds are the values.
from tomlrange import Domain
Hour = Domain(int, lo=0, hi=23, name="hour")
Hour.bound({"from": 7, "to": 16})
Hour.full() # requires lo and hi
Or name the domain by subclassing Spec — the class body is the schema:
from tomlrange import Spec
class Month(Spec):
typ = int
lo = 1
hi = 12
Month.parse({"from": 1, "to": 4})
Month.parse_many([{"from": 1, "to": 4}, {"from": 6, "to": 10}])
Named discrete order is members. wrap=True is a cyclic topology — from
after to walks across the seam. from == to stays a singleton; the full
cycle is full().
class Weekday(Spec):
typ = str
members = ("mon", "tue", "wed", "thu", "fri", "sat", "sun")
wrap = True
list(Weekday.parse({"from": "sat", "to": "mon"})) # sat, sun, mon
Time-of-day is clock position on the 24h line, not duration. Unquoted
09:00:00 is TOML local time (datetime.time).
import tomllib
from datetime import time, timedelta
from tomlrange import Clock, elapsed
hours = Clock.parse(tomllib.loads("open = { from = 09:00:00, to = 17:00:00 }")["open"])
assert hours.as_tuple()[0].hour == 9
assert type(hours.start) is time
assert type(hours.stop) is time
assert elapsed(hours) == timedelta(hours=8)
quarter = Clock.parse(
tomllib.loads("open = { from = 09:00:00, to = 17:00:00, step = 15 }")["open"]
)
assert quarter.step == timedelta(minutes=15)
assert quarter.domain.step == timedelta(minutes=1)
elapsed is derived walk length ((width - 1) * step) — endpoints stay
time. wrap=True allows overnight { from = 22:00:00, to = 02:00:00 }
(elapsed is then the wrap walk, 4 hours). A singleton is one tick, not
a full day. Optional table step is a positive int count of the domain
grain (Clock default one minute, so step = 15 is 15 minutes). Omit it
and walk stays Domain.step, or +1 on ints.
Validation
On one table:
- value is a mapping
- keys are
fromandto(override withDomain(..., keys=("start", "end"))) plus optionalstep - each endpoint is
type(raw) is domain.typ(so1.0andTrueare notint). A time domain accepts naivedatetime.timeonly. Aware times, dates, datetimes, timedeltas, and strings are rejected - endpoints sit inside
lo/hiwhen those are set - when
membersis set, each endpoint is an exact member (unknown → error atfrom/to) from <= toin domain order (a singleton is{ from = 3, to = 3 }); withwrap=True,fromaftertois a seam walk, not an error
On a list, overlap= is "reject" (default), "allow", or "merge".
Reject treats a shared endpoint as overlap (1–4 and 4–6 fail).
Adjacent integers (1–4 then 5–8) are fine; merge() will coalesce them.
Member domains overlap and merge on member identity, not string order.
list(), len(), and width work for int, members, and datetime.time
(at the bound's step when set, else Domain.step, default one minute).
as_range() is int-only. Other
ordered typs raise TypeError ("{name} width is only defined for int").
Membership, as_tuple(), and as_table() work for any ordered typ.
Errors carry a path:
months_ranges[1].to: 13 is above month 12
What this is not
- Not a file loader. Call
tomllibyourself. - Not string ranges (
"1-12","Jan–Apr","9AM-5PM"). - Not a two-element array (
[1, 12]). After TOML decode that is a list, not a table. - Not wrap-around unless the domain sets
wrap=True. - Not a free-for-all
strdomain. Names needmembers. - Not iteration over other ordered
typs (walk dates yourself). Time walks atstep.
Install
pip install tomlrange
Python 3.14+.
Metadata
Release files for tomlrange 0.3.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 | |
|---|---|---|---|
| tomlrange-0.3.0.tar.gz | 27.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tomlrange-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.4 kB
Release files / tomlrange-0.3.0.tar.gz
| Download URL | tomlrange-0.3.0.tar.gz |
|---|---|
| Size | 27.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5f4f243d5150f4e235726b292c3dfc48e3443bba3f5bcd7cafe09a593bb1461a
|
|
BLAKE2b-256 checksum How to use checksums |
f107ca894845e6de4ce743d0fbc2c71965fbccf7c141e5cc517ba1244251b87a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / tomlrange-0.3.0-py3-none-any.whl
| Download URL | tomlrange-0.3.0-py3-none-any.whl |
|---|---|
| Size | 13.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cb1d6ac9eacb4ec7358f2e18f4b986e50f40d88d07fe190948708582aeb361be
|
|
BLAKE2b-256 checksum How to use checksums |
e89b814244254540d6971800454a2d46b40e179eecff3507a1be2d1ab55ac84e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|