Skip to content

Commit

Permalink
Make the .message field on exceptions non-empty (#2947)
Browse files Browse the repository at this point in the history
* Make the `.message` field on exceptions non-empty

This allows subclasses of SanicException to access their message via the `message` attribute. This makes it consistent with the `status_code`, `quiet`, `headers` attributes that were previously present on the class.

Currently, the message attribute is present on the class but not on the instance, so accessing SanicException().message will return an empty string "" instead of the actual message "Internal Server Error", which you can get by calling the __str__() method or str().

This is a bit surprising, since the .message attribute shows up in autocomplete and type-checking. It also happens for the other exceptions, like str(BadRequest()) == "" as well.

I set the message attribute on instances of SanicException and added tests for this new behavior.

* Type error?

* Normalize bytes objects to str

* Update exceptions.py

* Try to fix type inference?

* Handle new logic staying closer to original intent

---------

Co-authored-by: Adam Hopkins <adam@amhopkins.com>
  • Loading branch information
ekzhang and ahopkins committed Jun 22, 2024
1 parent a5ceb3a commit 853f831
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 6 deletions.
17 changes: 11 additions & 6 deletions sanic/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def __init__(
quiet: Optional[bool] = None,
context: Optional[Dict[str, Any]] = None,
extra: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
) -> None:
self.context = context
self.extra = extra
Expand All @@ -75,17 +75,22 @@ def __init__(
quiet = quiet or getattr(self.__class__, "quiet", None)
headers = headers or getattr(self.__class__, "headers", {})
if message is None:
if self.message:
message = self.message
elif status_code:
msg: bytes = STATUS_CODES.get(status_code, b"")
message = msg.decode("utf8")
message = self.message
if not message and status_code:
msg = STATUS_CODES.get(status_code, b"")
message = msg.decode()
elif isinstance(message, bytes):
message = message.decode()

super().__init__(message)

self.status_code = status_code or self.status_code
self.quiet = quiet
self.headers = headers
try:
self.message = message
except AttributeError:
...


class HTTPException(SanicException):
Expand Down
15 changes: 15 additions & 0 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,18 @@ def test_exception_aliases():
assert MethodNotSupported is MethodNotAllowed
assert ContentRangeError is RangeNotSatisfiable
assert HeaderExpectationFailed is ExpectationFailed


def test_exception_message_attribute():
assert ServerError("it failed").message == "it failed"
assert ServerError(b"it failed").message == "it failed"
assert (
ServerError().message == str(ServerError()) == "Internal Server Error"
)

class CustomError(SanicException):
message = "Something bad happened"

assert CustomError().message == CustomError.message == str(CustomError())
assert SanicException().message != ""
assert SanicException("").message == ""

0 comments on commit 853f831

Please sign in to comment.