|  | 
| 19 | 19 | import datetime | 
| 20 | 20 | from enum import Enum, EnumMeta | 
| 21 | 21 | import inspect | 
|  | 22 | +import io | 
| 22 | 23 | import json | 
| 23 | 24 | import logging | 
| 24 | 25 | import sys | 
| 25 | 26 | import types as builtin_types | 
| 26 | 27 | import typing | 
| 27 |  | -from typing import Any, Callable, Literal, Optional, Sequence, Union, _UnionGenericAlias  # type: ignore | 
|  | 28 | +from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Union, _UnionGenericAlias  # type: ignore | 
| 28 | 29 | import pydantic | 
| 29 | 30 | from pydantic import ConfigDict, Field, PrivateAttr, model_validator | 
| 30 | 31 | from typing_extensions import Self, TypedDict | 
| @@ -1260,6 +1261,81 @@ class Part(_common.BaseModel): | 
| 1260 | 1261 |       default=None, description="""Optional. Text part (can be code).""" | 
| 1261 | 1262 |   ) | 
| 1262 | 1263 | 
 | 
|  | 1264 | +  def __init__( | 
|  | 1265 | +      self, | 
|  | 1266 | +      value: Optional['PartUnionDict'] = None, | 
|  | 1267 | +      /, | 
|  | 1268 | +      *, | 
|  | 1269 | +      video_metadata: Optional[VideoMetadata] = None, | 
|  | 1270 | +      thought: Optional[bool] = None, | 
|  | 1271 | +      inline_data: Optional[Blob] = None, | 
|  | 1272 | +      file_data: Optional[FileData] = None, | 
|  | 1273 | +      thought_signature: Optional[bytes] = None, | 
|  | 1274 | +      function_call: Optional[FunctionCall] = None, | 
|  | 1275 | +      code_execution_result: Optional[CodeExecutionResult] = None, | 
|  | 1276 | +      executable_code: Optional[ExecutableCode] = None, | 
|  | 1277 | +      function_response: Optional[FunctionResponse] = None, | 
|  | 1278 | +      text: Optional[str] = None, | 
|  | 1279 | +      # Pydantic allows CamelCase in addition to snake_case attribute | 
|  | 1280 | +      # names. kwargs here catch these aliases. | 
|  | 1281 | +      **kwargs: Any, | 
|  | 1282 | +  ): | 
|  | 1283 | +    part_dict = dict( | 
|  | 1284 | +        video_metadata=video_metadata, | 
|  | 1285 | +        thought=thought, | 
|  | 1286 | +        inline_data=inline_data, | 
|  | 1287 | +        file_data=file_data, | 
|  | 1288 | +        thought_signature=thought_signature, | 
|  | 1289 | +        function_call=function_call, | 
|  | 1290 | +        code_execution_result=code_execution_result, | 
|  | 1291 | +        executable_code=executable_code, | 
|  | 1292 | +        function_response=function_response, | 
|  | 1293 | +        text=text, | 
|  | 1294 | +    ) | 
|  | 1295 | +    part_dict = {k: v for k, v in part_dict.items() if v is not None} | 
|  | 1296 | + | 
|  | 1297 | +    if part_dict and value is not None: | 
|  | 1298 | +      raise ValueError( | 
|  | 1299 | +          'Positional and keyword arguments can not be combined when ' | 
|  | 1300 | +          'initializing a Part.' | 
|  | 1301 | +      ) | 
|  | 1302 | + | 
|  | 1303 | +    if value is None: | 
|  | 1304 | +      pass | 
|  | 1305 | +    elif isinstance(value, str): | 
|  | 1306 | +      part_dict['text'] = value | 
|  | 1307 | +    elif isinstance(value, File): | 
|  | 1308 | +      if not value.uri or not value.mime_type: | 
|  | 1309 | +        raise ValueError('file uri and mime_type are required.') | 
|  | 1310 | +      part_dict['file_data'] = FileData( | 
|  | 1311 | +          file_uri=value.uri, | 
|  | 1312 | +          mime_type=value.mime_type, | 
|  | 1313 | +          display_name=value.display_name, | 
|  | 1314 | +      ) | 
|  | 1315 | +    elif isinstance(value, dict): | 
|  | 1316 | +      try: | 
|  | 1317 | +        Part.model_validate(value) | 
|  | 1318 | +        part_dict.update(value)  # type: ignore[arg-type] | 
|  | 1319 | +      except pydantic.ValidationError: | 
|  | 1320 | +        part_dict['file_data'] = FileData.model_validate(value) | 
|  | 1321 | +    elif isinstance(value, Part): | 
|  | 1322 | +      part_dict.update(value.dict()) | 
|  | 1323 | +    elif 'image' in value.__class__.__name__.lower(): | 
|  | 1324 | +      # PIL.Image case. | 
|  | 1325 | + | 
|  | 1326 | +      suffix = value.format.lower() if value.format else 'jpeg' | 
|  | 1327 | +      mimetype = f'image/{suffix}' | 
|  | 1328 | +      bytes_io = io.BytesIO() | 
|  | 1329 | +      value.save(bytes_io, suffix.upper()) | 
|  | 1330 | + | 
|  | 1331 | +      part_dict['inline_data'] = Blob( | 
|  | 1332 | +          data=bytes_io.getvalue(), mime_type=mimetype | 
|  | 1333 | +      ) | 
|  | 1334 | +    else: | 
|  | 1335 | +      raise ValueError(f'Unsupported content part type: {type(value)}') | 
|  | 1336 | + | 
|  | 1337 | +    super().__init__(**part_dict, **kwargs) | 
|  | 1338 | + | 
| 1263 | 1339 |   def as_image(self) -> Optional['Image']: | 
| 1264 | 1340 |     """Returns the part as a PIL Image, or None if the part is not an image.""" | 
| 1265 | 1341 |     if not self.inline_data: | 
|  | 
0 commit comments