Skip to content

Commit

Permalink
fix(client): don't error by default for unexpected content types (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot authored Sep 22, 2023
1 parent 43544a6 commit 76cfcf9
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 6 deletions.
14 changes: 11 additions & 3 deletions src/anthropic/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,9 +531,17 @@ def _process_response(
# in the response, e.g. application/json; charset=utf-8
content_type, *_ = response.headers.get("content-type").split(";")
if content_type != "application/json":
raise ValueError(
f"Expected Content-Type response header to be `application/json` but received {content_type} instead."
)
if self._strict_response_validation:
raise exceptions.APIResponseValidationError(
response=response,
request=response.request,
message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
)

# If the API responds with content that isn't JSON then we just return
# the (decoded) text without performing any parsing so that you can still
# handle the response however you need to.
return response.text # type: ignore

data = response.json()
return self._process_response_data(data=data, cast_to=cast_to, response=response)
Expand Down
6 changes: 4 additions & 2 deletions src/anthropic/_base_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from typing_extensions import Literal

from httpx import Request, Response
Expand All @@ -17,8 +19,8 @@ class APIResponseValidationError(APIError):
response: Response
status_code: int

def __init__(self, request: Request, response: Response) -> None:
super().__init__("Data returned by API invalid for expected schema.", request)
def __init__(self, request: Request, response: Response, *, message: str | None = None) -> None:
super().__init__(message or "Data returned by API invalid for expected schema.", request)
self.response = response
self.status_code = response.status_code

Expand Down
37 changes: 36 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import pytest
from respx import MockRouter

from anthropic import Anthropic, AsyncAnthropic
from anthropic import Anthropic, AsyncAnthropic, APIResponseValidationError
from anthropic._types import Omit
from anthropic._models import BaseModel, FinalRequestOptions
from anthropic._streaming import Stream, AsyncStream
Expand Down Expand Up @@ -383,6 +383,23 @@ class Model(BaseModel):
response = self.client.post("/foo", cast_to=Model, stream=True)
assert isinstance(response, Stream)

@pytest.mark.respx(base_url=base_url)
def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
class Model(BaseModel):
name: str

respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))

strict_client = Anthropic(base_url=base_url, api_key=api_key, _strict_response_validation=True)

with pytest.raises(APIResponseValidationError):
strict_client.get("/foo", cast_to=Model)

client = Anthropic(base_url=base_url, api_key=api_key, _strict_response_validation=False)

response = client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]


class TestAsyncAnthropic:
client = AsyncAnthropic(base_url=base_url, api_key=api_key, _strict_response_validation=True)
Expand Down Expand Up @@ -740,3 +757,21 @@ class Model(BaseModel):

response = await self.client.post("/foo", cast_to=Model, stream=True)
assert isinstance(response, AsyncStream)

@pytest.mark.respx(base_url=base_url)
@pytest.mark.asyncio
async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
class Model(BaseModel):
name: str

respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))

strict_client = AsyncAnthropic(base_url=base_url, api_key=api_key, _strict_response_validation=True)

with pytest.raises(APIResponseValidationError):
await strict_client.get("/foo", cast_to=Model)

client = AsyncAnthropic(base_url=base_url, api_key=api_key, _strict_response_validation=False)

response = await client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]

0 comments on commit 76cfcf9

Please sign in to comment.