Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions google/genai/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ def _remove_extra_fields(model: Any, response: dict[str, object]) -> None:
Mutates the response in place.
"""

# Models are built on first use (`defer_build`), and an unbuilt model's field
# annotations still hold the forward references they were declared with, e.g.
# the string 'Part' rather than the `Part` class. Building the model resolves
# them, and costs nothing extra here because the caller validates against
# this model immediately afterwards.
if isinstance(model, type) and issubclass(model, pydantic.BaseModel):
model.model_rebuild(raise_errors=False)

key_values = list(response.items())

for key, value in key_values:
Expand Down
27 changes: 27 additions & 0 deletions google/genai/tests/common/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,3 +952,30 @@ class TestModel(_common.BaseModel):
TestModel.model_validate(data)

assert len(caplog.records) == 0


class _ForwardRefOuterModel(_common.BaseModel):
# Declared before the model it points at, the way the generated types
# sometimes are, so the annotation is a forward reference.
items: Optional[List["_ForwardRefInnerModel"]] = None


class _ForwardRefInnerModel(_common.BaseModel):
value: Optional[str] = None


def test_from_response_resolves_forward_references():
"""Extra fields are pruned from nested models declared as forward references.

Models are built on first use, so until something builds the outer model its
`items` annotation holds an unresolved reference to `_ForwardRefInnerModel`
rather than the class, and the pruning walk has nothing to recurse into.
"""
assert not _ForwardRefOuterModel.__pydantic_complete__

result = _ForwardRefOuterModel._from_response(
response={"items": [{"value": "a", "extraField": 1}]}, kwargs={}
)

assert result.items is not None
assert result.items[0].value == "a"
Loading