Python's Built-in tomllib Module — Core Concepts
Why tomllib matters
TOML (Tom’s Obvious Minimal Language) became Python’s official configuration format when PEP 518 made pyproject.toml the standard for project metadata. Yet until Python 3.11, reading TOML required a third-party package like toml or tomli. PEP 680 fixed this by adding tomllib to the standard library.
This matters for tools, scripts, and applications that need to read configuration without adding dependencies — especially CLI tools and plugins where dependency bloat is a real concern.
Basic usage
import tomllib
with open("config.toml", "rb") as f:
config = tomllib.loads(f.read().decode())
# or more idiomatically:
with open("config.toml", "rb") as f:
config = tomllib.load(f)
Note the binary mode ("rb"). This is intentional — TOML files must be UTF-8, and tomllib handles the decoding internally. Passing a text-mode file raises TypeError.
Two functions are available:
tomllib.load(fp)— reads from a binary file objecttomllib.loads(s)— parses a string
TOML to Python type mapping
| TOML type | Python type |
|---|---|
| String | str |
| Integer | int |
| Float | float |
| Boolean | bool |
| Date/Time | datetime.date, datetime.time, datetime.datetime |
| Array | list |
| Table | dict |
TOML’s type system is richer than JSON’s — it distinguishes integers from floats, supports dates natively, and has both inline and standard tables.
Reading pyproject.toml
The most common use case — reading your own project’s configuration:
import tomllib
from pathlib import Path
def load_project_config() -> dict:
pyproject = Path("pyproject.toml")
if not pyproject.exists():
return {}
with open(pyproject, "rb") as f:
data = tomllib.load(f)
return data.get("tool", {}).get("my-tool", {})
This is how tools like Black, Ruff, and pytest read their configuration from pyproject.toml.
Error handling
tomllib raises tomllib.TOMLDecodeError for invalid TOML:
try:
config = tomllib.loads(raw_text)
except tomllib.TOMLDecodeError as e:
print(f"Invalid TOML: {e}")
The error messages include line numbers, which makes debugging config syntax straightforward.
TOML vs JSON vs YAML
| Feature | TOML | JSON | YAML |
|---|---|---|---|
| Comments | ✅ | ❌ | ✅ |
| Human-writable | ✅ | ⚠️ | ✅ |
| Typed dates | ✅ | ❌ | ⚠️ |
| Trailing commas | ✅ | ❌ | N/A |
| Standard in Python | ✅ (3.11+) | ✅ | ❌ |
| Accidental code execution | ❌ | ❌ | ⚠️ (yaml.load) |
TOML’s sweet spot is configuration files. It’s not meant for data interchange (use JSON) or complex documents (use YAML carefully).
Common misconception
“tomllib can read and write TOML.”
It’s read-only by design. The rationale: reading TOML is the common case (loading config), and a reader is simpler to maintain in the stdlib. Writing TOML — preserving comments, formatting, and ordering — is significantly more complex. For writing, use the third-party tomli-w or tomlkit packages.
Backward compatibility
For projects that need to support Python 3.10 and earlier:
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib # pip install tomli
tomli is the package that tomllib was based on — the API is identical. This pattern gives you seamless forward compatibility.
Practical tips
- Always open in binary mode (
"rb") — this is the most common mistake. - Validate after parsing —
tomllibvalidates TOML syntax but not your schema. Use Pydantic or manual checks for value validation. - Use
get()chains for nested access —config.get("section", {}).get("key", default)avoidsKeyErroron missing sections. - Keep configs flat — deeply nested TOML tables become hard to read. Prefer
[tool.myapp]over three levels of nesting.
The one thing to remember: tomllib gives Python 3.11+ the ability to read TOML configuration files with zero dependencies — import it, open in binary mode, and you have a dictionary.
See Also
- Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
- Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
- Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
- Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
- Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.