Skip to content

Commit 1dbd2c5

Browse files
committed
Add ShadeError base exception
1 parent ccbe656 commit 1dbd2c5

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

src/shade/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1+
from .errors import (
2+
AuthenticationError,
3+
InvalidRequestError,
4+
NetworkError,
5+
NotFoundError,
6+
ShadeError,
7+
)
18
from .gateway import Gateway
29

310
__version__ = "0.1.0"
411

5-
__all__ = ["Gateway"]
12+
__all__ = [
13+
"AuthenticationError",
14+
"Gateway",
15+
"InvalidRequestError",
16+
"NetworkError",
17+
"NotFoundError",
18+
"ShadeError",
19+
]

src/shade/errors.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from __future__ import annotations
2+
3+
from typing import Optional
4+
5+
6+
class ShadeError(Exception):
7+
"""Base exception for all Shade SDK errors."""
8+
9+
def __init__(
10+
self,
11+
message: str,
12+
status_code: Optional[int] = None,
13+
response_body: Optional[str] = None,
14+
) -> None:
15+
self.message = message
16+
self.status_code = status_code
17+
self.response_body = response_body
18+
super().__init__(message)
19+
20+
def __str__(self) -> str:
21+
if self.status_code is None:
22+
return self.message
23+
return f"{self.message} (status code: {self.status_code})"
24+
25+
26+
class AuthenticationError(ShadeError):
27+
"""Raised when authentication fails or credentials are invalid."""
28+
29+
30+
class InvalidRequestError(ShadeError):
31+
"""Raised when a request is malformed or rejected by validation."""
32+
33+
34+
class NotFoundError(ShadeError):
35+
"""Raised when an API resource cannot be found."""
36+
37+
38+
class NetworkError(ShadeError):
39+
"""Raised when the SDK cannot complete a network request."""

tests/test_errors.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import shade
2+
from shade import (
3+
AuthenticationError,
4+
InvalidRequestError,
5+
NetworkError,
6+
NotFoundError,
7+
ShadeError,
8+
)
9+
10+
11+
def test_shade_error_can_be_raised_standalone():
12+
error = ShadeError("contract rejected payment")
13+
14+
assert str(error) == "contract rejected payment"
15+
assert error.message == "contract rejected payment"
16+
assert error.status_code is None
17+
assert error.response_body is None
18+
19+
20+
def test_shade_error_includes_http_context():
21+
error = ShadeError(
22+
"invalid request",
23+
status_code=422,
24+
response_body='{"error":"missing amount"}',
25+
)
26+
27+
assert str(error) == "invalid request (status code: 422)"
28+
assert error.status_code == 422
29+
assert error.response_body == '{"error":"missing amount"}'
30+
31+
32+
def test_specific_errors_inherit_from_shade_error():
33+
for error_type in (
34+
AuthenticationError,
35+
InvalidRequestError,
36+
NetworkError,
37+
NotFoundError,
38+
):
39+
error = error_type("request failed", status_code=400, response_body="raw")
40+
41+
assert isinstance(error, ShadeError)
42+
assert str(error) == "request failed (status code: 400)"
43+
assert error.response_body == "raw"
44+
45+
46+
def test_package_root_exports_error_classes():
47+
assert shade.ShadeError is ShadeError
48+
assert shade.AuthenticationError is AuthenticationError
49+
assert shade.InvalidRequestError is InvalidRequestError
50+
assert shade.NetworkError is NetworkError
51+
assert shade.NotFoundError is NotFoundError

0 commit comments

Comments
 (0)