Skip to content

fix: convert iterators to list in t_contents to avoid confusing errors - #3212

Open
yashdhumane wants to merge 3 commits into
googleapis:mainfrom
yashdhumane:fix/t-contents-iterator-support
Open

yashdhumane wants to merge 3 commits into
googleapis:mainfrom
yashdhumane:fix/t-contents-iterator-support

Conversation

@yashdhumane

Copy link
Copy Markdown

Passing a reversed() iterator or any non-list iterable to generate_content caused confusing errors like 'contents are required' or 'file uri and mime_type are required' instead of a clear message.

Fix converts iterables (generators, reversed, map, etc.) to a list before validation, so they work correctly.

Fixes #1272

Passing a reversed() iterator or any non-list iterable to generate_content
caused confusing errors like 'contents are required' or 'file uri and
mime_type are required' instead of a clear message.

Fix converts iterables (generators, reversed, map, etc.) to a list
before validation, so they work correctly.

Fixes googleapis#1272
@Venkaiahbabuneelam Venkaiahbabuneelam self-assigned this Oct 26, 2026
@Venkaiahbabuneelam Venkaiahbabuneelam added the size:S Code changes < 10 lines label Oct 26, 2026

@chrikrah chrikrah left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yashdhumane this breaks a single Part or File passed as contents, and #1272 still fails through generate_content. Pydantic models define __iter__, so list(contents) yields (field, value) tuples. And _GenerateContentParameters validates contents before t_contents runs, which turns reversed([...]) into [File()].

blocking: materialize an iterator in generate_content and generate_content_stream, sync and async, before the parameter model is built, or raise a TypeError there. The t_contents line can then go, which also clears the red mypy (3.11) check.

# probe: client.models.generate_content(model="gemini-2.5-flash", contents=x), _api_client.request mocked
# CPython 3.12.6, pydantic 2.12.0, google.genai imported from trees exported with git archive
                      base c0e9c54                             head f7f459d
reversed([Content])   ValueError: file uri and mime_type ...   ValueError: file uri and mime_type ...
generator of Content  ValueError: file uri and mime_type ...   ValueError: file uri and mime_type ...
Part(text="hi")       ok, 1 content sent                       ValueError: Unsupported content type: <class 'tuple'>

$ python -m pytest google/genai/tests/transformers -q -p no:cacheprovider
181 passed in 6.15s            # base c0e9c54
1 failed, 180 passed in 6.13s  # head f7f459d, test_file

$ mypy google/genai/           # mypy 2.4.0, CPython 3.11
Success: no issues found in 41 source files   # base c0e9c54
Found 1 error in 1 file (checked 41 source files)   # head f7f459d, _transformers.py:477 arg-type on list(contents)

# not run: the replay tests and anything outside tests/transformers

Could the #1272 test call generate_content with a mocked request, where it fails today?

@yashdhumane

Copy link
Copy Markdown
Author

Thank you for the detailed analysis and the probe script — you were completely right.

The __iter__ check on Pydantic models was the core mistake: Part(text="hi") iterates as (field, value) tuples, breaking single-Part inputs. And the fix was in the wrong place — _GenerateContentParameters already materializes reversed([...]) before t_contents sees it.

Updated fix:

  • Reverted the _transformers.py change
  • Added collections.abc.Iterator / collections.abc.Generator check in all 4 generate_content / generate_content_stream methods (sync + async) before the parameter model is built
  • 180 tests pass (the 1 failing test_function_with_optional_and_union_types is pre-existing and unrelated)

@chrikrah chrikrah left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yashdhumane 1754f71 sends an iterator whole on all four paths, and a single Part works again. From my perspective this can be merged. It does accept more than ContentListUnion, which still says list, so mypy flags reversed(...); widening the generated annotation is a maintainer call.

# probe: generate_content(contents=x) with _api_client request methods stubbed; Python 3.12, pydantic 2.12.0, trees from git archive
                              base c0e9c54                      head 1754f71
reversed([Content, Content])  ValueError: file uri and mime...  sync, stream, async, astream: 2 contents sent
generator of Content          ValueError: file uri and mime...  sync, stream, async, astream: 2 contents sent
Part(text="hi")               1 content sent                    1 content sent
$ mypy google/genai/   # mypy 2.4.0, CPython 3.11, head 1754f71
Success: no issues found in 41 source files
$ python -m pytest google/genai/tests/transformers google/genai/tests/models/test_gc_continuation_token.py -q -p no:cacheprovider
215 passed   # head 1754f71
215 passed   # base c0e9c54

non-blocking: the _transformers.py hunk is now a lone trailing blank line, and the title still names t_contents. No test tells the two commits apart, as the last two lines show. Can you add a mocked reversed([...]) case to test_gc_continuation_token.py?

@yashdhumane

Copy link
Copy Markdown
Author

Added mocked reversed([...]) test case to test_gc_continuation_token.py and removed the trailing blank line from _transformers.py. 35 tests pass on Python 3.12.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S Code changes < 10 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unrelated error shown when trying to use a reversed content list

3 participants