Skip to content

Commit

Permalink
FIX: define BaseError outside ErrorResponse πŸ§‘β€πŸ’», fixes #26
Browse files Browse the repository at this point in the history
  • Loading branch information
eigenein committed Jul 28, 2023
1 parent 85af0ba commit 282e4ec
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 41 deletions.
80 changes: 40 additions & 40 deletions combadge/core/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Generic, Iterable, NoReturn
from typing import Any, ClassVar, Generic, Iterable, NoReturn, Type

from pydantic import BaseModel
from typing_extensions import Self, TypeAlias
from typing_extensions import Self

from combadge.core.typevars import ResponseT

Expand Down Expand Up @@ -94,48 +94,52 @@ def unwrap(self) -> Self:
return self


class ErrorResponse(BaseResponse, ABC):
"""
Parent model for error responses.
class BaseError(Generic[ResponseT], Exception):
"""Base exception class for all errors derived from `ErrorResponse`."""

Users should not use it directly, but inherit their response models from it.
"""
def __init__(self, response: ResponseT) -> None:
"""
Instantiate the error.
class Error(Generic[ResponseT], Exception):
Args:
response: original response that caused the exception
"""
Dynamically derived exception class.
super().__init__(response)

For each model inherited from `ErrorResponse` Combadge generates an exception
class, which is accessible through the `<ModelClass>.Error` attribute.
@property
def response(self) -> ResponseT:
"""Get the response that caused the exception."""
return self.args[0]

Examples:
>>> class InvalidInput(ErrorResponse):
>>> code: Literal["INVALID_INPUT"]
>>>
>>> try:
>>> service.call(...)
>>> except InvalidInput.Error:
>>> ...
Note: Why dynamically constructed class?
The problem with `pydantic` is that you can't inherit from `BaseModel` and `Exception`
at the same time. Thus, Combadge dynamically constructs a derived exception class,
which is available via the class attribute and raised by `raise_for_result()` and `unwrap()`.
"""

def __init__(self, response: ResponseT) -> None:
"""
Instantiate the error.
class ErrorResponse(BaseResponse, ABC):
"""
Parent model for error responses.
Args:
response: original response that caused the exception
"""
super().__init__(response)
Users should not use it directly, but inherit their response models from it.
"""

@property
def response(self) -> ResponseT:
"""Get the response that caused the exception."""
return self.args[0]
Error: ClassVar[Type[BaseError]] = BaseError
"""
Dynamically derived exception class.
For each model inherited from `ErrorResponse` Combadge generates an exception
class, which is accessible through the `<ModelClass>.Error` attribute.
Examples:
>>> class InvalidInput(ErrorResponse):
>>> code: Literal["INVALID_INPUT"]
>>>
>>> try:
>>> service.call(...)
>>> except InvalidInput.Error:
>>> ...
Note: Why dynamically constructed class?
The problem with `pydantic` is that you can't inherit from `BaseModel` and `Exception`
at the same time. Thus, Combadge dynamically constructs a derived exception class,
which is available via the class attribute and raised by `raise_for_result()` and `unwrap()`.
"""

def __init_subclass__(cls, exception_bases: Iterable[type[BaseException]] = (), **kwargs: Any) -> None:
"""
Expand Down Expand Up @@ -183,7 +187,3 @@ def raise_for_result(self, exception: BaseException | None = None) -> NoReturn:
def unwrap(self) -> NoReturn:
"""Raise the derived exception."""
raise self.Error(self)


BaseError: TypeAlias = ErrorResponse.Error
"""Base exception class for all errors derived from `ErrorResponse`."""
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ ignore = [
"PT013",
"RET505",
"TRY003",
"UP006", # Python 3.9+
]
unfixable = ["B"]

Expand Down
3 changes: 2 additions & 1 deletion tests/core/test_response.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pytest import raises

from combadge.core.response import ErrorResponse
from combadge.core.response import BaseError, ErrorResponse


def test_error_inheritance() -> None:
Expand All @@ -14,6 +14,7 @@ class Bar(Foo):
pass

assert Bar.Error is not Foo.Error
assert issubclass(Foo.Error, BaseError)
assert issubclass(Bar.Error, Foo.Error)
assert issubclass(Foo.Error, AnotherBaseError)
assert issubclass(Bar.Error, AnotherBaseError)
Expand Down

0 comments on commit 282e4ec

Please sign in to comment.