diff --git a/src/openai/_utils/_transform.py b/src/openai/_utils/_transform.py index 304fd12ffe..9f1e66ebb5 100644 --- a/src/openai/_utils/_transform.py +++ b/src/openai/_utils/_transform.py @@ -387,7 +387,7 @@ async def _async_transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True, mode="json") + return model_dump(data, exclude_unset=True, mode="json", exclude=getattr(data, "__api_exclude__", None)) annotated_type = _get_annotated_type(annotation) if annotated_type is None: diff --git a/tests/test_transform.py b/tests/test_transform.py index 93f7ad8dd8..a6f2e6331b 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -357,6 +357,21 @@ async def test_pydantic_default_field(use_async: bool) -> None: assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"} +class ModelWithApiExcludedField(BaseModel): + foo: str + client_only: Union[str, None] = None + + __api_exclude__ = {"client_only"} + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_model_api_exclude(use_async: bool) -> None: + model = ModelWithApiExcludedField(foo="hello!", client_only="secret") + assert cast(Any, await transform(model, Any, use_async)) == {"foo": "hello!"} + assert cast(Any, await transform([model], List[ModelWithApiExcludedField], use_async)) == [{"foo": "hello!"}] + + class TypedDictIterableUnion(TypedDict): foo: Annotated[Union[Bar8, Iterable[Baz8]], PropertyInfo(alias="FOO")]