Python tomllib: Read TOML Files

Published on: July 24, 2026
Reading time: 6 minutes
TOML configuration in a Python project

Configuration files let applications change behavior without forcing developers to edit source code for every port, path, feature flag, or environment setting. TOML has become a popular format for this job because it is readable, strongly typed, and maps naturally to dictionaries. Since Python 3.11, the standard library includes tomllib, which can parse TOML without installing a third-party dependency.

This guide explains how to use Python tomllib to load files, read nested tables, work with dates, customize decimal parsing, validate required fields, and report malformed input. You will also see how it fits with pyproject.toml, the file used by modern packaging, linting, formatting, and testing tools. For related project organization topics, review managing dependencies with Poetry and the guide to creating an installable Python package.

What is TOML?

TOML stands for “Tom’s Obvious, Minimal Language.” Its goal is to provide a configuration format that is easy for people to read and easy for programs to transform into structured data. A TOML document contains key-value pairs, tables, arrays, and typed values such as integers, floating-point numbers, booleans, dates, and times.

app_name = "Sales dashboard"
debug = false
port = 8080

[database]
host = "localhost"
name = "sales"
timeout = 5.5

[features]
reports = true
exports = ["csv", "xlsx"]

When Python parses this document, it becomes a nested dictionary. The official TOML 1.0 specification describes the exact rules for strings, tables, arrays, dates, and key names.

When to use tomllib

Use tomllib when your program needs to read TOML. The module does not write or edit documents. That makes it ideal for services that load configuration at startup, command-line tools that inspect project metadata, and scripts that read tool settings. If you need to preserve comments and formatting while editing a document, choose a library designed for writing TOML.

The module is available in Python 3.11 and newer. Projects that support older interpreters commonly use tomli, which offers a similar interface. You can declare your minimum Python version in pyproject.toml. The article about Ruff for Python shows another practical use of that file for centralizing tool configuration.

Reading a TOML file

Create a file named config.toml with the earlier example. Open it in binary mode and call tomllib.load:

import tomllib

with open("config.toml", "rb") as file:
    config = tomllib.load(file)

print(config["app_name"])
print(config["database"]["host"])
print(config["features"]["exports"])

Binary mode is required by load. It avoids text-decoding ambiguity and matches the documented API. The official tomllib documentation lists the available functions, exceptions, and conversion rules.

The result is an ordinary dictionary. TOML strings become str, integers become int, decimal values become float, arrays become lists, and tables become dictionaries. This means familiar Python access and validation techniques work immediately.

Parsing TOML from a string

If the content is already in memory, use tomllib.loads. Unlike load, this function receives a string:

import tomllib

text = """
name = "worker"
workers = 4
active = true
"""

config = tomllib.loads(text)
print(config)

This is convenient in tests, for configuration returned by another service, or when a different component already read the file. Still, limit the size of untrusted input because a malicious document may consume excessive CPU or memory while being parsed.

Accessing values safely

Use square brackets for required keys. If a required key is missing, Python raises KeyError, which may be exactly what you want because the application should not start with incomplete configuration. For optional settings, use get and an explicit default:

debug = config.get("debug", False)
log_level = config.get("log_level", "INFO")
timeout = config.get("database", {}).get("timeout", 10)

Do not hide important mistakes with too many defaults. Database hosts, environment names, and security-related settings should usually fail clearly when absent. Separate required fields from optional ones and document the expected schema.

Validating configuration

tomllib validates TOML syntax, but it does not know your application rules. A negative port, an empty host, or an unsupported environment may still be valid TOML. Add domain validation after parsing:

def validate_config(config: dict) -> None:
    required = ["app_name", "database"]
    missing = [key for key in required if key not in config]

    if missing:
        raise ValueError(f"Missing keys: {', '.join(missing)}")

    port = config.get("port", 8080)
    if not 1 <= port <= 65535:
        raise ValueError("Port must be between 1 and 65535")

    if not config["database"].get("host"):
        raise ValueError("database.host is required")

Large projects may use typed validation models, but small functions and focused tests already prevent invalid settings from reaching production.

Handling syntax errors

An invalid document raises tomllib.TOMLDecodeError. Catch that exception at the configuration boundary and report a useful message:

import tomllib
from pathlib import Path

path = Path("config.toml")

try:
    with path.open("rb") as file:
        config = tomllib.load(file)
except FileNotFoundError:
    raise SystemExit(f"Configuration file not found: {path}")
except tomllib.TOMLDecodeError as error:
    raise SystemExit(f"Invalid TOML in {path}: {error}")

A broad except Exception that silently continues is dangerous. Configuration errors should generally stop startup early. Log the path and cause, but never dump passwords, tokens, or the full configuration dictionary.

Dates and times

TOML has native date and time types. tomllib converts them into classes from Python’s datetime module:

release_date = 2026-07-23
maintenance = 2026-07-23T22:00:00-03:00
date = config["release_date"]
maintenance = config["maintenance"]

print(type(date))
print(type(maintenance))
print(maintenance.tzinfo)

An offset date-time includes timezone information. A local date-time without an offset does not identify one universal instant. Applications that schedule jobs or compare events should define the expected timezone explicitly.

Using Decimal instead of float

By default, TOML decimal numbers become Python floats. Financial or precision-sensitive settings can use the parse_float argument:

import tomllib
from decimal import Decimal

with open("config.toml", "rb") as file:
    config = tomllib.load(file, parse_float=Decimal)

print(type(config["database"]["timeout"]))

The callable is invoked for every floating-point literal. It must not return a list or dictionary. This hook is useful when binary floating-point behavior is not acceptable.

Reading pyproject.toml

pyproject.toml stores project metadata and configuration for many Python tools. You can inspect fields controlled by your own project:

import tomllib

with open("pyproject.toml", "rb") as file:
    project = tomllib.load(file)

name = project["project"]["name"]
version = project["project"]["version"]
print(f"{name} {version}")

Not every project uses the same tables. Modern standards often use [project], while individual tools keep settings under their own namespaces. Read only the fields your program understands. When you are ready to distribute a project, see the guide to publishing a Python package to PyPI.

Separating environments and secrets

A practical design keeps non-secret defaults in TOML and injects credentials through environment variables or a secret manager. TOML can define development, testing, and production behavior without storing real passwords:

[environments.development]
debug = true
log_level = "DEBUG"

[environments.production]
debug = false
log_level = "WARNING"
environment = "production"
settings = config["environments"][environment]

Do not commit production credentials. If developers need a local file, ignore it in Git and provide a safe example containing placeholders.

Testing configuration parsing

Because loads accepts text, unit tests can avoid temporary files:

import tomllib

def test_minimum_config():
    text = """
    app_name = "test"
    port = 9000

    [database]
    host = "localhost"
    """

    config = tomllib.loads(text)
    validate_config(config)
    assert config["port"] == 9000

Add cases for missing keys, incorrect types, invalid ports, unsupported environments, and malformed TOML. These tests make schema changes visible instead of allowing silent failures.

Practical best practices

  • Open files in binary mode when using load.
  • Use loads for strings and focused tests.
  • Validate application rules after syntax parsing.
  • Never log secrets or the complete configuration object.
  • Use defaults only for truly optional settings.
  • Limit the size of untrusted TOML input.
  • Document the expected schema with a safe example file.
  • Keep compatible tool settings in pyproject.toml.

Conclusion

Python tomllib provides a direct way to read TOML with the standard library. It converts tables, arrays, numbers, booleans, and dates into normal Python types, raises a dedicated error for malformed syntax, and supports custom decimal conversion.

Parsing is only the first layer. Reliable configuration also requires domain validation, clear startup errors, secret protection, timezone awareness, and tests. With those practices, TOML becomes a clean interface between application code and the environments where it runs.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Developer monitoring structured Python application logs
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Observability in Python with structlog

    Build practical Python observability with structlog, JSON events, request context, tests, performance controls, and deployment guidance.

    Ler mais

    Tempo de leitura: 5 minutos
    24/07/2026
    Secure Python application configuration with Pydantic Settings
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Pydantic Settings for Safe Config

    Learn how to validate environment variables, manage secrets, and organize Python application settings with Pydantic Settings.

    Ler mais

    Tempo de leitura: 5 minutos
    23/07/2026
    Desenvolvedor programando em Python com Ruff para lint e formatação
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Ruff for Python: Lint and Format Code Step by Step

    Learn Ruff for Python linting, formatting, automatic fixes, pyproject.toml, VS Code and CI with a practical project configuration.

    Ler mais

    Tempo de leitura: 9 minutos
    22/07/2026
    2 balões de comentários em um fundo laranja
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Comments: Best Practices and Examples

    Learn Python comments with single-line and inline examples, block explanations, TODO notes, docstrings, security and performance context, review standards, and

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026
    Pessoa programando em um notebook, vista de trás, com código desfocado exibido na tela em um fundo escuro
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Docstrings: Document Code Clearly

    Learn Python docstrings for functions, classes, methods, modules, parameters, returns, exceptions, examples, pydoc, conventions, and documentation tools.

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026
    Logo do Python com o texto 'PEP 8' sobre fundo azul escuro, representando o guia de estilo da linguagem
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    PEP 8 in Python: Write Cleaner Code

    Learn PEP 8 in Python with naming, indentation, line length, imports, whitespace, comments, functions, classes, tools, exceptions, and practical refactoring.

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026