Skip to content

Commit 73781ec

Browse files
authored
Merge branch 'main' into feature/11-rate-limit-handling
2 parents 571ca50 + d9eb3d8 commit 73781ec

3 files changed

Lines changed: 98 additions & 2 deletions

File tree

src/shade/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
from .errors import HTTPError, RateLimitError, ShadeError
2+
from .errors import (
3+
AuthenticationError,
4+
InvalidRequestError,
5+
NetworkError,
6+
NotFoundError,
7+
ShadeError,
8+
)
29
from .gateway import Gateway
310
from .http import AsyncHTTPClient, SyncHTTPClient
411

@@ -11,4 +18,11 @@
1118
"ShadeError",
1219
"HTTPError",
1320
"RateLimitError",
14-
]
21+
]
22+
"AuthenticationError",
23+
"Gateway",
24+
"InvalidRequestError",
25+
"NetworkError",
26+
"NotFoundError",
27+
"ShadeError",
28+
]

src/shade/errors.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,35 @@ def __str__(self) -> str: # pragma: no cover
3939
base = super().__str__()
4040
if self.retry_after is not None:
4141
return f"{base} (retry after {self.retry_after}s)"
42-
return base
42+
return base
43+
def __init__(
44+
self,
45+
message: str,
46+
status_code: Optional[int] = None,
47+
response_body: Optional[str] = None,
48+
) -> None:
49+
self.message = message
50+
self.status_code = status_code
51+
self.response_body = response_body
52+
super().__init__(message)
53+
54+
def __str__(self) -> str:
55+
if self.status_code is None:
56+
return self.message
57+
return f"{self.message} (status code: {self.status_code})"
58+
59+
60+
class AuthenticationError(ShadeError):
61+
"""Raised when authentication fails or credentials are invalid."""
62+
63+
64+
class InvalidRequestError(ShadeError):
65+
"""Raised when a request is malformed or rejected by validation."""
66+
67+
68+
class NotFoundError(ShadeError):
69+
"""Raised when an API resource cannot be found."""
70+
71+
72+
class NetworkError(ShadeError):
73+
"""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)