Quick Start

This page walks through the most common errortools idioms end-to-end. Every example is runnable as-is — copy the snippets into a Python file or a REPL and they will work without further setup.

See also

A 30-second tour

from errortools import ignore, retry, BaseErrorCodes
from errortools.logging import logger

# 1. Suppress an exception, then introspect it
with ignore(KeyError) as err:
    _ = {}["missing"]
print(err.exception)        # KeyError('missing')

# 2. Retry a flaky operation
@retry(times=3, on=ConnectionError, delay=1.0)
def connect(host: str): ...

# 3. Raise a structured exception
raise BaseErrorCodes.not_found("user #42")   # NotFoundError [3001]

# 4. Structured logging
logger.info("Server started on port {}", 8080)

Suppressing exceptions

With metadata — ignore()

ignore() is a context manager that swallows the exceptions you list while keeping a full record of what happened. Bind the result to a name with as to read the metadata after the block exits.

from errortools import ignore

with ignore(KeyError) as err:
    _ = {}["missing"]

if err.be_ignore:
    print(f"Suppressed {err.name}: {err.exception}")
    print(f"Traceback:\n{err.traceback}")
Attributes on the bound object

Attribute

Description

be_ignore

True if an exception was suppressed.

name

Class name of the suppressed exception ("KeyError").

count

Number of exceptions suppressed in the block.

exception

The original exception instance, or None.

traceback

Pre-formatted traceback string, or None.

Without metadata — fast_ignore()

When you don’t need any of the above metadata, use fast_ignore(). It’s a thin wrapper around contextlib.suppress() and adds essentially no overhead.

from errortools import fast_ignore

with fast_ignore(KeyError, IndexError):
    _ = [][0]      # suppressed, minimal overhead

By base class — ignore_subclass()

ignore_subclass() matches every subclass of the type you pass. This is the most expressive form: “ignore anything that derives from X”.

from errortools import ignore_subclass

with ignore_subclass(LookupError):
    raise IndexError("out of range")     # IndexError ⊆ LookupError
    # IndexError is caught and discarded

Warnings — ignore_warns()

ignore_warns() mirrors warnings.catch_warnings() for the common case of suppressing specific categories.

from errortools import ignore_warns

with ignore_warns(DeprecationWarning):
    # Deprecated API call that would otherwise warn
    legacy_api()

See also

See Exception Handling for the full guide, including the “subclass vs. tuple” rule and the errortools.future zero-overhead variants.

Retrying and timing out

Automatic retry — retry()

retry() retries a callable on the configured exception, with an optional delay between attempts.

from errortools import retry

@retry(times=3, on=ConnectionError, delay=1.0)
def connect(host: str):
    # Will retry up to 3 times on ConnectionError
    ...

Async timeout — timeout()

timeout() cancels a coroutine after the given deadline. It requires an active event loop.

from errortools import timeout

@timeout(5.0)
async def fetch_data(url: str):
    # Raises asyncio.TimeoutError after 5 seconds
    ...

Defining custom exceptions

With error codes — PureBaseException

Subclass PureBaseException to attach a numeric error code and a default message. The code is rendered in the exception’s __str__ for easy log greppability.

from errortools import PureBaseException

class AppError(PureBaseException):
    code = 9000
    default_detail = "Application error"

raise AppError()                 # [9000] Application error
raise AppError("disk full")      # [9000] disk full

With rich context — ContextException

ContextException adds a trace ID, a free form context dict, and a chain of contextual events.

from errortools import ContextException

raise (
    ContextException("user not found")
    .with_context(request_id="abc-123", user_id=42)
    .with_cause(KeyError("user"))
)

Predefined codes — BaseErrorCodes

For the most common cases, BaseErrorCodes ships ready-made factories so you don’t have to subclass anything.

from errortools import BaseErrorCodes

raise BaseErrorCodes.not_found("user #42")         # [3001] user #42
raise BaseErrorCodes.invalid_input("username too short")
raise BaseErrorCodes.access_denied()

See also

See Custom Exceptions for the complete taxonomy of codes, the error-class hierarchy, and chaining examples.

Structured logging

errortools.logging is a Loguru-inspired logger that ships with the package — no extra install required.

from errortools.logging import logger

logger.info("Server started on port {}", 8080)
logger.warning("Disk at {pct:.1f}%", pct=92.5)
logger.success("All systems operational")

# Add a rotating file sink
logger.add("app.log", rotation=10_000_000, retention=5)

# Bind a context that propagates to every log call
req_log = logger.bind(request_id="abc-123")
req_log.info("Request received")

See also

See Logging for sinks, levels, formatting, and the integration with the standard-library logging module.

Where to go next