The TOML (Tom’s Obvious, Minimal Language) format has gained significant traction as a configuration file format, praised for its semantic clarity and human-readability, which often positions it as a more intuitive alternative to YAML or JSON for settings. Prior to Python 3.11, developers relied on third-party libraries like toml or tomli for parsing. Recognizing this need, Python 3.11 integrated TOML parsing into the standard library with the tomllib module, which is essentially a standardized version of the excellent tomli library. This move signifies TOML’s importance in the modern Python ecosystem, particularly for tooling like pyproject.toml as defined in PEP 518.

The tomllib Module (Standard Library)

The tomllib module, available from Python 3.11 onwards, provides functions for parsing TOML from strings and files. It does not include functionality for writing TOML, as the standard library typically focuses on reading configuration rather than generating it (writing is often handled by development tools or the tomli-w package).

The primary function is tomllib.load(), which takes a file object opened in binary mode ('rb') and returns a dictionary representing the parsed TOML data. It is crucial to open the file in binary mode because TOML files are UTF-8 encoded. Opening in text mode ('r') would require the tomllib module to handle decoding itself, which it is not designed to do. This design choice explicitly separates decoding (the file’s job) from parsing (tomllib’s job), adhering to a clear separation of concerns.

# Requires Python 3.11+
import tomllib

# Note the 'rb' (read binary) mode
with open('config.toml', 'rb') as f:
    config_data = tomllib.load(f)

print(config_data['database']['host']) # Outputs: localhost
print(config_data['servers']['alpha']['ip']) # Outputs: 10.0.0.1

For parsing a string that already contains TOML content, use tomllib.loads(). The string must be a str object.

import tomllib

toml_string = """
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00

[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
"""
data = tomllib.loads(toml_string)
print(data['owner']['name']) # Outputs: Tom Preston-Werner

The tomli Package (Third-Party)

For environments using Python versions older than 3.11, the third-party tomli package provides an identical API. This allows for forward and backward compatibility. A common pattern is to try importing tomllib first and fall back to tomli if it’s not available.

try:
    import tomllib
except ModuleNotFoundError:
    import tomli as tomllib

# Now use `tomllib` universally
with open('pyproject.toml', 'rb') as f:
    pyproject_data = tomllib.load(f)

Handling Dates and Times

A powerful feature of TOML is its native support for datetime objects, defined in the RFC 3339 standard. tomllib/tomli parses these into Python’s datetime module objects, preserving timezone information. This is a significant advantage over formats like JSON, which require custom string parsing for dates.

import tomllib
from datetime import datetime

toml_config = """
event_start = 2023-10-15T14:30:00Z
event_end = 2023-10-15T16:45:00-05:00
"""
data = tomllib.loads(toml_config)

start: datetime = data['event_start']
end: datetime = data['event_end']

print(f"Event starts at: {start.isoformat()}") # Formatted ISO string
print(f"Event ends at: {end}")                 # Standard datetime string
print(f"Timezone info: {end.tzinfo}")         # Outputs: UTC-05:00

Common Pitfalls and Best Practices

  1. File Mode: The most common error is opening a TOML file in text mode ('r') instead of binary mode ('rb'). This will result in a TypeError: expected bytes, got str.
  2. Invalid TOML: The parser is strict and will raise a tomllib.TOMLDecodeError (or tomli.TOMLDecodeError) on any syntax error, such as a missing closing quote, invalid indentation in inline tables, or duplicate keys. Always validate your TOML files, potentially using a dedicated linter.
  3. Data Structure: The parsed result is a nested dictionary. Accessing a key that doesn’t exist will raise a KeyError. Use .get() for safe access, especially for optional configuration sections: value = data.get('optional_section', {}).get('optional_key', 'default').
  4. Writing TOML: Since the standard library doesn’t write TOML, you must use a third-party package like tomli-w or tomlkit (which also supports preserving formatting and comments) for serialization.
# Writing TOML requires a separate library
# pip install tomli-w
import tomli_w

data_to_write = {
    "title": "My Application",
    "database": {
        "host": "localhost",
        "ports": [8001, 8002],
        "enabled": True
    }
}

with open('new_config.toml', 'wb') as f:
    tomli_w.dump(data_to_write, f)

The integration of tomllib solidifies TOML’s role as a first-class citizen for configuration in Python, offering a robust, standardized, and efficient way to read settings with rich data types directly into native Python objects.