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.
- google/genai/_api_client.py +19 -10
- google/genai/_interactions/__init__.py +3 -0
- google/genai/_interactions/_base_client.py +134 -11
- google/genai/_interactions/_client.py +57 -3
- google/genai/_interactions/_client_adapter.py +48 -0
- google/genai/_interactions/_models.py +16 -1
- google/genai/_interactions/_types.py +9 -0
- google/genai/_interactions/types/__init__.py +4 -0
- google/genai/_interactions/types/audio_content.py +2 -0
- google/genai/_interactions/types/audio_content_param.py +2 -0
- google/genai/_interactions/types/content.py +2 -0
- google/genai/_interactions/types/content_delta.py +10 -2
- google/genai/_interactions/types/content_param.py +2 -0
- google/genai/_interactions/types/content_start.py +1 -2
- google/genai/_interactions/types/content_stop.py +1 -2
- google/genai/_interactions/types/document_content.py +2 -0
- google/genai/_interactions/types/document_content_param.py +2 -0
- google/genai/_interactions/types/error_event.py +1 -2
- google/genai/_interactions/types/file_search_call_content.py +32 -0
- google/genai/_interactions/types/file_search_call_content_param.py +31 -0
- google/genai/_interactions/types/generation_config.py +4 -0
- google/genai/_interactions/types/generation_config_param.py +4 -0
- google/genai/_interactions/types/image_config.py +31 -0
- google/genai/_interactions/types/image_config_param.py +30 -0
- google/genai/_interactions/types/image_content.py +2 -0
- google/genai/_interactions/types/image_content_param.py +2 -0
- google/genai/_interactions/types/interaction.py +2 -0
- google/genai/_interactions/types/interaction_create_params.py +2 -0
- google/genai/_interactions/types/interaction_event.py +1 -2
- google/genai/_interactions/types/interaction_sse_event.py +5 -3
- google/genai/_interactions/types/interaction_status_update.py +1 -2
- google/genai/_interactions/types/video_content.py +2 -0
- google/genai/_interactions/types/video_content_param.py +2 -0
- google/genai/_live_converters.py +84 -0
- google/genai/_transformers.py +15 -21
- google/genai/client.py +61 -55
- google/genai/live.py +1 -1
- google/genai/tests/client/test_custom_client.py +27 -0
- google/genai/tests/client/test_http_options.py +1 -1
- google/genai/tests/interactions/test_auth.py +1 -4
- google/genai/tests/interactions/test_integration.py +2 -0
- google/genai/tests/transformers/test_blobs.py +16 -3
- google/genai/types.py +58 -2
- google/genai/version.py +1 -1
- {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/METADATA +6 -11
- {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/RECORD +49 -44
- {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/WHEEL +0 -0
- {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/licenses/LICENSE +0 -0
- {google_genai-1.57.0.dist-info → google_genai-1.59.0.dist-info}/top_level.txt +0 -0
google/genai/_api_client.py
CHANGED
|
@@ -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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
|
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,
|