From b0cd49aa3f817f1cf4b24d1213548422c279b4c9 Mon Sep 17 00:00:00 2001 From: mukunda katta Date: Tue, 21 Apr 2026 08:00:37 -0700 Subject: [PATCH 1/2] fix(models): retry after pydantic schema rebuild error --- src/openai/_models.py | 7 ++++++- tests/test_models.py | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/openai/_models.py b/src/openai/_models.py index 810e49dfc5..2951b51d47 100644 --- a/src/openai/_models.py +++ b/src/openai/_models.py @@ -36,6 +36,7 @@ ) import pydantic +from pydantic.errors import PydanticUserError from pydantic.fields import FieldInfo from ._types import ( @@ -442,7 +443,11 @@ def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: # TODO return None - schema = cls.__pydantic_core_schema__ + try: + schema = cls.__pydantic_core_schema__ + except PydanticUserError: + cls.model_rebuild(force=True, raise_errors=False) + schema = cls.__pydantic_core_schema__ if schema["type"] == "model": fields = schema["schema"] if fields["type"] == "model-fields": diff --git a/tests/test_models.py b/tests/test_models.py index 588869ee35..63e2ef0b5c 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -9,7 +9,7 @@ from openai._utils import PropertyInfo from openai._compat import PYDANTIC_V1, parse_obj, model_dump, model_json -from openai._models import DISCRIMINATOR_CACHE, BaseModel, construct_type +from openai._models import DISCRIMINATOR_CACHE, BaseModel, _get_extra_fields_type, construct_type class BasicModel(BaseModel): @@ -157,6 +157,43 @@ def test_unknown_fields() -> None: assert model_dump(m2) == {"foo": "foo", "unknown": {"foo_bar": True}} +@pytest.mark.skipif(PYDANTIC_V1, reason="pydantic v2 only") +def test_get_extra_fields_type_rebuilds_after_pydantic_user_error() -> None: + schema = { + "type": "model", + "schema": { + "type": "model-fields", + "extras_schema": {"cls": str}, + }, + } + + class Meta(type): + _schema_calls = 0 + _rebuild_calls = 0 + + @property + def __pydantic_core_schema__(cls) -> dict[str, object]: + type(cls)._schema_calls += 1 + if type(cls)._schema_calls == 1: + raise pydantic.errors.PydanticUserError( + "schema unavailable", + code="schema-unavailable", + ) + return schema + + def model_rebuild(cls, *, force: bool, raise_errors: bool) -> bool: + type(cls)._rebuild_calls += 1 + assert force is True + assert raise_errors is False + return True + + class FakeModel(metaclass=Meta): + pass + + assert _get_extra_fields_type(cast(Any, FakeModel)) is str + assert Meta._rebuild_calls == 1 + + def test_strict_validation_unknown_fields() -> None: class Model(BaseModel): foo: str From 649a772df90f5644d9dd14f2caba232c3187c7bc Mon Sep 17 00:00:00 2001 From: Mukunda Rao Katta Date: Wed, 22 Apr 2026 15:40:06 -0700 Subject: [PATCH 2/2] fix(_models): guard PydanticUserError import for pydantic v1 Per @chatgpt-codex-connector review: pydantic.errors.PydanticUserError does not exist in pydantic v1, so importing it at module load time breaks the declared >=1.9.0,<3 compatibility path and fails the test-pydantic-v1 nox session before the PYDANTIC_V1 branch in _get_extra_fields_type can short-circuit. Wrap the import in try/except ImportError and fall back to a local Exception subclass. On v2 the real PydanticUserError is used. On v1 the fallback is unreachable at runtime because _get_extra_fields_type returns early via 'if PYDANTIC_V1'. --- src/openai/_models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/openai/_models.py b/src/openai/_models.py index 2951b51d47..50773f193c 100644 --- a/src/openai/_models.py +++ b/src/openai/_models.py @@ -36,7 +36,13 @@ ) import pydantic -from pydantic.errors import PydanticUserError + +try: + from pydantic.errors import PydanticUserError +except ImportError: # pydantic v1 does not export PydanticUserError + class PydanticUserError(Exception): # type: ignore[no-redef] + pass + from pydantic.fields import FieldInfo from ._types import (