Skip to content

miriada-io/exceptly

Repository files navigation

exceptly

PyPI Python Tests License

Tiny utilities for raising exceptions on a condition and rendering them as readable strings.

Installation

pip install exceptly

Requires Python 3.11+.

Why exceptly?

Two small annoyances show up in every Python codebase:

  • Guard clauses take three lines. if not value: raise ValueError("missing") is four tokens of logic wrapped in ceremony. raise_if_not(value, ValueError, "missing") is the same thing on one line, with the exception type spelled out up front where the reader can see it.
  • repr(e) and str(e) are both wrong for logs. repr(e) gives you ValueError('missing') (quoted, parenthesised, noisy). str(e) gives you 'missing' — no type at all. repr_exception(e) gives you ValueError: missing — the form humans actually want in log lines and assertion messages.

No dependencies, no configuration, no magic — three functions that do one thing each.

Quick Start

from exceptly import raise_if, raise_if_not, repr_exception


def set_positive(value: int) -> None:
    raise_if_not(isinstance(value, int), TypeError, f"expected int, got {type(value).__qualname__}")
    raise_if(value <= 0, ValueError, "expected a positive value, got", value)
    # ... actual work


try:
    set_positive(None)
except TypeError as e:
    assert repr_exception(e) == "TypeError: expected int, got NoneType"

try:
    set_positive(0)
except ValueError as e:
    assert repr_exception(e) == "ValueError: ('expected a positive value, got', 0)"

Overview

raise_if | raise_if_not | repr_exception


raise_if

Raises the given exception when condition is truthy; does nothing otherwise.

Use when: you want an inline guard clause without the if ...: raise ... boilerplate, and you want the exception type to be visible at the start of the call rather than buried inside the if body.

from exceptly import raise_if

# Pass a pre-built exception instance:
raise_if(x < 0, ValueError("negative value"))

# Or pass the class and its constructor args — the instance is only built if the condition fires:
raise_if(x < 0, ValueError, "negative value")
raise_if(x < 0, ValueError, "negative value", x)  # ValueError('negative value', -1)

The two forms are equivalent, but the class-plus-args form avoids constructing the exception when the guard does not trip — useful when the message is expensive to format (e.g. repr of a large object).

raise_if_not

Inverse of raise_if: raises when condition is falsy.

Use when: the natural phrasing of the guard is positive ("must be non-empty", "must be an int") rather than negative. Also a drop-in replacement for assert in code paths where assert is unreliable (stripped by python -O, silent in production configs).

from exceptly import raise_if_not

raise_if_not(isinstance(value, int), TypeError, f"expected int, got {type(value).__qualname__}")
raise_if_not(items, ValueError("items must not be empty"))

repr_exception

Renders an exception as "ClassName: message", or "ClassName" when the exception carries no message.

Use when: writing log lines, assertion messages, or error responses. repr(e) is cluttered (ValueError('missing')); str(e) loses the type ('missing'); f"{type(e).__name__}: {e}" is the hand-rolled version you end up writing anyway.

from exceptly import repr_exception

repr_exception(ValueError("not found"))     # "ValueError: not found"
repr_exception(RuntimeError())              # "RuntimeError"
repr_exception(ValueError(1, 2, 3))         # "ValueError: (1, 2, 3)"


class DomainError(Exception):
    pass


repr_exception(DomainError("boom"))         # "DomainError: boom"

The output is plain text, safe for logs and assertion messages, and never raises.

License

MIT

About

Tiny utilities for raising exceptions on a condition and rendering them as readable strings.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages