Skip to content

Commit 0000dc5

Browse files
committed
feat: add TemporarilyUnavailableError for 503 responses
1 parent 740ca27 commit 0000dc5

File tree

5 files changed

+78
-2
lines changed

5 files changed

+78
-2
lines changed

src/rdapapi/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
RateLimitError,
99
RdapApiError,
1010
SubscriptionRequiredError,
11+
TemporarilyUnavailableError,
1112
UpstreamError,
1213
ValidationError,
1314
)
@@ -43,6 +44,7 @@
4344
"NotFoundError",
4445
"ValidationError",
4546
"RateLimitError",
47+
"TemporarilyUnavailableError",
4648
"UpstreamError",
4749
# Models
4850
"AsnResponse",

src/rdapapi/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
RateLimitError,
1414
RdapApiError,
1515
SubscriptionRequiredError,
16+
TemporarilyUnavailableError,
1617
UpstreamError,
1718
ValidationError,
1819
)
@@ -36,6 +37,7 @@
3637
404: NotFoundError,
3738
429: RateLimitError,
3839
502: UpstreamError,
40+
503: TemporarilyUnavailableError,
3941
}
4042

4143

@@ -56,7 +58,7 @@ def _raise_for_status(response: httpx.Response) -> None:
5658
"error": error,
5759
}
5860

59-
if exc_class is RateLimitError:
61+
if exc_class in (RateLimitError, TemporarilyUnavailableError):
6062
retry_after = response.headers.get("Retry-After")
6163
kwargs["retry_after"] = int(retry_after) if retry_after else None
6264

src/rdapapi/exceptions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"NotFoundError",
1010
"ValidationError",
1111
"RateLimitError",
12+
"TemporarilyUnavailableError",
1213
"UpstreamError",
1314
]
1415

@@ -60,5 +61,20 @@ def __init__(
6061
self.retry_after = retry_after
6162

6263

64+
class TemporarilyUnavailableError(RdapApiError):
65+
"""Raised when the domain data is temporarily unavailable (HTTP 503)."""
66+
67+
def __init__(
68+
self,
69+
message: str,
70+
*,
71+
status_code: int | None = None,
72+
error: str | None = None,
73+
retry_after: int | None = None,
74+
) -> None:
75+
super().__init__(message, status_code=status_code, error=error)
76+
self.retry_after = retry_after
77+
78+
6379
class UpstreamError(RdapApiError):
6480
"""Raised when the upstream RDAP server fails (HTTP 502)."""

tests/test_async.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
import respx
66

7-
from rdapapi import AsyncRdapApi, AuthenticationError, NotFoundError, RateLimitError, SubscriptionRequiredError
7+
from rdapapi import AsyncRdapApi, AuthenticationError, NotFoundError, RateLimitError, SubscriptionRequiredError, TemporarilyUnavailableError
88
from rdapapi.models import BulkDomainResponse
99

1010
BASE_URL = "https://rdapapi.io/api/v1"
@@ -198,6 +198,24 @@ async def test_async_rate_limit_error():
198198
assert exc_info.value.retry_after == 30
199199

200200

201+
@pytest.mark.asyncio
202+
@respx.mock
203+
async def test_async_temporarily_unavailable_error():
204+
respx.get(f"{BASE_URL}/domain/test.com").mock(
205+
return_value=httpx.Response(
206+
503,
207+
json={"error": "temporarily_unavailable", "message": "Data for this domain is temporarily unavailable."},
208+
headers={"Retry-After": "300"},
209+
)
210+
)
211+
212+
async with AsyncRdapApi("test-key", base_url=BASE_URL) as api:
213+
with pytest.raises(TemporarilyUnavailableError) as exc_info:
214+
await api.domain("test.com")
215+
216+
assert exc_info.value.retry_after == 300
217+
218+
201219
# === Async Bulk Domain Lookups ===
202220

203221
BULK_RESPONSE = {

tests/test_client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
RdapApi,
1212
RdapApiError,
1313
SubscriptionRequiredError,
14+
TemporarilyUnavailableError,
1415
UpstreamError,
1516
ValidationError,
1617
)
@@ -333,6 +334,43 @@ def test_rate_limit_error():
333334
api.close()
334335

335336

337+
@respx.mock
338+
def test_temporarily_unavailable_error():
339+
respx.get(f"{BASE_URL}/domain/test.com").mock(
340+
return_value=httpx.Response(
341+
503,
342+
json={"error": "temporarily_unavailable", "message": "Data for this domain is temporarily unavailable."},
343+
headers={"Retry-After": "300"},
344+
)
345+
)
346+
347+
api = RdapApi("test-key", base_url=BASE_URL)
348+
with pytest.raises(TemporarilyUnavailableError) as exc_info:
349+
api.domain("test.com")
350+
351+
assert exc_info.value.status_code == 503
352+
assert exc_info.value.retry_after == 300
353+
api.close()
354+
355+
356+
@respx.mock
357+
def test_temporarily_unavailable_error_without_retry_after():
358+
respx.get(f"{BASE_URL}/domain/test.com").mock(
359+
return_value=httpx.Response(
360+
503,
361+
json={"error": "temporarily_unavailable", "message": "Data for this domain is temporarily unavailable."},
362+
)
363+
)
364+
365+
api = RdapApi("test-key", base_url=BASE_URL)
366+
with pytest.raises(TemporarilyUnavailableError) as exc_info:
367+
api.domain("test.com")
368+
369+
assert exc_info.value.status_code == 503
370+
assert exc_info.value.retry_after is None
371+
api.close()
372+
373+
336374
@respx.mock
337375
def test_validation_error():
338376
respx.get(f"{BASE_URL}/domain/invalid").mock(

0 commit comments

Comments
 (0)