google-genai 0.8.0__py3-none-any.whl → 1.0.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.
@@ -99,6 +99,19 @@ class HttpRequest:
99
99
  timeout: Optional[float] = None
100
100
 
101
101
 
102
+ # TODO(b/394358912): Update this class to use a SDKResponse class that can be
103
+ # generated and used for all languages.
104
+ @dataclass
105
+ class BaseResponse:
106
+ http_headers: dict[str, str]
107
+
108
+ @property
109
+ def dict(self) -> dict[str, Any]:
110
+ if isinstance(self, dict):
111
+ return self
112
+ return {'httpHeaders': self.http_headers}
113
+
114
+
102
115
  class HttpResponse:
103
116
 
104
117
  def __init__(
@@ -434,18 +447,12 @@ class ApiClient:
434
447
  http_method, path, request_dict, http_options
435
448
  )
436
449
  response = self._request(http_request, stream=False)
437
- if http_options:
438
- if (
439
- isinstance(http_options, HttpOptions)
440
- and http_options.deprecated_response_payload is not None
441
- ):
442
- response._copy_to_dict(http_options.deprecated_response_payload)
443
- elif (
444
- isinstance(http_options, dict)
445
- and 'deprecated_response_payload' in http_options
446
- ):
447
- response._copy_to_dict(http_options['deprecated_response_payload'])
448
- return response.json
450
+ json_response = response.json
451
+ if not json_response:
452
+ base_response = BaseResponse(response.headers).dict
453
+ return base_response
454
+
455
+ return json_response
449
456
 
450
457
  def request_streamed(
451
458
  self,
@@ -459,10 +466,6 @@ class ApiClient:
459
466
  )
460
467
 
461
468
  session_response = self._request(http_request, stream=True)
462
- if http_options and 'deprecated_response_payload' in http_options:
463
- session_response._copy_to_dict(
464
- http_options['deprecated_response_payload']
465
- )
466
469
  for chunk in session_response.segments():
467
470
  yield chunk
468
471
 
@@ -478,9 +481,11 @@ class ApiClient:
478
481
  )
479
482
 
480
483
  result = await self._async_request(http_request=http_request, stream=False)
481
- if http_options and 'deprecated_response_payload' in http_options:
482
- result._copy_to_dict(http_options['deprecated_response_payload'])
483
- return result.json
484
+ json_response = result.json
485
+ if not json_response:
486
+ base_response = BaseResponse(result.headers).dict
487
+ return base_response
488
+ return json_response
484
489
 
485
490
  async def async_request_streamed(
486
491
  self,
@@ -495,8 +500,6 @@ class ApiClient:
495
500
 
496
501
  response = await self._async_request(http_request=http_request, stream=True)
497
502
 
498
- if http_options and 'deprecated_response_payload' in http_options:
499
- response._copy_to_dict(http_options['deprecated_response_payload'])
500
503
  async def async_generator():
501
504
  async for chunk in response:
502
505
  yield chunk
@@ -17,14 +17,18 @@ import inspect
17
17
  import sys
18
18
  import types as builtin_types
19
19
  import typing
20
- from typing import Any, Callable, Literal, Union, _GenericAlias, get_args, get_origin
20
+ from typing import _GenericAlias, Any, Callable, get_args, get_origin, Literal, Union
21
+
21
22
  import pydantic
23
+
24
+ from . import _extra_utils
22
25
  from . import types
23
26
 
27
+
24
28
  if sys.version_info >= (3, 10):
25
- UnionType = builtin_types.UnionType
29
+ VersionedUnionType = builtin_types.UnionType
26
30
  else:
27
- UnionType = typing._UnionGenericAlias
31
+ VersionedUnionType = typing._UnionGenericAlias
28
32
 
29
33
  _py_builtin_type_to_schema_type = {
30
34
  str: 'STRING',
@@ -45,7 +49,8 @@ def _is_builtin_primitive_or_compound(
45
49
  def _raise_for_any_of_if_mldev(schema: types.Schema):
46
50
  if schema.any_of:
47
51
  raise ValueError(
48
- 'AnyOf is not supported in function declaration schema for Google AI.'
52
+ 'AnyOf is not supported in function declaration schema for'
53
+ ' the Gemini API.'
49
54
  )
50
55
 
51
56
 
@@ -53,15 +58,7 @@ def _raise_for_default_if_mldev(schema: types.Schema):
53
58
  if schema.default is not None:
54
59
  raise ValueError(
55
60
  'Default value is not supported in function declaration schema for'
56
- ' Google AI.'
57
- )
58
-
59
-
60
- def _raise_for_nullable_if_mldev(schema: types.Schema):
61
- if schema.nullable:
62
- raise ValueError(
63
- 'Nullable is not supported in function declaration schema for'
64
- ' Google AI.'
61
+ ' the Gemini API.'
65
62
  )
66
63
 
67
64
 
@@ -69,7 +66,6 @@ def _raise_if_schema_unsupported(client, schema: types.Schema):
69
66
  if not client.vertexai:
70
67
  _raise_for_any_of_if_mldev(schema)
71
68
  _raise_for_default_if_mldev(schema)
72
- _raise_for_nullable_if_mldev(schema)
73
69
 
74
70
 
75
71
  def _is_default_value_compatible(
@@ -82,10 +78,10 @@ def _is_default_value_compatible(
82
78
  if (
83
79
  isinstance(annotation, _GenericAlias)
84
80
  or isinstance(annotation, builtin_types.GenericAlias)
85
- or isinstance(annotation, UnionType)
81
+ or isinstance(annotation, VersionedUnionType)
86
82
  ):
87
83
  origin = get_origin(annotation)
88
- if origin in (Union, UnionType):
84
+ if origin in (Union, VersionedUnionType):
89
85
  return any(
90
86
  _is_default_value_compatible(default_value, arg)
91
87
  for arg in get_args(annotation)
@@ -141,7 +137,7 @@ def _parse_schema_from_parameter(
141
137
  _raise_if_schema_unsupported(client, schema)
142
138
  return schema
143
139
  if (
144
- isinstance(param.annotation, UnionType)
140
+ isinstance(param.annotation, VersionedUnionType)
145
141
  # only parse simple UnionType, example int | str | float | bool
146
142
  # complex UnionType will be invoked in raise branch
147
143
  and all(
@@ -229,7 +225,11 @@ def _parse_schema_from_parameter(
229
225
  schema.type = 'OBJECT'
230
226
  unique_types = set()
231
227
  for arg in args:
232
- if arg.__name__ == 'NoneType': # Optional type
228
+ # The first check is for NoneType in Python 3.9, since the __name__
229
+ # attribute is not available in Python 3.9
230
+ if type(arg) is type(None) or (
231
+ hasattr(arg, '__name__') and arg.__name__ == 'NoneType'
232
+ ): # Optional type
233
233
  schema.nullable = True
234
234
  continue
235
235
  schema_in_any_of = _parse_schema_from_parameter(
@@ -272,9 +272,8 @@ def _parse_schema_from_parameter(
272
272
  return schema
273
273
  # all other generic alias will be invoked in raise branch
274
274
  if (
275
- inspect.isclass(param.annotation)
276
275
  # for user defined class, we only support pydantic model
277
- and issubclass(param.annotation, pydantic.BaseModel)
276
+ _extra_utils.is_annotation_pydantic_model(param.annotation)
278
277
  ):
279
278
  if (
280
279
  param.default is not inspect.Parameter.empty
google/genai/_common.py CHANGED
@@ -18,6 +18,7 @@
18
18
  import base64
19
19
  import datetime
20
20
  import enum
21
+ import functools
21
22
  import typing
22
23
  from typing import Union
23
24
  import uuid
@@ -27,6 +28,7 @@ import pydantic
27
28
  from pydantic import alias_generators
28
29
 
29
30
  from . import _api_client
31
+ from . import errors
30
32
 
31
33
 
32
34
  def set_value_by_path(data, keys, value):
@@ -273,3 +275,23 @@ def encode_unserializable_types(data: dict[str, object]) -> dict[str, object]:
273
275
  else:
274
276
  processed_data[key] = value
275
277
  return processed_data
278
+
279
+
280
+ def experimental_warning(message: str):
281
+ """Experimental warning, only warns once."""
282
+ def decorator(func):
283
+ warning_done = False
284
+ @functools.wraps(func)
285
+ def wrapper(*args, **kwargs):
286
+ nonlocal warning_done
287
+ if not warning_done:
288
+ warning_done = True
289
+ warnings.warn(
290
+ message=message,
291
+ category=errors.ExperimentalWarning,
292
+ stacklevel=2,
293
+ )
294
+ return func(*args, **kwargs)
295
+ return wrapper
296
+ return decorator
297
+
@@ -108,16 +108,22 @@ def convert_number_values_for_function_call_args(
108
108
  return args
109
109
 
110
110
 
111
- def _is_annotation_pydantic_model(annotation: Any) -> bool:
112
- return inspect.isclass(annotation) and issubclass(
113
- annotation, pydantic.BaseModel
114
- )
111
+ def is_annotation_pydantic_model(annotation: Any) -> bool:
112
+ try:
113
+ return inspect.isclass(annotation) and issubclass(
114
+ annotation, pydantic.BaseModel
115
+ )
116
+ # for python 3.10 and below, inspect.isclass(annotation) has inconsistent
117
+ # results with versions above. for example, inspect.isclass(dict[str, int]) is
118
+ # True in 3.10 and below but False in 3.11 and above.
119
+ except TypeError:
120
+ return False
115
121
 
116
122
 
117
123
  def convert_if_exist_pydantic_model(
118
124
  value: Any, annotation: Any, param_name: str, func_name: str
119
125
  ) -> Any:
120
- if isinstance(value, dict) and _is_annotation_pydantic_model(annotation):
126
+ if isinstance(value, dict) and is_annotation_pydantic_model(annotation):
121
127
  try:
122
128
  return annotation(**value)
123
129
  except pydantic.ValidationError as e:
@@ -146,7 +152,7 @@ def convert_if_exist_pydantic_model(
146
152
  if (
147
153
  (get_args(arg) and get_origin(arg) is list)
148
154
  or isinstance(value, arg)
149
- or (isinstance(value, dict) and _is_annotation_pydantic_model(arg))
155
+ or (isinstance(value, dict) and is_annotation_pydantic_model(arg))
150
156
  ):
151
157
  try:
152
158
  return convert_if_exist_pydantic_model(
@@ -362,6 +362,8 @@ class ReplayApiClient(ApiClient):
362
362
  if self._should_update_replay():
363
363
  if isinstance(response_model, list):
364
364
  response_model = response_model[0]
365
+ if response_model and 'http_headers' in response_model.model_fields:
366
+ response_model.http_headers.pop('Date', None)
365
367
  interaction.response.sdk_response_segments.append(
366
368
  response_model.model_dump(exclude_none=True)
367
369
  )
@@ -34,10 +34,12 @@ import pydantic
34
34
  from . import _api_client
35
35
  from . import types
36
36
 
37
- if sys.version_info >= (3, 11):
38
- from types import UnionType
37
+ if sys.version_info >= (3, 10):
38
+ VersionedUnionType = typing.types.UnionType
39
+ _UNION_TYPES = (typing.Union, typing.types.UnionType)
39
40
  else:
40
- UnionType = typing._UnionGenericAlias
41
+ VersionedUnionType = typing._UnionGenericAlias
42
+ _UNION_TYPES = (typing.Union,)
41
43
 
42
44
 
43
45
  def _resource_name(
@@ -225,6 +227,7 @@ PartType = Union[types.Part, types.PartDict, str, 'PIL.Image.Image']
225
227
  def t_part(client: _api_client.ApiClient, part: PartType) -> types.Part:
226
228
  try:
227
229
  import PIL.Image
230
+
228
231
  PIL_Image = PIL.Image.Image
229
232
  except ImportError:
230
233
  PIL_Image = None
@@ -342,7 +345,7 @@ def handle_null_fields(schema: dict[str, Any]):
342
345
  "type": "null"
343
346
  }
344
347
  ],
345
- "default": null,
348
+ "default": None,
346
349
  "title": "Total Area Sq Mi"
347
350
  }
348
351
  }
@@ -356,16 +359,12 @@ def handle_null_fields(schema: dict[str, Any]):
356
359
  "total_area_sq_mi": {
357
360
  "type": "integer",
358
361
  "nullable": true,
359
- "default": null,
362
+ "default": None,
360
363
  "title": "Total Area Sq Mi"
361
364
  }
362
365
  }
363
366
  """
364
- if (
365
- isinstance(schema, dict)
366
- and 'type' in schema
367
- and schema['type'] == 'null'
368
- ):
367
+ if schema.get('type', None) == 'null':
369
368
  schema['nullable'] = True
370
369
  del schema['type']
371
370
  elif 'anyOf' in schema:
@@ -445,6 +444,11 @@ def process_schema(
445
444
  if client and not client.vertexai:
446
445
  schema.pop('title', None)
447
446
 
447
+ if schema.get('default') is not None:
448
+ raise ValueError(
449
+ 'Default value is not supported in the response schema for the Gemmini API.'
450
+ )
451
+
448
452
  if defs is None:
449
453
  defs = schema.pop('$defs', {})
450
454
  for _, sub_schema in defs.items():
@@ -454,8 +458,19 @@ def process_schema(
454
458
 
455
459
  any_of = schema.get('anyOf', None)
456
460
  if any_of is not None:
461
+ if not client.vertexai:
462
+ raise ValueError(
463
+ 'AnyOf is not supported in the response schema for the Gemini API.'
464
+ )
457
465
  for sub_schema in any_of:
458
- process_schema(sub_schema, client, defs)
466
+ # $ref is present in any_of if the schema is a union of Pydantic classes
467
+ ref_key = sub_schema.get('$ref', None)
468
+ if ref_key is None:
469
+ process_schema(sub_schema, client, defs)
470
+ else:
471
+ ref = defs[ref_key.split('defs/')[-1]]
472
+ any_of.append(ref)
473
+ schema['anyOf'] = [item for item in any_of if '$ref' not in item]
459
474
  return
460
475
 
461
476
  schema_type = schema.get('type', None)
@@ -526,15 +541,18 @@ def t_schema(
526
541
  if (
527
542
  # in Python 3.9 Generic alias list[int] counts as a type,
528
543
  # and breaks issubclass because it's not a class.
529
- not isinstance(origin, GenericAlias) and
530
- isinstance(origin, type) and
544
+ not isinstance(origin, GenericAlias) and
545
+ isinstance(origin, type) and
531
546
  issubclass(origin, pydantic.BaseModel)
532
547
  ):
533
548
  schema = origin.model_json_schema()
534
549
  process_schema(schema, client)
535
550
  return types.Schema.model_validate(schema)
536
551
  elif (
537
- isinstance(origin, GenericAlias) or isinstance(origin, type) or isinstance(origin, UnionType)
552
+ isinstance(origin, GenericAlias)
553
+ or isinstance(origin, type)
554
+ or isinstance(origin, VersionedUnionType)
555
+ or typing.get_origin(origin) in _UNION_TYPES
538
556
  ):
539
557
  class Placeholder(pydantic.BaseModel):
540
558
  placeholder: origin
google/genai/errors.py CHANGED
@@ -128,3 +128,7 @@ class FunctionInvocationError(ValueError):
128
128
  """Raised when the function cannot be invoked with the given arguments."""
129
129
 
130
130
  pass
131
+
132
+
133
+ class ExperimentalWarning(Warning):
134
+ """Warning for experimental features."""
google/genai/files.py CHANGED
@@ -19,7 +19,7 @@ import io
19
19
  import mimetypes
20
20
  import os
21
21
  import pathlib
22
- from typing import Optional, Union
22
+ from typing import Any, Optional, Union
23
23
  from urllib.parse import urlencode
24
24
  from . import _api_module
25
25
  from . import _common
@@ -351,12 +351,12 @@ def _DeleteFileParameters_to_vertex(
351
351
  return to_object
352
352
 
353
353
 
354
- def _FileState_to_vertex_enum_validate(enum_value: any):
354
+ def _FileState_to_vertex_enum_validate(enum_value: Any):
355
355
  if enum_value in set(['STATE_UNSPECIFIED', 'PROCESSING', 'ACTIVE', 'FAILED']):
356
356
  raise ValueError(f'{enum_value} enum value is not supported in Vertex AI.')
357
357
 
358
358
 
359
- def _FileSource_to_vertex_enum_validate(enum_value: any):
359
+ def _FileSource_to_vertex_enum_validate(enum_value: Any):
360
360
  if enum_value in set(['SOURCE_UNSPECIFIED', 'UPLOADED', 'GENERATED']):
361
361
  raise ValueError(f'{enum_value} enum value is not supported in Vertex AI.')
362
362
 
@@ -494,6 +494,8 @@ def _CreateFileResponse_from_mldev(
494
494
  parent_object: dict = None,
495
495
  ) -> dict:
496
496
  to_object = {}
497
+ if getv(from_object, ['httpHeaders']) is not None:
498
+ setv(to_object, ['http_headers'], getv(from_object, ['httpHeaders']))
497
499
 
498
500
  return to_object
499
501
 
@@ -504,6 +506,8 @@ def _CreateFileResponse_from_vertex(
504
506
  parent_object: dict = None,
505
507
  ) -> dict:
506
508
  to_object = {}
509
+ if getv(from_object, ['httpHeaders']) is not None:
510
+ setv(to_object, ['http_headers'], getv(from_object, ['httpHeaders']))
507
511
 
508
512
  return to_object
509
513
 
@@ -840,7 +844,7 @@ class Files(_api_module.BaseModule):
840
844
  'Unknown mime type: Could not determine the mimetype for your'
841
845
  ' file\n please set the `mime_type` argument'
842
846
  )
843
- response = {}
847
+
844
848
  if config_model and config_model.http_options:
845
849
  http_options = config_model.http_options
846
850
  else:
@@ -853,19 +857,20 @@ class Files(_api_module.BaseModule):
853
857
  'X-Goog-Upload-Header-Content-Length': f'{file_obj.size_bytes}',
854
858
  'X-Goog-Upload-Header-Content-Type': f'{file_obj.mime_type}',
855
859
  },
856
- 'deprecated_response_payload': response,
857
860
  }
858
- self._create(file=file_obj, config={'http_options': http_options})
861
+ response = self._create(
862
+ file=file_obj, config={'http_options': http_options}
863
+ )
859
864
 
860
865
  if (
861
- 'headers' not in response
862
- or 'X-Goog-Upload-URL' not in response['headers']
866
+ response.http_headers is None
867
+ or 'X-Goog-Upload-URL' not in response.http_headers
863
868
  ):
864
869
  raise KeyError(
865
870
  'Failed to create file. Upload URL did not returned from the create'
866
871
  ' file request.'
867
872
  )
868
- upload_url = response['headers']['X-Goog-Upload-URL']
873
+ upload_url = response.http_headers['X-Goog-Upload-URL']
869
874
 
870
875
  if isinstance(file, io.IOBase):
871
876
  return_file = self._api_client.upload_file(
@@ -1272,7 +1277,6 @@ class AsyncFiles(_api_module.BaseModule):
1272
1277
  ' file\n please set the `mime_type` argument'
1273
1278
  )
1274
1279
 
1275
- response = {}
1276
1280
  if config_model and config_model.http_options:
1277
1281
  http_options = config_model.http_options
1278
1282
  else:
@@ -1285,18 +1289,20 @@ class AsyncFiles(_api_module.BaseModule):
1285
1289
  'X-Goog-Upload-Header-Content-Length': f'{file_obj.size_bytes}',
1286
1290
  'X-Goog-Upload-Header-Content-Type': f'{file_obj.mime_type}',
1287
1291
  },
1288
- 'deprecated_response_payload': response,
1289
1292
  }
1290
- await self._create(file=file_obj, config={'http_options': http_options})
1293
+ response = await self._create(
1294
+ file=file_obj, config={'http_options': http_options}
1295
+ )
1296
+
1291
1297
  if (
1292
- 'headers' not in response
1293
- or 'X-Goog-Upload-URL' not in response['headers']
1298
+ response.http_headers is None
1299
+ or 'X-Goog-Upload-URL' not in response.http_headers
1294
1300
  ):
1295
1301
  raise KeyError(
1296
1302
  'Failed to create file. Upload URL did not returned from the create'
1297
1303
  ' file request.'
1298
1304
  )
1299
- upload_url = response['headers']['X-Goog-Upload-URL']
1305
+ upload_url = response.http_headers['X-Goog-Upload-URL']
1300
1306
 
1301
1307
  if isinstance(file, io.IOBase):
1302
1308
  return_file = await self._api_client.async_upload_file(
google/genai/live.py CHANGED
@@ -29,8 +29,10 @@ from . import _api_module
29
29
  from . import _common
30
30
  from . import _transformers as t
31
31
  from . import client
32
+ from . import errors
32
33
  from . import types
33
34
  from ._api_client import ApiClient
35
+ from ._common import experimental_warning
34
36
  from ._common import get_value_by_path as getv
35
37
  from ._common import set_value_by_path as setv
36
38
  from .models import _Content_from_mldev
@@ -633,6 +635,9 @@ class AsyncLive(_api_module.BaseModule):
633
635
  return_value['setup'].update(to_object)
634
636
  return return_value
635
637
 
638
+ @experimental_warning(
639
+ "The live API is experimental and may change in future versions.",
640
+ )
636
641
  @contextlib.asynccontextmanager
637
642
  async def connect(
638
643
  self,