diff --git a/src/openai/_exceptions.py b/src/openai/_exceptions.py index 7a30e4a336..17e89e8f2f 100644 --- a/src/openai/_exceptions.py +++ b/src/openai/_exceptions.py @@ -69,7 +69,8 @@ def __init__(self, message: str, request: httpx2.Request, *, body: object | None self.body = body if is_dict(body): - self.code = cast(Any, construct_type(type_=Optional[str], value=body.get("code"))) + code = body.get("code") + self.code = str(code) if code is not None else None self.param = cast(Any, construct_type(type_=Optional[str], value=body.get("param"))) self.type = cast(Any, construct_type(type_=str, value=body.get("type"))) else: diff --git a/tests/test_client.py b/tests/test_client.py index 7fa7ee31bd..3281add6d2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -123,6 +123,40 @@ def _get_open_connections(client: OpenAI | AsyncOpenAI) -> int: class TestOpenAI: + @pytest.mark.parametrize( + "code_fields,expected_code", + [ + ({"code": 404}, "404"), + ({"code": 0}, "0"), + ({"code": "invalid_request"}, "invalid_request"), + ({"code": ""}, ""), + ({"code": None}, None), + ({}, None), + ], + ) + @pytest.mark.respx2(base_url=base_url) + def test_api_error_code_is_string( + self, + code_fields: dict[str, object], + expected_code: str | None, + respx2_mock: MockRouter, + client: OpenAI, + ) -> None: + body = {"message": "Example error", "type": "invalid_request_error", "param": "model", **code_fields} + response = httpx2.Response(400, json={"error": body}) + respx2_mock.get("/foo").mock(return_value=response) + + with pytest.raises(APIStatusError) as exc_info: + client.get("/foo", cast_to=httpx2.Response) + + error = exc_info.value + assert error.code == expected_code + assert error.body == body + assert error.response.json() == {"error": body} + assert error.status_code == 400 + assert error.type == "invalid_request_error" + assert error.param == "model" + @pytest.mark.respx2(base_url=base_url) def test_raw_response(self, respx2_mock: MockRouter, client: OpenAI) -> None: respx2_mock.post("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"})) @@ -1454,6 +1488,40 @@ def test_copy_auth(self) -> None: class TestAsyncOpenAI: + @pytest.mark.parametrize( + "code_fields,expected_code", + [ + ({"code": 404}, "404"), + ({"code": 0}, "0"), + ({"code": "invalid_request"}, "invalid_request"), + ({"code": ""}, ""), + ({"code": None}, None), + ({}, None), + ], + ) + @pytest.mark.respx2(base_url=base_url) + async def test_api_error_code_is_string( + self, + code_fields: dict[str, object], + expected_code: str | None, + respx2_mock: MockRouter, + async_client: AsyncOpenAI, + ) -> None: + body = {"message": "Example error", "type": "invalid_request_error", "param": "model", **code_fields} + response = httpx2.Response(400, json={"error": body}) + respx2_mock.get("/foo").mock(return_value=response) + + with pytest.raises(APIStatusError) as exc_info: + await async_client.get("/foo", cast_to=httpx2.Response) + + error = exc_info.value + assert error.code == expected_code + assert error.body == body + assert error.response.json() == {"error": body} + assert error.status_code == 400 + assert error.type == "invalid_request_error" + assert error.param == "model" + @pytest.mark.respx2(base_url=base_url) async def test_raw_response(self, respx2_mock: MockRouter, async_client: AsyncOpenAI) -> None: respx2_mock.post("/foo").mock(return_value=httpx2.Response(200, json={"foo": "bar"}))