diff --git a/src/musher/_http.py b/src/musher/_http.py index c64c3a1..8c79d7f 100644 --- a/src/musher/_http.py +++ b/src/musher/_http.py @@ -89,7 +89,12 @@ def _raise_for_status(response: httpx.Response) -> None: raise AuthenticationError("Invalid or missing API token") if status == 404: # noqa: PLR2004 - raise BundleNotFoundError(str(response.url)) + try: + body_404: dict[str, object] = response.json() # pyright: ignore[reportAny] + detail_404 = str(body_404.get("detail", "")) + except (ValueError, KeyError): + detail_404 = "" + raise BundleNotFoundError(detail_404 or str(response.url)) if status == 429: # noqa: PLR2004 retry_after_header: str | None = response.headers.get("Retry-After") # pyright: ignore[reportAny] diff --git a/tests/test_http.py b/tests/test_http.py index 66833ce..9bce7e8 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -64,6 +64,22 @@ async def test_404_raises_bundle_not_found(self, transport: HTTPTransport): with pytest.raises(BundleNotFoundError): await transport.get("/v1/test") + @respx.mock + async def test_404_with_json_body_uses_detail(self, transport: HTTPTransport): + respx.get("https://api.test.dev/v1/test").mock( + return_value=httpx.Response( + 404, + json={ + "type": "https://api.platform.musher.dev/errors/not-found", + "title": "Resource Not Found", + "status": 404, + "detail": "Bundle version with identifier '1.0.0' not found", + }, + ) + ) + with pytest.raises(BundleNotFoundError, match="Bundle version with identifier"): + await transport.get("/v1/test") + @respx.mock async def test_429_raises_rate_limit_with_retry_after(self, transport: HTTPTransport): respx.get("https://api.test.dev/v1/test").mock( diff --git a/uv.lock b/uv.lock index cde3b64..78397be 100644 --- a/uv.lock +++ b/uv.lock @@ -1439,7 +1439,7 @@ wheels = [ [[package]] name = "musher-sdk" -version = "0.3.2" +version = "0.3.3" source = { editable = "." } dependencies = [ { name = "httpx" },