Skip to content

← Stdlib Mastery step 51 of 55

Medium Primitives

tomllib: turn dict[str, Any] into a typed config, or a named error

Parse a TOML document into a frozen dataclass, validating every field, and report failures with the field name and the reason.

class ConfigError(Exception):
    def __init__(self, field: str, reason: str) -> None: ...
    field: str
    reason: str


@dataclass(frozen=True, slots=True)
class ServerConfig:
    host: str
    port: int
    workers: int = 4
    debug: bool = False


def load_config(stream: BinaryIO) -> ServerConfig: ...
def solve(document: str) -> dict[str, object]: ...

solve encodes document to UTF-8, wraps it in an io.BytesIO, and calls load_config. On success it returns {"ok": True, "host", "port", "workers", "debug"}; on ConfigError it returns {"ok": False, "field": exc.field, "reason": exc.reason}.

The exact (field, reason) pairs:

condition field reason
the document does not parse "<document>" "malformed TOML"
no [server] table "server" "missing table"
host absent "server.host" "missing"
host not a string "server.host" "expected string"
port absent "server.port" "missing"
port not an integer "server.port" "expected integer"
port outside 1..65535 "server.port" "out of range"
workers present and not an integer "server.workers" "expected integer"
workers present and below 1 "server.workers" "out of range"
debug present and not a boolean "server.debug" "expected boolean"

Why this is the highest-value --strict demonstration in the track. tomllib.load returns dict[str, Any], and Any is not “unknown” to a type checker — it is “assume this is whatever you need”, so it propagates silently and switches checking off wherever it lands. A function declared -> int that ends in return document["server"]["port"] is accepted by every check except one: --warn-return-any, which is included in --strict. A config with port = "8080" otherwise produces a str that flows into your socket call and fails somewhere else entirely.

Do not silence it with cast. A cast is an unchecked assertion that generates no runtime code; using one here converts “this value is unverified” into “the checker has been told to stop mentioning it”, and leaves evidence in the source suggesting someone checked. Narrow with isinstance instead — after that, mypy knows the type because it has seen the proof.

bool is a subclass of int. A TOML port = true passes isinstance(x, int) and gives you True where a port number belongs. Any integer validation at a boundary needs the bool exclusion, and one of the tests is exactly that.

Binary mode. tomllib.load requires a binary stream: TOML is defined to be UTF-8 and the parser decodes it itself, which removes the whole class of “works on my laptop, fails in the container” locale-encoding bugs. tomllib.loads takes a str when you already have the text.

Two version notes, neither tested here: 3.14 gives TOMLDecodeError structured lineno/colno attributes, and 3.15 upgrades to TOML 1.1.0, so a document valid on 3.15 may fail on 3.14. There is deliberately no writing support — use tomli-w or tomlkit.

Loading visualization…