fix: handle bare dict and list annotations without type arguments - #3760
Open
rohitdeppi38 wants to merge 1 commit into
Open
fix: handle bare dict and list annotations without type arguments#3760rohitdeppi38 wants to merge 1 commit into
dict and list annotations without type arguments#3760rohitdeppi38 wants to merge 1 commit into
Conversation
`transform()` and `construct_type()` both assumed that any `dict` or `list`
annotation is parameterised, and indexed into `get_args()` unconditionally.
For a bare, unparameterised annotation `get_args()` returns an empty tuple,
so the index access raised instead of transforming/constructing the value:
```py
class Params(TypedDict, total=False):
metadata: dict
transform({"metadata": {"key": "value"}}, Params)
# IndexError: tuple index out of range
construct_type(value={"key": "value"}, type_=dict)
# ValueError: not enough values to unpack (expected 2, got 0)
```
The same happened for bare `list` annotations, and in `construct_type()` this
also crashed for any `BaseModel` with a bare `dict`/`list` field, since
`Model.construct()` goes through the same code path.
Bare containers are now treated as if their contents were annotated with
`Any`, matching the existing behaviour of `dict[str, Any]` / `list[Any]`.
Fixes openai#3338
Fixes openai#3341
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3338
Fixes #3341
The bug
transform()andconstruct_type()both assume that anydictorlistannotation is parameterised, and index intoget_args()unconditionally. For a bare, unparameterised annotationget_args()returns an empty tuple, so the index access raises instead of transforming/constructing the value.Two details that weren't in the linked issues:
Bare
listis affected too.construct_type(value=[1, 2], type_=list)raisesIndexErroratargs[0], andtransform([1, 2], list)raisesRuntimeError: Expected type <class 'list'> to have a type argument at index 0viaextract_type_arg. Same root cause, so it's fixed here as well.This reaches real response deserialization. Any
BaseModelwith a baredict/listfield crashes on construction, becauseModel.construct()routes through the sameconstruct_type()branch:The async transform path (
_async_transform_recursive) has the same two call sites and is fixed alongside the sync one.The fix
Bare containers are treated as if their contents were annotated with
Any, which is exactly the existing behaviour fordict[str, Any]/list[Any]— values pass through unchanged, while nestedBaseModelvalues still get dumped for JSON-serializability. This keeps parameterised and unparameterised annotations consistent rather than special-casing bare ones into a "return unchanged" path._transform.py: new_extract_container_arg(typ, index)helper that falls back toobjectwhen the index is out of range, used by thedictandlist/Iterable/Sequencebranches in both the sync and async recursion._models.py:argsis already computed at the top ofconstruct_type, so thedictandlistbranches just guard the index access.Verification
Added
test_bare_dict_annotation/test_bare_list_annotationtotests/test_transform.py(parametrised over sync and async) andtests/test_models.py. All six new cases fail onmainand pass with this change.ruff check,ruff format --check,pyright, andmypyare clean on the four touched files.tests/test_models.py,tests/test_transform.pyandtests/test_utilspass, with no change to the set of pre-existing failures.