Metadata-Version: 2.4
Name: exceptly
Version: 1.0.0
Summary: Tiny utilities for raising exceptions on a condition and rendering them as readable strings
Project-URL: Homepage, https://github.com/miriada-io/exceptly
Project-URL: Repository, https://github.com/miriada-io/exceptly
Project-URL: Issues, https://github.com/miriada-io/exceptly/issues
Project-URL: Changelog, https://github.com/miriada-io/exceptly/blob/master/CHANGELOG.md
Author-email: Miriada <info@miriada.io>
License-Expression: MIT
License-File: LICENSE
Keywords: assert,error,errors,exception,exceptions,raise,repr
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: coverage>=7.0; extra == 'dev'
Requires-Dist: pre-commit>=3.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# exceptly

[![PyPI](https://img.shields.io/pypi/v/exceptly.svg?label=PyPI)](https://pypi.org/project/exceptly/)
[![Python](https://img.shields.io/pypi/pyversions/exceptly.svg?label=Python)](https://pypi.org/project/exceptly/)
[![Tests](https://github.com/miriada-io/exceptly/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/miriada-io/exceptly/actions/workflows/tests.yml)
[![License](https://img.shields.io/pypi/l/exceptly.svg?label=License)](https://github.com/miriada-io/exceptly/blob/master/LICENSE)

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

## Installation

```bash
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

```python
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) | [`raise_if_not`](#raise_if_not) | [`repr_exception`](#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.

```python
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).

```python
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.

```python
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](LICENSE)
