Tiny utilities for raising exceptions on a condition and rendering them as readable strings.
pip install exceptlyRequires Python 3.11+.
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)andstr(e)are both wrong for logs.repr(e)gives youValueError('missing')(quoted, parenthesised, noisy).str(e)gives you'missing'— no type at all.repr_exception(e)gives youValueError: 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.
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)"raise_if | raise_if_not | repr_exception
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).
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"))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.