Skip to main content

tomlrange

PyPI version Python versions CI License pre-commit.ci status

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)

half = Clock.parse(
    tomllib.loads("open = { from = 09:00:00, to = 17:00:00, step = 00:30:00 }")["open"]
)
assert half.step == timedelta(minutes=30)
assert half.as_table()["step"] == 30

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). On Clock, a naive local time is also accepted as length-since-midnight (step = 00:30:00 is 30 minutes) when that length is a positive multiple of Domain.step. as_table() always emits the int count. Omit step and walk stays Domain.step, or +1 on ints.

Validation

On one table:

  • value is a mapping
  • keys are from and to (override with Domain(..., keys=("start", "end"))) plus optional step
  • each endpoint is type(raw) is domain.typ (so 1.0 and True are not int). A time domain accepts naive datetime.time only. Aware times, dates, datetimes, timedeltas, and strings are rejected
  • endpoints sit inside lo / hi when those are set
  • when members is set, each endpoint is an exact member (unknown → error at from / to)
  • from <= to in domain order (a singleton is { from = 3, to = 3 }); with wrap=True, from after to is 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 tomllib yourself.
  • 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 str domain. Names need members.
  • Not iteration over other ordered typs (walk dates yourself). Time walks at step.

Install

pip install tomlrange

Python 3.14+.

Metadata

Release files for tomlrange 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tomlrange 0.3.1
File Size Uploaded
tomlrange-0.3.1.tar.gz 28.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tomlrange 0.3.1
File Interpreter ABI Platform
tomlrange-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 42.4 kB

Release files / tomlrange-0.3.1.tar.gz

Download URL tomlrange-0.3.1.tar.gz
Size 28.4 kB
Tags Source
SHA-256 checksum
How to use checksums
f206e257c2b6be148904e9549b15a2904b05e079e1f9c6b6aa315882f11f19f4
BLAKE2b-256 checksum
How to use checksums
17ee2a718dd4d740394acabba36c507f440ee579bf0b0d2494025d293686922a
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.1-py3-none-any.whl

Download URL tomlrange-0.3.1-py3-none-any.whl
Size 14.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
11191dc2c0cd89d3260bc3108a1396cc72a644457319ac35276a254ec7a2cd4c
BLAKE2b-256 checksum
How to use checksums
4bd68ccaaca8d0a6aa8fbed0fecc275bef108fe1982d8a2198a488d9dbb01bd5
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 history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page