google-genai 1.57.0__py3-none-any.whl → 1.59.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.
Files changed (49) hide show
  1. google/genai/_api_client.py +19 -10
  2. google/genai/_interactions/__init__.py +3 -0
  3. google/genai/_interactions/_base_client.py +134 -11
  4. google/genai/_interactions/_client.py +57 -3
  5. google/genai/_interactions/_client_adapter.py +48 -0
  6. google/genai/_interactions/_models.py +16 -1
  7. google/genai/_interactions/_types.py +9 -0
  8. google/genai/_interactions/types/__init__.py +4 -0
  9. google/genai/_interactions/types/audio_content.py +2 -0
  10. google/genai/_interactions/types/audio_content_param.py +2 -0
  11. google/genai/_interactions/types/content.py +2 -0
  12. google/genai/_interactions/types/content_delta.py +10 -2
  13. google/genai/_interactions/types/content_param.py +2 -0
  14. google/genai/_interactions/types/content_start.py +1 -2
  15. google/genai/_interactions/types/content_stop.py +1 -2
  16. google/genai/_interactions/types/document_content.py +2 -0
  17. google/genai/_interactions/types/document_content_param.py +2 -0
  18. google/genai/_interactions/types/error_event.py +1 -2
  19. google/genai/_interactions/types/file_search_call_content.py +32 -0
  20. google/genai/_interactions/types/file_search_call_content_param.py +31 -0
  21. google/genai/_interactions/types/generation_config.py +4 -0
  22. google/genai/_interactions/types/generation_config_param.py +4 -0
  23. google/genai/_interactions/types/image_config.py +31 -0
  24. google/genai/_interactions/types/image_config_param.py +30 -0
  25. google/genai/_interactions/types/image_content.py +2 -0
  26. google/genai/_interactions/types/image_content_param.py +2 -0
  27. google/genai/_interactions/types/interaction.py +2 -0
  28. google/genai/_interactions/types/interaction_create_params.py +2 -0
  29. google/genai/_interactions/types/interaction_event.py +1 -2
  30. google/genai/_interactions/types/interaction_sse_event.py +5 -3
  31. google/genai/_interactions/types/interaction_status_update.py +1 -2
  32. google/genai/_interactions/types/video_content.py +2 -0
  33. google/genai/_interactions/types/video_content_param.py +2 -0
  34. google/genai/_live_converters.py +84 -0
  35. google/genai/_transformers.py +15 -21
  36. google/genai/client.py +61 -55
  37. google/genai/live.py +1 -1
  38. google/genai/tests/client/test_custom_client.py +27 -0
  39. google/genai/tests/client/test_http_options.py +1 -1
  40. google/genai/tests/interactions/test_auth.py +1 -4
  41. google/genai/tests/interactions/test_integration.py +2 -0
  42. google/genai/tests/transformers/test_blobs.py +16 -3
  43. google/genai/types.py +58 -2
  44. google/genai/version.py +1 -1
  45. {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/METADATA +6 -11
  46. {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/RECORD +49 -44
  47. {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/WHEEL +0 -0
  48. {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/licenses/LICENSE +0 -0
  49. {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/top_level.txt +0 -0
@@ -181,6 +181,13 @@ def join_url_path(base_url: str, path: str) -> str:
181
181
 
182
182
  def load_auth(*, project: Union[str, None]) -> Tuple[Credentials, str]:
183
183
  """Loads google auth credentials and project id."""
184
+
185
+ ## Set GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES to false
186
+ ## to disable bound token sharing. Tracking on
187
+ ## https://github.com/googleapis/python-genai/issues/1956
188
+ os.environ['GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES'] = (
189
+ 'false'
190
+ )
184
191
  credentials, loaded_project_id = google.auth.default( # type: ignore[no-untyped-call]
185
192
  scopes=['https://www.googleapis.com/auth/cloud-platform'],
186
193
  )
@@ -715,19 +722,23 @@ class BaseApiClient:
715
722
  self._async_httpx_client = self._http_options.httpx_async_client
716
723
  else:
717
724
  self._async_httpx_client = AsyncHttpxClient(**async_client_args)
725
+
726
+ # Initialize the aiohttp client session.
727
+ self._aiohttp_session: Optional[aiohttp.ClientSession] = None
718
728
  if self._use_aiohttp():
719
729
  try:
720
730
  import aiohttp # pylint: disable=g-import-not-at-top
721
- # Do it once at the genai.Client level. Share among all requests.
722
- self._async_client_session_request_args = self._ensure_aiohttp_ssl_ctx(
723
- self._http_options
724
- )
731
+
732
+ if self._http_options.aiohttp_client:
733
+ self._aiohttp_session = self._http_options.aiohttp_client
734
+ else:
735
+ # Do it once at the genai.Client level. Share among all requests.
736
+ self._async_client_session_request_args = (
737
+ self._ensure_aiohttp_ssl_ctx(self._http_options)
738
+ )
725
739
  except ImportError:
726
740
  pass
727
741
 
728
- # Initialize the aiohttp client session.
729
- self._aiohttp_session: Optional[aiohttp.ClientSession] = None
730
-
731
742
  retry_kwargs = retry_args(self._http_options.retry_options)
732
743
  self._websocket_ssl_ctx = self._ensure_websocket_ssl_ctx(self._http_options)
733
744
  self._retry = tenacity.Retrying(**retry_kwargs)
@@ -1892,7 +1903,7 @@ class BaseApiClient:
1892
1903
  # close the client when the object is garbage collected.
1893
1904
  if not self._http_options.httpx_async_client:
1894
1905
  await self._async_httpx_client.aclose()
1895
- if self._aiohttp_session:
1906
+ if self._aiohttp_session and not self._http_options.aiohttp_client:
1896
1907
  await self._aiohttp_session.close()
1897
1908
 
1898
1909
  def __del__(self) -> None:
@@ -1942,5 +1953,3 @@ async def async_get_token_from_credentials(
1942
1953
  raise RuntimeError('Could not resolve API token from the environment')
1943
1954
 
1944
1955
  return credentials.token # type: ignore[no-any-return]
1945
-
1946
-
@@ -53,6 +53,7 @@ from ._exceptions import (
53
53
  )
54
54
  from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
55
55
  from ._utils._logs import setup_logging as _setup_logging
56
+ from ._client_adapter import GeminiNextGenAPIClientAdapter, AsyncGeminiNextGenAPIClientAdapter
56
57
 
57
58
  __all__ = [
58
59
  "types",
@@ -96,6 +97,8 @@ __all__ = [
96
97
  "DefaultHttpxClient",
97
98
  "DefaultAsyncHttpxClient",
98
99
  "DefaultAioHttpClient",
100
+ "AsyncGeminiNextGenAPIClientAdapter",
101
+ "GeminiNextGenAPIClientAdapter"
99
102
  ]
100
103
 
101
104
  if not _t.TYPE_CHECKING:
@@ -24,6 +24,7 @@ import asyncio
24
24
  import inspect
25
25
  import logging
26
26
  import platform
27
+ import warnings
27
28
  import email.utils
28
29
  from types import TracebackType
29
30
  from random import random
@@ -66,9 +67,11 @@ from ._types import (
66
67
  ResponseT,
67
68
  AnyMapping,
68
69
  PostParser,
70
+ BinaryTypes,
69
71
  RequestFiles,
70
72
  HttpxSendArgs,
71
73
  RequestOptions,
74
+ AsyncBinaryTypes,
72
75
  HttpxRequestFiles,
73
76
  ModelBuilderProtocol,
74
77
  not_given,
@@ -501,8 +504,19 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
501
504
  retries_taken: int = 0,
502
505
  ) -> httpx.Request:
503
506
  if log.isEnabledFor(logging.DEBUG):
504
- log.debug("Request options: %s", model_dump(options, exclude_unset=True))
505
-
507
+ log.debug(
508
+ "Request options: %s",
509
+ model_dump(
510
+ options,
511
+ exclude_unset=True,
512
+ # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
513
+ exclude={
514
+ "content",
515
+ }
516
+ if PYDANTIC_V1
517
+ else {},
518
+ ),
519
+ )
506
520
  kwargs: dict[str, Any] = {}
507
521
 
508
522
  json_data = options.json_data
@@ -556,7 +570,13 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
556
570
  is_body_allowed = options.method.lower() != "get"
557
571
 
558
572
  if is_body_allowed:
559
- if isinstance(json_data, bytes):
573
+ if options.content is not None and json_data is not None:
574
+ raise TypeError("Passing both `content` and `json_data` is not supported")
575
+ if options.content is not None and files is not None:
576
+ raise TypeError("Passing both `content` and `files` is not supported")
577
+ if options.content is not None:
578
+ kwargs["content"] = options.content
579
+ elif isinstance(json_data, bytes):
560
580
  kwargs["content"] = json_data
561
581
  else:
562
582
  kwargs["json"] = json_data if is_given(json_data) else None
@@ -1218,6 +1238,7 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1218
1238
  *,
1219
1239
  cast_to: Type[ResponseT],
1220
1240
  body: Body | None = None,
1241
+ content: BinaryTypes | None = None,
1221
1242
  options: RequestOptions = {},
1222
1243
  files: RequestFiles | None = None,
1223
1244
  stream: Literal[False] = False,
@@ -1230,6 +1251,7 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1230
1251
  *,
1231
1252
  cast_to: Type[ResponseT],
1232
1253
  body: Body | None = None,
1254
+ content: BinaryTypes | None = None,
1233
1255
  options: RequestOptions = {},
1234
1256
  files: RequestFiles | None = None,
1235
1257
  stream: Literal[True],
@@ -1243,6 +1265,7 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1243
1265
  *,
1244
1266
  cast_to: Type[ResponseT],
1245
1267
  body: Body | None = None,
1268
+ content: BinaryTypes | None = None,
1246
1269
  options: RequestOptions = {},
1247
1270
  files: RequestFiles | None = None,
1248
1271
  stream: bool,
@@ -1255,13 +1278,25 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1255
1278
  *,
1256
1279
  cast_to: Type[ResponseT],
1257
1280
  body: Body | None = None,
1281
+ content: BinaryTypes | None = None,
1258
1282
  options: RequestOptions = {},
1259
1283
  files: RequestFiles | None = None,
1260
1284
  stream: bool = False,
1261
1285
  stream_cls: type[_StreamT] | None = None,
1262
1286
  ) -> ResponseT | _StreamT:
1287
+ if body is not None and content is not None:
1288
+ raise TypeError("Passing both `body` and `content` is not supported")
1289
+ if files is not None and content is not None:
1290
+ raise TypeError("Passing both `files` and `content` is not supported")
1291
+ if isinstance(body, bytes):
1292
+ warnings.warn(
1293
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1294
+ "Please pass raw bytes via the `content` parameter instead.",
1295
+ DeprecationWarning,
1296
+ stacklevel=2,
1297
+ )
1263
1298
  opts = FinalRequestOptions.construct(
1264
- method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1299
+ method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
1265
1300
  )
1266
1301
  return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
1267
1302
 
@@ -1271,11 +1306,23 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1271
1306
  *,
1272
1307
  cast_to: Type[ResponseT],
1273
1308
  body: Body | None = None,
1309
+ content: BinaryTypes | None = None,
1274
1310
  files: RequestFiles | None = None,
1275
1311
  options: RequestOptions = {},
1276
1312
  ) -> ResponseT:
1313
+ if body is not None and content is not None:
1314
+ raise TypeError("Passing both `body` and `content` is not supported")
1315
+ if files is not None and content is not None:
1316
+ raise TypeError("Passing both `files` and `content` is not supported")
1317
+ if isinstance(body, bytes):
1318
+ warnings.warn(
1319
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1320
+ "Please pass raw bytes via the `content` parameter instead.",
1321
+ DeprecationWarning,
1322
+ stacklevel=2,
1323
+ )
1277
1324
  opts = FinalRequestOptions.construct(
1278
- method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
1325
+ method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
1279
1326
  )
1280
1327
  return self.request(cast_to, opts)
1281
1328
 
@@ -1285,11 +1332,23 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1285
1332
  *,
1286
1333
  cast_to: Type[ResponseT],
1287
1334
  body: Body | None = None,
1335
+ content: BinaryTypes | None = None,
1288
1336
  files: RequestFiles | None = None,
1289
1337
  options: RequestOptions = {},
1290
1338
  ) -> ResponseT:
1339
+ if body is not None and content is not None:
1340
+ raise TypeError("Passing both `body` and `content` is not supported")
1341
+ if files is not None and content is not None:
1342
+ raise TypeError("Passing both `files` and `content` is not supported")
1343
+ if isinstance(body, bytes):
1344
+ warnings.warn(
1345
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1346
+ "Please pass raw bytes via the `content` parameter instead.",
1347
+ DeprecationWarning,
1348
+ stacklevel=2,
1349
+ )
1291
1350
  opts = FinalRequestOptions.construct(
1292
- method="put", url=path, json_data=body, files=to_httpx_files(files), **options
1351
+ method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
1293
1352
  )
1294
1353
  return self.request(cast_to, opts)
1295
1354
 
@@ -1299,9 +1358,19 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1299
1358
  *,
1300
1359
  cast_to: Type[ResponseT],
1301
1360
  body: Body | None = None,
1361
+ content: BinaryTypes | None = None,
1302
1362
  options: RequestOptions = {},
1303
1363
  ) -> ResponseT:
1304
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
1364
+ if body is not None and content is not None:
1365
+ raise TypeError("Passing both `body` and `content` is not supported")
1366
+ if isinstance(body, bytes):
1367
+ warnings.warn(
1368
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1369
+ "Please pass raw bytes via the `content` parameter instead.",
1370
+ DeprecationWarning,
1371
+ stacklevel=2,
1372
+ )
1373
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
1305
1374
  return self.request(cast_to, opts)
1306
1375
 
1307
1376
  def get_api_list(
@@ -1741,6 +1810,7 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1741
1810
  *,
1742
1811
  cast_to: Type[ResponseT],
1743
1812
  body: Body | None = None,
1813
+ content: AsyncBinaryTypes | None = None,
1744
1814
  files: RequestFiles | None = None,
1745
1815
  options: RequestOptions = {},
1746
1816
  stream: Literal[False] = False,
@@ -1753,6 +1823,7 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1753
1823
  *,
1754
1824
  cast_to: Type[ResponseT],
1755
1825
  body: Body | None = None,
1826
+ content: AsyncBinaryTypes | None = None,
1756
1827
  files: RequestFiles | None = None,
1757
1828
  options: RequestOptions = {},
1758
1829
  stream: Literal[True],
@@ -1766,6 +1837,7 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1766
1837
  *,
1767
1838
  cast_to: Type[ResponseT],
1768
1839
  body: Body | None = None,
1840
+ content: AsyncBinaryTypes | None = None,
1769
1841
  files: RequestFiles | None = None,
1770
1842
  options: RequestOptions = {},
1771
1843
  stream: bool,
@@ -1778,13 +1850,25 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1778
1850
  *,
1779
1851
  cast_to: Type[ResponseT],
1780
1852
  body: Body | None = None,
1853
+ content: AsyncBinaryTypes | None = None,
1781
1854
  files: RequestFiles | None = None,
1782
1855
  options: RequestOptions = {},
1783
1856
  stream: bool = False,
1784
1857
  stream_cls: type[_AsyncStreamT] | None = None,
1785
1858
  ) -> ResponseT | _AsyncStreamT:
1859
+ if body is not None and content is not None:
1860
+ raise TypeError("Passing both `body` and `content` is not supported")
1861
+ if files is not None and content is not None:
1862
+ raise TypeError("Passing both `files` and `content` is not supported")
1863
+ if isinstance(body, bytes):
1864
+ warnings.warn(
1865
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1866
+ "Please pass raw bytes via the `content` parameter instead.",
1867
+ DeprecationWarning,
1868
+ stacklevel=2,
1869
+ )
1786
1870
  opts = FinalRequestOptions.construct(
1787
- method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1871
+ method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
1788
1872
  )
1789
1873
  return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
1790
1874
 
@@ -1794,11 +1878,28 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1794
1878
  *,
1795
1879
  cast_to: Type[ResponseT],
1796
1880
  body: Body | None = None,
1881
+ content: AsyncBinaryTypes | None = None,
1797
1882
  files: RequestFiles | None = None,
1798
1883
  options: RequestOptions = {},
1799
1884
  ) -> ResponseT:
1885
+ if body is not None and content is not None:
1886
+ raise TypeError("Passing both `body` and `content` is not supported")
1887
+ if files is not None and content is not None:
1888
+ raise TypeError("Passing both `files` and `content` is not supported")
1889
+ if isinstance(body, bytes):
1890
+ warnings.warn(
1891
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1892
+ "Please pass raw bytes via the `content` parameter instead.",
1893
+ DeprecationWarning,
1894
+ stacklevel=2,
1895
+ )
1800
1896
  opts = FinalRequestOptions.construct(
1801
- method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1897
+ method="patch",
1898
+ url=path,
1899
+ json_data=body,
1900
+ content=content,
1901
+ files=await async_to_httpx_files(files),
1902
+ **options,
1802
1903
  )
1803
1904
  return await self.request(cast_to, opts)
1804
1905
 
@@ -1808,11 +1909,23 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1808
1909
  *,
1809
1910
  cast_to: Type[ResponseT],
1810
1911
  body: Body | None = None,
1912
+ content: AsyncBinaryTypes | None = None,
1811
1913
  files: RequestFiles | None = None,
1812
1914
  options: RequestOptions = {},
1813
1915
  ) -> ResponseT:
1916
+ if body is not None and content is not None:
1917
+ raise TypeError("Passing both `body` and `content` is not supported")
1918
+ if files is not None and content is not None:
1919
+ raise TypeError("Passing both `files` and `content` is not supported")
1920
+ if isinstance(body, bytes):
1921
+ warnings.warn(
1922
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1923
+ "Please pass raw bytes via the `content` parameter instead.",
1924
+ DeprecationWarning,
1925
+ stacklevel=2,
1926
+ )
1814
1927
  opts = FinalRequestOptions.construct(
1815
- method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1928
+ method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
1816
1929
  )
1817
1930
  return await self.request(cast_to, opts)
1818
1931
 
@@ -1822,9 +1935,19 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1822
1935
  *,
1823
1936
  cast_to: Type[ResponseT],
1824
1937
  body: Body | None = None,
1938
+ content: AsyncBinaryTypes | None = None,
1825
1939
  options: RequestOptions = {},
1826
1940
  ) -> ResponseT:
1827
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
1941
+ if body is not None and content is not None:
1942
+ raise TypeError("Passing both `body` and `content` is not supported")
1943
+ if isinstance(body, bytes):
1944
+ warnings.warn(
1945
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1946
+ "Please pass raw bytes via the `content` parameter instead.",
1947
+ DeprecationWarning,
1948
+ stacklevel=2,
1949
+ )
1950
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
1828
1951
  return await self.request(cast_to, opts)
1829
1952
 
1830
1953
  def get_api_list(
@@ -37,6 +37,7 @@ from ._types import (
37
37
  )
38
38
  from ._utils import is_given, get_async_library
39
39
  from ._compat import cached_property
40
+ from ._models import FinalRequestOptions
40
41
  from ._version import __version__
41
42
  from ._streaming import Stream as Stream, AsyncStream as AsyncStream
42
43
  from ._exceptions import APIStatusError
@@ -45,6 +46,7 @@ from ._base_client import (
45
46
  SyncAPIClient,
46
47
  AsyncAPIClient,
47
48
  )
49
+ from ._client_adapter import GeminiNextGenAPIClientAdapter, AsyncGeminiNextGenAPIClientAdapter
48
50
 
49
51
  if TYPE_CHECKING:
50
52
  from .resources import interactions
@@ -66,6 +68,7 @@ class GeminiNextGenAPIClient(SyncAPIClient):
66
68
  # client options
67
69
  api_key: str | None
68
70
  api_version: str
71
+ client_adapter: GeminiNextGenAPIClientAdapter | None
69
72
 
70
73
  def __init__(
71
74
  self,
@@ -81,6 +84,7 @@ class GeminiNextGenAPIClient(SyncAPIClient):
81
84
  # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
82
85
  # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
83
86
  http_client: httpx.Client | None = None,
87
+ client_adapter: GeminiNextGenAPIClientAdapter | None = None,
84
88
  # Enable or disable schema validation for data returned by the API.
85
89
  # When enabled an error APIResponseValidationError is raised
86
90
  # if the API responds with invalid data for the expected schema.
@@ -108,6 +112,8 @@ class GeminiNextGenAPIClient(SyncAPIClient):
108
112
  if base_url is None:
109
113
  base_url = f"https://generativelanguage.googleapis.com"
110
114
 
115
+ self.client_adapter = client_adapter
116
+
111
117
  super().__init__(
112
118
  version=__version__,
113
119
  base_url=base_url,
@@ -159,15 +165,35 @@ class GeminiNextGenAPIClient(SyncAPIClient):
159
165
 
160
166
  @override
161
167
  def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
168
+ if headers.get("Authorization") or custom_headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
169
+ return
162
170
  if self.api_key and headers.get("x-goog-api-key"):
163
171
  return
164
- if isinstance(custom_headers.get("x-goog-api-key"), Omit):
172
+ if custom_headers.get("x-goog-api-key") or isinstance(custom_headers.get("x-goog-api-key"), Omit):
165
173
  return
166
174
 
167
175
  raise TypeError(
168
176
  '"Could not resolve authentication method. Expected the api_key to be set. Or for the `x-goog-api-key` headers to be explicitly omitted"'
169
177
  )
170
-
178
+
179
+ @override
180
+ def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
181
+ if not self.client_adapter or not self.client_adapter.is_vertex_ai():
182
+ return options
183
+
184
+ headers = options.headers or {}
185
+ has_auth = headers.get("Authorization") or headers.get("x-goog-api-key") # pytype: disable=attribute-error
186
+ if has_auth:
187
+ return options
188
+
189
+ adapted_headers = self.client_adapter.get_auth_headers()
190
+ if adapted_headers:
191
+ options.headers = {
192
+ **adapted_headers,
193
+ **headers
194
+ }
195
+ return options
196
+
171
197
  def copy(
172
198
  self,
173
199
  *,
@@ -181,6 +207,7 @@ class GeminiNextGenAPIClient(SyncAPIClient):
181
207
  set_default_headers: Mapping[str, str] | None = None,
182
208
  default_query: Mapping[str, object] | None = None,
183
209
  set_default_query: Mapping[str, object] | None = None,
210
+ client_adapter: GeminiNextGenAPIClientAdapter | None = None,
184
211
  _extra_kwargs: Mapping[str, Any] = {},
185
212
  ) -> Self:
186
213
  """
@@ -214,6 +241,7 @@ class GeminiNextGenAPIClient(SyncAPIClient):
214
241
  max_retries=max_retries if is_given(max_retries) else self.max_retries,
215
242
  default_headers=headers,
216
243
  default_query=params,
244
+ client_adapter=self.client_adapter or client_adapter,
217
245
  **_extra_kwargs,
218
246
  )
219
247
 
@@ -262,6 +290,7 @@ class AsyncGeminiNextGenAPIClient(AsyncAPIClient):
262
290
  # client options
263
291
  api_key: str | None
264
292
  api_version: str
293
+ client_adapter: AsyncGeminiNextGenAPIClientAdapter | None
265
294
 
266
295
  def __init__(
267
296
  self,
@@ -277,6 +306,7 @@ class AsyncGeminiNextGenAPIClient(AsyncAPIClient):
277
306
  # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
278
307
  # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
279
308
  http_client: httpx.AsyncClient | None = None,
309
+ client_adapter: AsyncGeminiNextGenAPIClientAdapter | None = None,
280
310
  # Enable or disable schema validation for data returned by the API.
281
311
  # When enabled an error APIResponseValidationError is raised
282
312
  # if the API responds with invalid data for the expected schema.
@@ -304,6 +334,8 @@ class AsyncGeminiNextGenAPIClient(AsyncAPIClient):
304
334
  if base_url is None:
305
335
  base_url = f"https://generativelanguage.googleapis.com"
306
336
 
337
+ self.client_adapter = client_adapter
338
+
307
339
  super().__init__(
308
340
  version=__version__,
309
341
  base_url=base_url,
@@ -355,14 +387,34 @@ class AsyncGeminiNextGenAPIClient(AsyncAPIClient):
355
387
 
356
388
  @override
357
389
  def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
390
+ if headers.get("Authorization") or custom_headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
391
+ return
358
392
  if self.api_key and headers.get("x-goog-api-key"):
359
393
  return
360
- if isinstance(custom_headers.get("x-goog-api-key"), Omit):
394
+ if custom_headers.get("x-goog-api-key") or isinstance(custom_headers.get("x-goog-api-key"), Omit):
361
395
  return
362
396
 
363
397
  raise TypeError(
364
398
  '"Could not resolve authentication method. Expected the api_key to be set. Or for the `x-goog-api-key` headers to be explicitly omitted"'
365
399
  )
400
+
401
+ @override
402
+ async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
403
+ if not self.client_adapter or not self.client_adapter.is_vertex_ai():
404
+ return options
405
+
406
+ headers = options.headers or {}
407
+ has_auth = headers.get("Authorization") or headers.get("x-goog-api-key") # pytype: disable=attribute-error
408
+ if has_auth:
409
+ return options
410
+
411
+ adapted_headers = await self.client_adapter.async_get_auth_headers()
412
+ if adapted_headers:
413
+ options.headers = {
414
+ **adapted_headers,
415
+ **headers
416
+ }
417
+ return options
366
418
 
367
419
  def copy(
368
420
  self,
@@ -377,6 +429,7 @@ class AsyncGeminiNextGenAPIClient(AsyncAPIClient):
377
429
  set_default_headers: Mapping[str, str] | None = None,
378
430
  default_query: Mapping[str, object] | None = None,
379
431
  set_default_query: Mapping[str, object] | None = None,
432
+ client_adapter: AsyncGeminiNextGenAPIClientAdapter | None = None,
380
433
  _extra_kwargs: Mapping[str, Any] = {},
381
434
  ) -> Self:
382
435
  """
@@ -410,6 +463,7 @@ class AsyncGeminiNextGenAPIClient(AsyncAPIClient):
410
463
  max_retries=max_retries if is_given(max_retries) else self.max_retries,
411
464
  default_headers=headers,
412
465
  default_query=params,
466
+ client_adapter=self.client_adapter or client_adapter,
413
467
  **_extra_kwargs,
414
468
  )
415
469
 
@@ -0,0 +1,48 @@
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
+
16
+ from __future__ import annotations
17
+
18
+ from abc import ABC, abstractmethod
19
+
20
+ __all__ = [
21
+ "GeminiNextGenAPIClientAdapter",
22
+ "AsyncGeminiNextGenAPIClientAdapter"
23
+ ]
24
+
25
+ class BaseGeminiNextGenAPIClientAdapter(ABC):
26
+ @abstractmethod
27
+ def is_vertex_ai(self) -> bool:
28
+ ...
29
+
30
+ @abstractmethod
31
+ def get_project(self) -> str | None:
32
+ ...
33
+
34
+ @abstractmethod
35
+ def get_location(self) -> str | None:
36
+ ...
37
+
38
+
39
+ class AsyncGeminiNextGenAPIClientAdapter(BaseGeminiNextGenAPIClientAdapter):
40
+ @abstractmethod
41
+ async def async_get_auth_headers(self) -> dict[str, str] | None:
42
+ ...
43
+
44
+
45
+ class GeminiNextGenAPIClientAdapter(BaseGeminiNextGenAPIClientAdapter):
46
+ @abstractmethod
47
+ def get_auth_headers(self) -> dict[str, str] | None:
48
+ ...
@@ -19,7 +19,20 @@ from __future__ import annotations
19
19
  import os
20
20
  import inspect
21
21
  import weakref
22
- from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
22
+ from typing import (
23
+ IO,
24
+ TYPE_CHECKING,
25
+ Any,
26
+ Type,
27
+ Union,
28
+ Generic,
29
+ TypeVar,
30
+ Callable,
31
+ Iterable,
32
+ Optional,
33
+ AsyncIterable,
34
+ cast,
35
+ )
23
36
  from datetime import date, datetime
24
37
  from typing_extensions import (
25
38
  List,
@@ -803,6 +816,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
803
816
  timeout: float | Timeout | None
804
817
  files: HttpxRequestFiles | None
805
818
  idempotency_key: str
819
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
806
820
  json_data: Body
807
821
  extra_json: AnyMapping
808
822
  follow_redirects: bool
@@ -821,6 +835,7 @@ class FinalRequestOptions(pydantic.BaseModel):
821
835
  post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
822
836
  follow_redirects: Union[bool, None] = None
823
837
 
838
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
824
839
  # It should be noted that we cannot use `json` here as that would override
825
840
  # a BaseModel method in an incompatible fashion.
826
841
  json_data: Union[Body, None] = None
@@ -28,9 +28,11 @@ from typing import (
28
28
  Mapping,
29
29
  TypeVar,
30
30
  Callable,
31
+ Iterable,
31
32
  Iterator,
32
33
  Optional,
33
34
  Sequence,
35
+ AsyncIterable,
34
36
  )
35
37
  from typing_extensions import (
36
38
  Set,
@@ -71,6 +73,13 @@ if TYPE_CHECKING:
71
73
  else:
72
74
  Base64FileInput = Union[IO[bytes], PathLike]
73
75
  FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8.
76
+
77
+
78
+ # Used for sending raw binary data / streaming data in request bodies
79
+ # e.g. for file uploads without multipart encoding
80
+ BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
81
+ AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]
82
+
74
83
  FileTypes = Union[
75
84
  # file (or bytes)
76
85
  FileContent,