google-genai 1.41.0__py3-none-any.whl → 1.42.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,6 +41,28 @@ from . import types
41
41
 
42
42
  logger = logging.getLogger('google_genai._transformers')
43
43
 
44
+
45
+ def _is_duck_type_of(obj: Any, cls: type[pydantic.BaseModel]) -> bool:
46
+ """Checks if an object has all of the fields of a Pydantic model.
47
+
48
+ This is a duck-typing alternative to `isinstance` to solve dual-import
49
+ problems. It returns False for dictionaries, which should be handled by
50
+ `isinstance(obj, dict)`.
51
+
52
+ Args:
53
+ obj: The object to check.
54
+ cls: The Pydantic model class to duck-type against.
55
+
56
+ Returns:
57
+ True if the object has all the fields defined in the Pydantic model, False
58
+ otherwise.
59
+ """
60
+ if isinstance(obj, dict) or not hasattr(cls, 'model_fields'):
61
+ return False
62
+
63
+ # Check if the object has all of the Pydantic model's defined fields.
64
+ return all(hasattr(obj, field) for field in cls.model_fields)
65
+
44
66
  if sys.version_info >= (3, 10):
45
67
  VersionedUnionType = builtin_types.UnionType
46
68
  _UNION_TYPES = (typing.Union, builtin_types.UnionType)
@@ -366,9 +388,12 @@ def t_part(part: Optional[types.PartUnionDict]) -> types.Part:
366
388
  raise ValueError('file uri and mime_type are required.')
367
389
  return types.Part.from_uri(file_uri=part.uri, mime_type=part.mime_type)
368
390
  if isinstance(part, dict):
369
- return types.Part.model_validate(part)
370
- if isinstance(part, types.Part):
371
- return part
391
+ try:
392
+ return types.Part.model_validate(part)
393
+ except pydantic.ValidationError:
394
+ return types.Part(file_data=types.FileData.model_validate(part))
395
+ if _is_duck_type_of(part, types.Part):
396
+ return part # type: ignore[return-value]
372
397
 
373
398
  if 'image' in part.__class__.__name__.lower():
374
399
  try:
@@ -420,7 +445,7 @@ ContentType = Union[types.Content, types.ContentDict, types.PartUnionDict]
420
445
 
421
446
 
422
447
  def t_content(
423
- content: Optional[ContentType],
448
+ content: Union[ContentType, types.ContentDict, None],
424
449
  ) -> types.Content:
425
450
  if content is None:
426
451
  raise ValueError('content is required.')
@@ -430,12 +455,14 @@ def t_content(
430
455
  try:
431
456
  return types.Content.model_validate(content)
432
457
  except pydantic.ValidationError:
433
- possible_part = types.Part.model_validate(content)
458
+ possible_part = t_part(content) # type: ignore[arg-type]
434
459
  return (
435
460
  types.ModelContent(parts=[possible_part])
436
461
  if possible_part.function_call
437
462
  else types.UserContent(parts=[possible_part])
438
463
  )
464
+ if isinstance(content, types.File):
465
+ return types.UserContent(parts=[t_part(content)])
439
466
  if isinstance(content, types.Part):
440
467
  return (
441
468
  types.ModelContent(parts=[content])
@@ -495,11 +522,18 @@ def t_contents(
495
522
  return True
496
523
 
497
524
  if isinstance(part, dict):
525
+ if not part:
526
+ # Empty dict should be considered as Content, not Part.
527
+ return False
498
528
  try:
499
529
  types.Part.model_validate(part)
500
530
  return True
501
531
  except pydantic.ValidationError:
502
- return False
532
+ try:
533
+ types.FileData.model_validate(part)
534
+ return True
535
+ except pydantic.ValidationError:
536
+ return False
503
537
 
504
538
  if 'image' in part.__class__.__name__.lower():
505
539
  try:
@@ -553,16 +587,12 @@ def t_contents(
553
587
  # append to result
554
588
  # if list, we only accept a list of types.PartUnion
555
589
  for content in contents:
556
- if (
557
- isinstance(content, types.Content)
558
- # only allowed inner list is a list of types.PartUnion
559
- or isinstance(content, list)
560
- ):
590
+ if _is_duck_type_of(content, types.Content) or isinstance(content, list):
561
591
  _append_accumulated_parts_as_content(result, accumulated_parts)
562
592
  if isinstance(content, list):
563
593
  result.append(types.UserContent(parts=content)) # type: ignore[arg-type]
564
594
  else:
565
- result.append(content)
595
+ result.append(content) # type: ignore[arg-type]
566
596
  elif _is_part(content):
567
597
  _handle_current_part(result, accumulated_parts, content)
568
598
  elif isinstance(content, dict):