1+ # Copyright 2025 Google LLC
2+ #
3+ # Licensed under the Apache License, Version 2.0 (the "License");
4+ # you may not use this file except in compliance with the License.
5+ # You may obtain a copy of the License at
6+ #
7+ # http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # Unless required by applicable law or agreed to in writing, software
10+ # distributed under the License is distributed on an "AS IS" BASIS,
11+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ # See the License for the specific language governing permissions and
13+ # limitations under the License.
14+ #
15+
116"""Tests to verify that mypy correctly handles list[pydantic.BaseModel] in response.parsed."""
217
318from typing import List , cast
@@ -27,11 +42,11 @@ class Recipe(BaseModel):
2742 # Create a mock response (simulating what we'd get from the API)
2843 response = types .GenerateContentResponse ()
2944
30- # Before the fix, this next line would cause a mypy error:
45+ # Before the fix[issue #886] , this next line would cause a mypy error:
3146 # Incompatible types in assignment (expression has type "List[Recipe]",
3247 # variable has type "Optional[Union[BaseModel, Dict[Any, Any], Enum]]")
3348 #
34- # With our fix adding list[pydantic.BaseModel] to the Union, this is now valid:
49+ # With the fix adding list[pydantic.BaseModel] to the Union, this is now valid:
3550 response .parsed = [
3651 Recipe (
3752 recipe_name = "Chocolate Chip Cookies" ,
@@ -48,16 +63,16 @@ class Recipe(BaseModel):
4863 # and require a cast:
4964 # parsed_items = cast(list[Recipe], response.parsed)
5065
51- # With our fix, we can directly use it as a list without casting:
66+ # With the fix, we can directly use it as a list without casting:
5267 recipes = response .parsed
5368
54- # We can iterate over the list without casting
69+ # Now iteration over the list without casting is possible
5570 for recipe in recipes :
5671 logger .info (f"Recipe: { recipe .recipe_name } " )
5772 for ingredient in recipe .ingredients :
5873 logger .info (f" - { ingredient } " )
5974
60- # We can access elements by index without casting
75+ # Also accessing elements by index without casting is possible
6176 first_recipe = recipes [0 ]
6277 logger .info (f"First recipe: { first_recipe .recipe_name } " )
6378
@@ -74,12 +89,16 @@ class Recipe(FoodItem):
7489 response = types .GenerateContentResponse ()
7590
7691 # Before the fix, this would require a cast with mypy
77- # Now it works directly with our enhanced type annotation
92+ # Now it works directly with the enhanced type annotation
7893 response .parsed = [
7994 Recipe (
80- name = "Chocolate Chip Cookies" , ingredients = ["Flour" , "Sugar" , "Chocolate" ]
95+ name = "Chocolate Chip Cookies" ,
96+ ingredients = ["Flour" , "Sugar" , "Chocolate" ],
97+ ),
98+ Recipe (
99+ name = "Oatmeal Cookies" ,
100+ ingredients = ["Oats" , "Flour" , "Brown Sugar" ],
81101 ),
82- Recipe (name = "Oatmeal Cookies" , ingredients = ["Oats" , "Flour" , "Brown Sugar" ]),
83102 ]
84103
85104 if response .parsed is not None :
@@ -157,7 +176,7 @@ class Recipe(BaseModel):
157176 ]
158177
159178 if response .parsed is not None :
160- # Before our fix, you'd need this cast for mypy to be happy
179+ # Before the fix, you'd need this cast for mypy to work successfully
161180 recipes = cast (List [Recipe ], response .parsed )
162181
163182 # Using the cast list
0 commit comments