Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 3 additions & 34 deletions src/view/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from __future__ import annotations

from contextlib import contextmanager
from functools import wraps
from typing import TYPE_CHECKING, ParamSpec, TypeVar
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from collections.abc import Callable, Iterator
from collections.abc import Iterator

__all__ = "reraise", "reraises"
__all__ = ("reraise",)


@contextmanager
Expand All @@ -27,33 +26,3 @@ def reraise(
yield
except target as error:
raise new_exception from error


T = TypeVar("T")
P = ParamSpec("P")


def reraises(
new_exception: type[BaseException] | BaseException,
*exceptions: type[BaseException],
) -> Callable[[Callable[P, T]], Callable[P, T]]:
"""
Decorator to reraise one or many exceptions as a single exception for an
entire function.

This is primarily useful for reraising exceptions into HTTP errors, such
as an error 400 (Bad Request).
"""
target = exceptions or Exception

def factory(function: Callable[P, T], /) -> Callable[P, T]:
@wraps(function)
def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
try:
return function(*args, **kwargs)
except target as error:
raise new_exception from error

return decorator

return factory
26 changes: 13 additions & 13 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from view.utils import reraise, reraises
from view.utils import reraise


def test_simple_reraise():
Expand Down Expand Up @@ -55,38 +55,38 @@ def test_do_not_reraise_base_exceptions():
raise KeyboardInterrupt


def test_simple_reraises():
@reraises(RuntimeError, TypeError)
def test_simple_reraise_as_decorator():
@reraise(RuntimeError, TypeError)
def runtime_from_type() -> None:
raise TypeError("silly")

with pytest.raises(RuntimeError):
runtime_from_type()


def test_reraises_unexpected():
def test_reraise_unexpected_as_decorator():

@reraises(RuntimeError, TypeError)
@reraise(RuntimeError, TypeError)
def runtime_from_type_but_value() -> None:
raise ValueError("haha")

with pytest.raises(ValueError):
runtime_from_type_but_value()


def test_reraises_all_exceptions():
def test_reraise_all_exceptions_as_decorator():

@reraises(RuntimeError)
@reraise(RuntimeError)
def runtime_from_all() -> None:
raise ZeroDivisionError("anything")

with pytest.raises(RuntimeError):
runtime_from_all()


def test_reraise_exception_instance():
def test_reraise_exception_instance_as_decorator():

@reraises(RuntimeError("test"))
@reraise(RuntimeError("test"))
def runtime_value_from_all() -> None:
raise ZeroDivisionError("anything")

Expand All @@ -96,9 +96,9 @@ def runtime_value_from_all() -> None:
assert str(error.value) == "test"


def test_multi_reraise():
def test_multi_reraise_as_decorator():

@reraises(RuntimeError, TypeError, ValueError)
@reraise(RuntimeError, TypeError, ValueError)
def runtime_from_type_or_value(exception: BaseException) -> None:
raise exception

Expand All @@ -112,8 +112,8 @@ def runtime_from_type_or_value(exception: BaseException) -> None:
runtime_from_type_or_value(ZeroDivisionError())


def test_do_not_reraises_base_exceptions():
@reraises(RuntimeError)
def test_do_not_reraise_base_exceptions_as_decorator():
@reraise(RuntimeError)
def runtime_from_all_but_interrupt() -> None:
raise KeyboardInterrupt

Expand Down
Loading