From 1dbd2c5f3efbcddbfa31ce7e5b5413190b6810c8 Mon Sep 17 00:00:00 2001 From: Errordog2 Date: Sat, 20 Jun 2026 11:08:04 +0800 Subject: [PATCH] Add ShadeError base exception --- src/shade/__init__.py | 16 +++++++++++++- src/shade/errors.py | 39 +++++++++++++++++++++++++++++++++ tests/test_errors.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/shade/errors.py create mode 100644 tests/test_errors.py diff --git a/src/shade/__init__.py b/src/shade/__init__.py index fe0645a..01488cb 100644 --- a/src/shade/__init__.py +++ b/src/shade/__init__.py @@ -1,5 +1,19 @@ +from .errors import ( + AuthenticationError, + InvalidRequestError, + NetworkError, + NotFoundError, + ShadeError, +) from .gateway import Gateway __version__ = "0.1.0" -__all__ = ["Gateway"] +__all__ = [ + "AuthenticationError", + "Gateway", + "InvalidRequestError", + "NetworkError", + "NotFoundError", + "ShadeError", +] diff --git a/src/shade/errors.py b/src/shade/errors.py new file mode 100644 index 0000000..710bf34 --- /dev/null +++ b/src/shade/errors.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from typing import Optional + + +class ShadeError(Exception): + """Base exception for all Shade SDK errors.""" + + def __init__( + self, + message: str, + status_code: Optional[int] = None, + response_body: Optional[str] = None, + ) -> None: + self.message = message + self.status_code = status_code + self.response_body = response_body + super().__init__(message) + + def __str__(self) -> str: + if self.status_code is None: + return self.message + return f"{self.message} (status code: {self.status_code})" + + +class AuthenticationError(ShadeError): + """Raised when authentication fails or credentials are invalid.""" + + +class InvalidRequestError(ShadeError): + """Raised when a request is malformed or rejected by validation.""" + + +class NotFoundError(ShadeError): + """Raised when an API resource cannot be found.""" + + +class NetworkError(ShadeError): + """Raised when the SDK cannot complete a network request.""" diff --git a/tests/test_errors.py b/tests/test_errors.py new file mode 100644 index 0000000..4339d2b --- /dev/null +++ b/tests/test_errors.py @@ -0,0 +1,51 @@ +import shade +from shade import ( + AuthenticationError, + InvalidRequestError, + NetworkError, + NotFoundError, + ShadeError, +) + + +def test_shade_error_can_be_raised_standalone(): + error = ShadeError("contract rejected payment") + + assert str(error) == "contract rejected payment" + assert error.message == "contract rejected payment" + assert error.status_code is None + assert error.response_body is None + + +def test_shade_error_includes_http_context(): + error = ShadeError( + "invalid request", + status_code=422, + response_body='{"error":"missing amount"}', + ) + + assert str(error) == "invalid request (status code: 422)" + assert error.status_code == 422 + assert error.response_body == '{"error":"missing amount"}' + + +def test_specific_errors_inherit_from_shade_error(): + for error_type in ( + AuthenticationError, + InvalidRequestError, + NetworkError, + NotFoundError, + ): + error = error_type("request failed", status_code=400, response_body="raw") + + assert isinstance(error, ShadeError) + assert str(error) == "request failed (status code: 400)" + assert error.response_body == "raw" + + +def test_package_root_exports_error_classes(): + assert shade.ShadeError is ShadeError + assert shade.AuthenticationError is AuthenticationError + assert shade.InvalidRequestError is InvalidRequestError + assert shade.NetworkError is NetworkError + assert shade.NotFoundError is NotFoundError