From 295bdf163d05b28a5ab7e5bbffe1e8ee8972e486 Mon Sep 17 00:00:00 2001 From: Rajeev91691 Date: Thu, 30 Jul 2026 22:50:01 +0530 Subject: [PATCH] fix(exceptions): support integer error codes in APIError validation Widen APIError.code and ErrorObject.code types from Optional[str] to Optional[Union[str, int]] to prevent runtime validation failures when API error responses return numeric error codes. Add test to verify correct parsing of numeric error codes. --- src/openai/_exceptions.py | 6 ++--- src/openai/types/shared/error_object.py | 4 +-- tests/test_exceptions_custom.py | 33 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 tests/test_exceptions_custom.py diff --git a/src/openai/_exceptions.py b/src/openai/_exceptions.py index 86f44b0e15..6e5ef7c484 100644 --- a/src/openai/_exceptions.py +++ b/src/openai/_exceptions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, cast +from typing import TYPE_CHECKING, Any, Optional, cast, Union from typing_extensions import Literal import httpx @@ -60,7 +60,7 @@ class APIError(OpenAIError): If there was no response associated with this error then it will be `None`. """ - code: Optional[str] = None + code: Optional[Union[str, int]] = None param: Optional[str] = None type: Optional[str] @@ -71,7 +71,7 @@ def __init__(self, message: str, request: httpx.Request, *, body: object | None) self.body = body if is_dict(body): - self.code = cast(Any, construct_type(type_=Optional[str], value=body.get("code"))) + self.code = cast(Any, construct_type(type_=Optional[Union[str, int]], value=body.get("code"))) 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/src/openai/types/shared/error_object.py b/src/openai/types/shared/error_object.py index 32d7045e00..7fb2504ca5 100644 --- a/src/openai/types/shared/error_object.py +++ b/src/openai/types/shared/error_object.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Optional, Union from ..._models import BaseModel @@ -8,7 +8,7 @@ class ErrorObject(BaseModel): - code: Optional[str] = None + code: Optional[Union[str, int]] = None message: str diff --git a/tests/test_exceptions_custom.py b/tests/test_exceptions_custom.py new file mode 100644 index 0000000000..6e2b22002b --- /dev/null +++ b/tests/test_exceptions_custom.py @@ -0,0 +1,33 @@ +import httpx +import pytest +import openai +from openai import OpenAI, BadRequestError +from respx import MockRouter + +base_url = "https://api.openai.com/v1" + +@pytest.mark.respx(base_url=base_url) +def test_error_code_integer_parsing(respx_mock: MockRouter) -> None: + respx_mock.post("/chat/completions").mock( + return_value=httpx.Response( + 400, + json={ + "error": { + "message": "The request is invalid.", + "type": "invalid_request_error", + "code": 400, + "param": "model" + } + } + ) + ) + + client = OpenAI(base_url=base_url, api_key="test-api-key") + + with pytest.raises(BadRequestError) as exc_info: + client.chat.completions.create(messages=[{"role": "user", "content": "hello"}], model="gpt-4") + + assert exc_info.value.status_code == 400 + assert exc_info.value.code == 400 + assert "The request is invalid." in exc_info.value.message + assert exc_info.value.param == "model"