From fde9c57dc48df6ac58184c993c03198cfc3e9e96 Mon Sep 17 00:00:00 2001 From: Zawwar Sami Date: Sat, 13 Jun 2026 03:04:10 +0000 Subject: [PATCH 1/2] fix(client): handle bare dict/list annotations in construct_type get_args() returns an empty tuple for an unparameterized `dict` or `list` annotation, so the dict branch's two-value unpack raised ValueError and the list branch's args[0] raised IndexError. Guard both and fall back to `object` as the element type so values pass through unchanged instead of crashing. Fixes #1619, #1626 --- src/anthropic/_models.py | 5 +++-- tests/test_models.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/anthropic/_models.py b/src/anthropic/_models.py index dc00516bc..ac1b212b1 100644 --- a/src/anthropic/_models.py +++ b/src/anthropic/_models.py @@ -647,7 +647,8 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any] if not is_mapping(value): return value - _, items_type = get_args(type_) # Dict[_, items_type] + args = get_args(type_) + items_type = args[1] if len(args) >= 2 else object # Dict[_, items_type] return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} if ( @@ -668,7 +669,7 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any] if not is_list(value): return value - inner_type = args[0] # List[inner_type] + inner_type = args[0] if args else object # List[inner_type] return [construct_type(value=entry, type_=inner_type) for entry in value] if origin == float: diff --git a/tests/test_models.py b/tests/test_models.py index 195f23079..d7e5850fa 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -655,6 +655,18 @@ class Model(BaseModel): assert m.value == "foo" +def test_bare_dict_annotation() -> None: + # a bare `dict` annotation (no type parameters) should not raise + m = construct_type(value={"key": "value", "n": 1}, type_=cast(Any, dict)) + assert m == {"key": "value", "n": 1} + + +def test_bare_list_annotation() -> None: + # a bare `list` annotation (no type parameters) should not raise + m = construct_type(value=["a", "b", 1], type_=cast(Any, list)) + assert m == ["a", "b", 1] + + def test_discriminated_unions_invalid_data() -> None: class A(BaseModel): type: Literal["a"] From 6302050ec85f10fc149e185bb4edd0cb01aa4a84 Mon Sep 17 00:00:00 2001 From: Zawwar Sami Date: Sat, 13 Jun 2026 03:04:15 +0000 Subject: [PATCH 2/2] fix(client): handle bare dict annotation in transform A bare `dict` annotation (no type parameters) made both the sync and async transform helpers index get_args(...)[1] on an empty tuple, raising IndexError. Guard the lookup and fall back to `object` so the mapping is passed through unchanged. Fixes #1628 --- src/anthropic/_utils/_transform.py | 6 ++++-- tests/test_transform.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/anthropic/_utils/_transform.py b/src/anthropic/_utils/_transform.py index 1331da174..0229f3d5c 100644 --- a/src/anthropic/_utils/_transform.py +++ b/src/anthropic/_utils/_transform.py @@ -180,7 +180,8 @@ 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) + items_type = args[1] if len(args) >= 2 else object return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} if ( @@ -348,7 +349,8 @@ 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) + items_type = args[1] if len(args) >= 2 else object 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 513b09cac..406d838de 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -61,6 +61,13 @@ class Baz2(TypedDict): my_baz: Annotated[str, PropertyInfo(alias="myBaz")] +@parametrize +@pytest.mark.asyncio +async def test_bare_dict_annotation(use_async: bool) -> None: + # a bare `dict` annotation (no type parameters) should pass values through unchanged + assert await transform({"key": "value", "n": 1}, cast(Any, dict), use_async) == {"key": "value", "n": 1} + + @parametrize @pytest.mark.asyncio async def test_recursive_typeddict(use_async: bool) -> None: