From 00d02c49fad10fe8ae784d7f70f8290b6ca534d1 Mon Sep 17 00:00:00 2001 From: lrg913427-dot Date: Mon, 1 Jun 2026 22:30:04 +0800 Subject: [PATCH] fix: handle bare dict annotation in _transform_recursive When a TypedDict field is annotated with a bare dict (e.g. metadata: dict instead of metadata: dict[str, str]), get_args() returns an empty tuple. The previous code unconditionally indexed [1] from it, causing IndexError. Add a length check before indexing and return the data as-is for bare dicts since there are no type parameters to recurse into. Fixes #3338 --- src/openai/_utils/_transform.py | 10 ++++++++-- tests/test_transform.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/openai/_utils/_transform.py b/src/openai/_utils/_transform.py index 414f38c340..7655e55fcc 100644 --- a/src/openai/_utils/_transform.py +++ b/src/openai/_utils/_transform.py @@ -180,7 +180,10 @@ def _transform_recursive( return _transform_typeddict(data, stripped_type) if origin == dict and is_mapping(data): - items_type = get_args(stripped_type)[1] + args = get_args(stripped_type) + if len(args) < 2: + return cast(object, data) + items_type = args[1] return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} if ( @@ -346,7 +349,10 @@ async def _async_transform_recursive( return await _async_transform_typeddict(data, stripped_type) if origin == dict and is_mapping(data): - items_type = get_args(stripped_type)[1] + args = get_args(stripped_type) + if len(args) < 2: + return cast(object, data) + items_type = args[1] return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} if ( diff --git a/tests/test_transform.py b/tests/test_transform.py index bece75dfc7..483f87a8e9 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -397,6 +397,22 @@ class DictItems(TypedDict): assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}} + +class BareDictTypedDict(TypedDict, total=False): + metadata: dict # bare dict — no type parameters + + +@parametrize +@pytest.mark.asyncio +async def test_bare_dict_in_typeddict(use_async: bool) -> None: + """transform should not crash on bare dict annotations in TypedDict.""" + result = await transform({"metadata": {"key": "value"}}, BareDictTypedDict, use_async) + assert result == {"metadata": {"key": "value"}} + + result = await transform({"metadata": {}}, BareDictTypedDict, use_async) + assert result == {"metadata": {}} + + class TypedDictIterableUnionStr(TypedDict): foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")]