Skip to main content

thiserror

Typed, matchable error variants for Python, inspired by Rust's thiserror.

Define related errors in one place, attach structured payloads, and give each variant a clear message. Raise the resulting exceptions normally and handle individual variants with Python's match statement.

Features

  • Grouped error variants: define named variants in an Error subclass (an enum); calling a variant creates a raisable TypedVariant exception.
  • Payload schemas: declare one payload type per variant, or use () for a payload-free (unit) variant. Types are not validated at runtime.
  • Custom messages: use a function (such as lambda payload: f"...") or a string template rendered with str.format.
  • Pattern matching: distinguish variants with qualified case patterns after catching a TypedVariant exception.
  • Automatic exception causes: when the payload is an exception, it is assigned to the resulting error's __cause__.

Requires Python 3.12+. No third-party runtime dependencies.

Installation

With uv:

uv add thiserror

With pip:

pip install thiserror

Quick start

from dataclasses import dataclass

from thiserror import Error, TypedVariant, Variant


@dataclass(frozen=True, slots=True)
class InvalidHeaderFields:
    expected: str
    found: str


class DataStoreError(Error):
    Disconnect = Variant(OSError, "data store disconnected")
    Redaction = Variant(str, lambda payload: f"the data for key `{payload}` is not available")
    InvalidHeader = Variant(
        InvalidHeaderFields,
        lambda payload: f"invalid header (expected {payload.expected}, found {payload.found})",
    )
    Unknown = Variant((), "unknown data store error")


fields = InvalidHeaderFields(expected="content-type", found="x-type")
header_error = DataStoreError.InvalidHeader(fields)
print(header_error)

try:
    raise DataStoreError.Disconnect(OSError("disk offline"))
except TypedVariant as error:
    match error:
        case DataStoreError.Disconnect:
            print(error)
        case DataStoreError.Unknown:
            print(error)

Output:

invalid header (expected content-type, found x-type)
data store disconnected

DataStoreError defines the variants; TypedVariant is the exception class. Catch TypedVariant (or Exception), not DataStoreError.

Defining variants

Use Variant(schema, message) as a member of an Error subclass. Variant is an alias for make_variant.

class LookupError(Error):
    Missing = Variant(str, "key {0!r} not found")
    InvalidStatus = Variant(int, lambda status: f"invalid status: {status}")
    Empty = Variant((), "no results available")


assert str(LookupError.Missing("account")) == "key 'account' not found"
assert str(LookupError.InvalidStatus(503)) == "invalid status: 503"
assert str(LookupError.Empty()) == "no results available"

A variant with a payload requires exactly one argument. A unit variant, declared with Variant((), message), takes no arguments. Incorrect argument counts raise TypeError.

Formatting messages

String messages use str.format when the error is converted to a string:

  • {0} references the entire payload, as in "I/O error: {0}".
  • {name} references a field of a dataclass payload or a value in a payload object's __dict__.
  • Python format conversions also work, such as {0!r} for repr(payload).

Alternatively, pass a callable such as lambda payload: f"invalid: {payload}". This is how the quick-start example uses f-strings: the f-string is evaluated inside the function when the exception is rendered, not when it is created. A plain string passed to Variant uses str.format, not f-string interpolation. For unit variants, a message callable receives the TypedVariant instance rather than a payload.

Accessing the payload

A constructed exception exposes its original payload through .value:

error = LookupError.Missing("account")
assert error.value == "account"

Unknown attributes on the exception are delegated to its payload, so structured payload fields can also be accessed directly (for example, header_error.expected in the quick-start example).

Catching and matching errors

All constructed variants are instances of TypedVariant, which extends Exception. Match against qualified enum members to distinguish variants:

try:
    raise LookupError.Empty()
except TypedVariant as error:
    match error:
        case LookupError.Missing:
            print("Missing:", error.value)
        case LookupError.Empty:
            print("Nothing to return")
        case _:
            raise

If a payload is itself an exception, it is assigned to __cause__ automatically:

original = OSError("disk offline")
error = DataStoreError.Disconnect(original)
assert error.__cause__ is original

API overview

Name Description
Error Base enum for declaring a related set of error variants. It is not an exception type.
Variant(schema, message) Declares a variant with a payload type or () for no payload.
TypedVariant The raisable exception created by calling a declared variant. Its .value contains the payload.

Limitations

  • No runtime payload type validation: schemas provide type information, but the implementation checks the number of arguments, not whether a payload is an instance of the declared type.
  • No exhaustiveness guarantees: unlike Rust, Python does not require every variant to appear in a match statement.
  • No automatic type narrowing by variant: a successful case does not, by itself, ensure static type checkers will infer a more specific payload type.

Metadata

Release files for thiserror 0.1.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 thiserror 0.1.1
File Size Uploaded Provenance
thiserror-0.1.1.tar.gz 6.2 kB With provenance Details

Built distribution (wheel)

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

Total release size: 12.7 kB

Release files / thiserror-0.1.1.tar.gz

Download URL thiserror-0.1.1.tar.gz
Size 6.2 kB
Tags Source
SHA-256 checksum
How to use checksums
d30e56b8a5c79e6e4f327000e75888cdbea06c63d513800b0b0f68945d8bc2d3
BLAKE2b-256 checksum
How to use checksums
45498e80a2894e89bf837dcd9033dc94611ee67358b1f0abd01801528f929457
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Release files / thiserror-0.1.1-py3-none-any.whl

Download URL thiserror-0.1.1-py3-none-any.whl
Size 6.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ba1be0473104447b2ed32f23285ca9a54003ec5cda702f969a100737f60c978c
BLAKE2b-256 checksum
How to use checksums
9ee713bbddadc89655c1cda395545a6482a44d06055b239eb9f07feffd23e0ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Security for thiserror 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for thiserror-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

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