Skip to content

Commit 822c37d

Browse files
stainless-app[bot]stainless-bot
authored andcommitted
chore(client): fix parsing union responses when non-json is returned (#1665)
1 parent 30215c1 commit 822c37d

File tree

3 files changed

+61
-2
lines changed

3 files changed

+61
-2
lines changed

src/openai/_models.py

+2
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ def is_basemodel(type_: type) -> bool:
380380

381381
def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]:
382382
origin = get_origin(type_) or type_
383+
if not inspect.isclass(origin):
384+
return False
383385
return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)
384386

385387

tests/test_legacy_response.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import cast
2+
from typing import Any, Union, cast
33
from typing_extensions import Annotated
44

55
import httpx
@@ -81,3 +81,23 @@ def test_response_parse_annotated_type(client: OpenAI) -> None:
8181
)
8282
assert obj.foo == "hello!"
8383
assert obj.bar == 2
84+
85+
86+
class OtherModel(pydantic.BaseModel):
87+
a: str
88+
89+
90+
@pytest.mark.parametrize("client", [False], indirect=True) # loose validation
91+
def test_response_parse_expect_model_union_non_json_content(client: OpenAI) -> None:
92+
response = LegacyAPIResponse(
93+
raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
94+
client=client,
95+
stream=False,
96+
stream_cls=None,
97+
cast_to=str,
98+
options=FinalRequestOptions.construct(method="get", url="/foo"),
99+
)
100+
101+
obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
102+
assert isinstance(obj, str)
103+
assert obj == "foo"

tests/test_response.py

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import List, cast
2+
from typing import Any, List, Union, cast
33
from typing_extensions import Annotated
44

55
import httpx
@@ -188,3 +188,40 @@ async def test_async_response_parse_annotated_type(async_client: AsyncOpenAI) ->
188188
)
189189
assert obj.foo == "hello!"
190190
assert obj.bar == 2
191+
192+
193+
class OtherModel(BaseModel):
194+
a: str
195+
196+
197+
@pytest.mark.parametrize("client", [False], indirect=True) # loose validation
198+
def test_response_parse_expect_model_union_non_json_content(client: OpenAI) -> None:
199+
response = APIResponse(
200+
raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
201+
client=client,
202+
stream=False,
203+
stream_cls=None,
204+
cast_to=str,
205+
options=FinalRequestOptions.construct(method="get", url="/foo"),
206+
)
207+
208+
obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
209+
assert isinstance(obj, str)
210+
assert obj == "foo"
211+
212+
213+
@pytest.mark.asyncio
214+
@pytest.mark.parametrize("async_client", [False], indirect=True) # loose validation
215+
async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncOpenAI) -> None:
216+
response = AsyncAPIResponse(
217+
raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
218+
client=async_client,
219+
stream=False,
220+
stream_cls=None,
221+
cast_to=str,
222+
options=FinalRequestOptions.construct(method="get", url="/foo"),
223+
)
224+
225+
obj = await response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
226+
assert isinstance(obj, str)
227+
assert obj == "foo"

0 commit comments

Comments
 (0)