google-genai 1.20.0__tar.gz → 1.21.1__tar.gz
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-1.20.0 → google_genai-1.21.1}/PKG-INFO +45 -1
- google_genai-1.20.0/google_genai.egg-info/PKG-INFO → google_genai-1.21.1/README.md +43 -33
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_api_client.py +170 -103
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_common.py +73 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_live_converters.py +174 -414
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_replay_api_client.py +9 -3
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_tokens_converters.py +81 -176
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_transformers.py +19 -40
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/batches.py +46 -64
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/caches.py +131 -222
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/chats.py +4 -4
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/client.py +1 -1
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/files.py +88 -106
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/live.py +15 -20
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/live_music.py +4 -5
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/models.py +317 -560
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/operations.py +35 -68
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/tokens.py +11 -6
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/tunings.py +64 -113
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/types.py +132 -9
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/version.py +1 -1
- google_genai-1.20.0/README.md → google_genai-1.21.1/google_genai.egg-info/PKG-INFO +77 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google_genai.egg-info/requires.txt +1 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/pyproject.toml +2 -1
- {google_genai-1.20.0 → google_genai-1.21.1}/LICENSE +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/MANIFEST.in +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/__init__.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_adapters.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_api_module.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_automatic_function_calling_util.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_base_url.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_extra_utils.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_mcp_utils.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/_test_api_client.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/errors.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/pagers.py +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google/genai/py.typed +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google_genai.egg-info/SOURCES.txt +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google_genai.egg-info/dependency_links.txt +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/google_genai.egg-info/top_level.txt +0 -0
- {google_genai-1.20.0 → google_genai-1.21.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: google-genai
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.21.1
|
4
4
|
Summary: GenAI Python SDK
|
5
5
|
Author-email: Google LLC <googleapis-packages@google.com>
|
6
6
|
License: Apache-2.0
|
@@ -25,6 +25,7 @@ Requires-Dist: google-auth<3.0.0,>=2.14.1
|
|
25
25
|
Requires-Dist: httpx<1.0.0,>=0.28.1
|
26
26
|
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
27
27
|
Requires-Dist: requests<3.0.0,>=2.28.1
|
28
|
+
Requires-Dist: tenacity<9.0.0,>=8.2.3
|
28
29
|
Requires-Dist: websockets<15.1.0,>=13.0.0
|
29
30
|
Requires-Dist: typing-extensions<5.0.0,>=4.11.0
|
30
31
|
Provides-Extra: aiohttp
|
@@ -142,6 +143,49 @@ client = genai.Client(
|
|
142
143
|
)
|
143
144
|
```
|
144
145
|
|
146
|
+
### Faster async client option: Aiohttp
|
147
|
+
|
148
|
+
By default we use httpx for both sync and async client implementations. In order
|
149
|
+
to have faster performance, you may install `google-genai[aiohttp]`. In Gen AI
|
150
|
+
SDK we configure `trust_env=True` to match with the default behavior of httpx.
|
151
|
+
Additional args of `aiohttp.ClientSession.request()` ([see _RequestOptions args](https://github.com/aio-libs/aiohttp/blob/v3.12.13/aiohttp/client.py#L170)) can be passed
|
152
|
+
through the following way:
|
153
|
+
|
154
|
+
```python
|
155
|
+
|
156
|
+
http_options = types.HttpOptions(
|
157
|
+
async_client_args={'cookies': ..., 'ssl': ...},
|
158
|
+
)
|
159
|
+
|
160
|
+
client=Client(..., http_options=http_options)
|
161
|
+
```
|
162
|
+
|
163
|
+
### Proxy
|
164
|
+
|
165
|
+
Both httpx and aiohttp libraries use `urllib.request.getproxies` from
|
166
|
+
environment variables. Before client initialization, you may set proxy (and
|
167
|
+
optional SSL_CERT_FILE) by setting the environment variables:
|
168
|
+
|
169
|
+
|
170
|
+
```bash
|
171
|
+
export HTTPS_PROXY='http://username:password@proxy_uri:port'
|
172
|
+
export SSL_CERT_FILE='client.pem'
|
173
|
+
```
|
174
|
+
|
175
|
+
If you need `socks5` proxy, httpx [supports](https://www.python-httpx.org/advanced/proxies/#socks) `socks5` proxy if you pass it via
|
176
|
+
args to `httpx.Client()`. You may install `httpx[socks]` to use it.
|
177
|
+
Then, you can pass it through the following way:
|
178
|
+
|
179
|
+
```python
|
180
|
+
|
181
|
+
http_options = types.HttpOptions(
|
182
|
+
client_args={'proxy': 'socks5://user:pass@host:port'},
|
183
|
+
async_client_args={'proxy': 'socks5://user:pass@host:port'},,
|
184
|
+
)
|
185
|
+
|
186
|
+
client=Client(..., http_options=http_options)
|
187
|
+
```
|
188
|
+
|
145
189
|
## Types
|
146
190
|
|
147
191
|
Parameter types can be specified as either dictionaries(`TypedDict`) or
|
@@ -1,36 +1,3 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: google-genai
|
3
|
-
Version: 1.20.0
|
4
|
-
Summary: GenAI Python SDK
|
5
|
-
Author-email: Google LLC <googleapis-packages@google.com>
|
6
|
-
License: Apache-2.0
|
7
|
-
Project-URL: Homepage, https://github.com/googleapis/python-genai
|
8
|
-
Classifier: Intended Audience :: Developers
|
9
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
10
|
-
Classifier: Operating System :: OS Independent
|
11
|
-
Classifier: Programming Language :: Python
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
17
|
-
Classifier: Programming Language :: Python :: 3.13
|
18
|
-
Classifier: Topic :: Internet
|
19
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
20
|
-
Requires-Python: >=3.9
|
21
|
-
Description-Content-Type: text/markdown
|
22
|
-
License-File: LICENSE
|
23
|
-
Requires-Dist: anyio<5.0.0,>=4.8.0
|
24
|
-
Requires-Dist: google-auth<3.0.0,>=2.14.1
|
25
|
-
Requires-Dist: httpx<1.0.0,>=0.28.1
|
26
|
-
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
27
|
-
Requires-Dist: requests<3.0.0,>=2.28.1
|
28
|
-
Requires-Dist: websockets<15.1.0,>=13.0.0
|
29
|
-
Requires-Dist: typing-extensions<5.0.0,>=4.11.0
|
30
|
-
Provides-Extra: aiohttp
|
31
|
-
Requires-Dist: aiohttp<4.0.0; extra == "aiohttp"
|
32
|
-
Dynamic: license-file
|
33
|
-
|
34
1
|
# Google Gen AI SDK
|
35
2
|
|
36
3
|
[](https://pypi.org/project/google-genai/)
|
@@ -142,6 +109,49 @@ client = genai.Client(
|
|
142
109
|
)
|
143
110
|
```
|
144
111
|
|
112
|
+
### Faster async client option: Aiohttp
|
113
|
+
|
114
|
+
By default we use httpx for both sync and async client implementations. In order
|
115
|
+
to have faster performance, you may install `google-genai[aiohttp]`. In Gen AI
|
116
|
+
SDK we configure `trust_env=True` to match with the default behavior of httpx.
|
117
|
+
Additional args of `aiohttp.ClientSession.request()` ([see _RequestOptions args](https://github.com/aio-libs/aiohttp/blob/v3.12.13/aiohttp/client.py#L170)) can be passed
|
118
|
+
through the following way:
|
119
|
+
|
120
|
+
```python
|
121
|
+
|
122
|
+
http_options = types.HttpOptions(
|
123
|
+
async_client_args={'cookies': ..., 'ssl': ...},
|
124
|
+
)
|
125
|
+
|
126
|
+
client=Client(..., http_options=http_options)
|
127
|
+
```
|
128
|
+
|
129
|
+
### Proxy
|
130
|
+
|
131
|
+
Both httpx and aiohttp libraries use `urllib.request.getproxies` from
|
132
|
+
environment variables. Before client initialization, you may set proxy (and
|
133
|
+
optional SSL_CERT_FILE) by setting the environment variables:
|
134
|
+
|
135
|
+
|
136
|
+
```bash
|
137
|
+
export HTTPS_PROXY='http://username:password@proxy_uri:port'
|
138
|
+
export SSL_CERT_FILE='client.pem'
|
139
|
+
```
|
140
|
+
|
141
|
+
If you need `socks5` proxy, httpx [supports](https://www.python-httpx.org/advanced/proxies/#socks) `socks5` proxy if you pass it via
|
142
|
+
args to `httpx.Client()`. You may install `httpx[socks]` to use it.
|
143
|
+
Then, you can pass it through the following way:
|
144
|
+
|
145
|
+
```python
|
146
|
+
|
147
|
+
http_options = types.HttpOptions(
|
148
|
+
client_args={'proxy': 'socks5://user:pass@host:port'},
|
149
|
+
async_client_args={'proxy': 'socks5://user:pass@host:port'},,
|
150
|
+
)
|
151
|
+
|
152
|
+
client=Client(..., http_options=http_options)
|
153
|
+
```
|
154
|
+
|
145
155
|
## Types
|
146
156
|
|
147
157
|
Parameter types can be specified as either dictionaries(`TypedDict`) or
|
@@ -25,6 +25,7 @@ import copy
|
|
25
25
|
from dataclasses import dataclass
|
26
26
|
import datetime
|
27
27
|
import http
|
28
|
+
import inspect
|
28
29
|
import io
|
29
30
|
import json
|
30
31
|
import logging
|
@@ -34,7 +35,7 @@ import ssl
|
|
34
35
|
import sys
|
35
36
|
import threading
|
36
37
|
import time
|
37
|
-
from typing import Any, AsyncIterator, Optional,
|
38
|
+
from typing import Any, AsyncIterator, Optional, TYPE_CHECKING, Tuple, Union
|
38
39
|
from urllib.parse import urlparse
|
39
40
|
from urllib.parse import urlunparse
|
40
41
|
|
@@ -48,6 +49,7 @@ import httpx
|
|
48
49
|
from pydantic import BaseModel
|
49
50
|
from pydantic import Field
|
50
51
|
from pydantic import ValidationError
|
52
|
+
import tenacity
|
51
53
|
|
52
54
|
from . import _common
|
53
55
|
from . import errors
|
@@ -55,15 +57,19 @@ from . import version
|
|
55
57
|
from .types import HttpOptions
|
56
58
|
from .types import HttpOptionsDict
|
57
59
|
from .types import HttpOptionsOrDict
|
60
|
+
from .types import HttpResponse as SdkHttpResponse
|
61
|
+
from .types import HttpRetryOptions
|
62
|
+
|
58
63
|
|
59
64
|
has_aiohttp = False
|
60
65
|
try:
|
61
66
|
import aiohttp
|
67
|
+
|
62
68
|
has_aiohttp = True
|
63
69
|
except ImportError:
|
64
70
|
pass
|
65
71
|
|
66
|
-
|
72
|
+
# internal comment
|
67
73
|
|
68
74
|
|
69
75
|
if TYPE_CHECKING:
|
@@ -94,8 +100,7 @@ def _get_env_api_key() -> Optional[str]:
|
|
94
100
|
env_gemini_api_key = os.environ.get('GEMINI_API_KEY', None)
|
95
101
|
if env_google_api_key and env_gemini_api_key:
|
96
102
|
logger.warning(
|
97
|
-
'Both GOOGLE_API_KEY and GEMINI_API_KEY are set. Using'
|
98
|
-
' GOOGLE_API_KEY.'
|
103
|
+
'Both GOOGLE_API_KEY and GEMINI_API_KEY are set. Using GOOGLE_API_KEY.'
|
99
104
|
)
|
100
105
|
|
101
106
|
return env_google_api_key or env_gemini_api_key or None
|
@@ -117,7 +122,9 @@ def _append_library_version_headers(headers: dict[str, str]) -> None:
|
|
117
122
|
'x-goog-api-client' in headers
|
118
123
|
and version_header_value not in headers['x-goog-api-client']
|
119
124
|
):
|
120
|
-
headers['x-goog-api-client'] =
|
125
|
+
headers['x-goog-api-client'] = (
|
126
|
+
f'{version_header_value} ' + headers['x-goog-api-client']
|
127
|
+
)
|
121
128
|
elif 'x-goog-api-client' not in headers:
|
122
129
|
headers['x-goog-api-client'] = version_header_value
|
123
130
|
|
@@ -213,18 +220,6 @@ class HttpRequest:
|
|
213
220
|
timeout: Optional[float] = None
|
214
221
|
|
215
222
|
|
216
|
-
# TODO(b/394358912): Update this class to use a SDKResponse class that can be
|
217
|
-
# generated and used for all languages.
|
218
|
-
class BaseResponse(_common.BaseModel):
|
219
|
-
http_headers: Optional[dict[str, str]] = Field(
|
220
|
-
default=None, description='The http headers of the response.'
|
221
|
-
)
|
222
|
-
|
223
|
-
json_payload: Optional[Any] = Field(
|
224
|
-
default=None, description='The json payload of the response.'
|
225
|
-
)
|
226
|
-
|
227
|
-
|
228
223
|
class HttpResponse:
|
229
224
|
|
230
225
|
def __init__(
|
@@ -327,6 +322,56 @@ class HttpResponse:
|
|
327
322
|
response_payload[attribute] = copy.deepcopy(getattr(self, attribute))
|
328
323
|
|
329
324
|
|
325
|
+
# Default retry options.
|
326
|
+
# The config is based on https://cloud.google.com/storage/docs/retry-strategy.
|
327
|
+
_RETRY_ATTEMPTS = 3
|
328
|
+
_RETRY_INITIAL_DELAY = 1.0 # seconds
|
329
|
+
_RETRY_MAX_DELAY = 120.0 # seconds
|
330
|
+
_RETRY_EXP_BASE = 2
|
331
|
+
_RETRY_JITTER = 1
|
332
|
+
_RETRY_HTTP_STATUS_CODES = (
|
333
|
+
408, # Request timeout.
|
334
|
+
429, # Too many requests.
|
335
|
+
500, # Internal server error.
|
336
|
+
502, # Bad gateway.
|
337
|
+
503, # Service unavailable.
|
338
|
+
504, # Gateway timeout
|
339
|
+
)
|
340
|
+
|
341
|
+
|
342
|
+
def _retry_args(options: Optional[HttpRetryOptions]) -> dict[str, Any]:
|
343
|
+
"""Returns the retry args for the given http retry options.
|
344
|
+
|
345
|
+
Args:
|
346
|
+
options: The http retry options to use for the retry configuration. If None,
|
347
|
+
the 'never retry' stop strategy will be used.
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
The arguments passed to the tenacity.(Async)Retrying constructor.
|
351
|
+
"""
|
352
|
+
if options is None:
|
353
|
+
return {'stop': tenacity.stop_after_attempt(1)}
|
354
|
+
|
355
|
+
stop = tenacity.stop_after_attempt(options.attempts or _RETRY_ATTEMPTS)
|
356
|
+
retriable_codes = options.http_status_codes or _RETRY_HTTP_STATUS_CODES
|
357
|
+
retry = tenacity.retry_if_result(
|
358
|
+
lambda response: response.status_code in retriable_codes,
|
359
|
+
)
|
360
|
+
retry_error_callback = lambda retry_state: retry_state.outcome.result()
|
361
|
+
wait = tenacity.wait_exponential_jitter(
|
362
|
+
initial=options.initial_delay or _RETRY_INITIAL_DELAY,
|
363
|
+
max=options.max_delay or _RETRY_MAX_DELAY,
|
364
|
+
exp_base=options.exp_base or _RETRY_EXP_BASE,
|
365
|
+
jitter=options.jitter or _RETRY_JITTER,
|
366
|
+
)
|
367
|
+
return {
|
368
|
+
'stop': stop,
|
369
|
+
'retry': retry,
|
370
|
+
'retry_error_callback': retry_error_callback,
|
371
|
+
'wait': wait,
|
372
|
+
}
|
373
|
+
|
374
|
+
|
330
375
|
class SyncHttpxClient(httpx.Client):
|
331
376
|
"""Sync httpx client."""
|
332
377
|
|
@@ -408,7 +453,7 @@ class BaseApiClient:
|
|
408
453
|
try:
|
409
454
|
validated_http_options = HttpOptions.model_validate(http_options)
|
410
455
|
except ValidationError as e:
|
411
|
-
raise ValueError(
|
456
|
+
raise ValueError('Invalid http_options') from e
|
412
457
|
elif isinstance(http_options, HttpOptions):
|
413
458
|
validated_http_options = http_options
|
414
459
|
|
@@ -509,6 +554,15 @@ class BaseApiClient:
|
|
509
554
|
)
|
510
555
|
self._httpx_client = SyncHttpxClient(**client_args)
|
511
556
|
self._async_httpx_client = AsyncHttpxClient(**async_client_args)
|
557
|
+
if has_aiohttp:
|
558
|
+
# Do it once at the genai.Client level. Share among all requests.
|
559
|
+
self._async_client_session_request_args = self._ensure_aiohttp_ssl_ctx(
|
560
|
+
self._http_options
|
561
|
+
)
|
562
|
+
|
563
|
+
retry_kwargs = _retry_args(self._http_options.retry_options)
|
564
|
+
self._retry = tenacity.Retrying(**retry_kwargs, reraise=True)
|
565
|
+
self._async_retry = tenacity.AsyncRetrying(**retry_kwargs, reraise=True)
|
512
566
|
|
513
567
|
@staticmethod
|
514
568
|
def _ensure_httpx_ssl_ctx(
|
@@ -529,8 +583,11 @@ class BaseApiClient:
|
|
529
583
|
args = options.client_args
|
530
584
|
async_args = options.async_client_args
|
531
585
|
ctx = (
|
532
|
-
args.get(verify)
|
533
|
-
|
586
|
+
args.get(verify)
|
587
|
+
if args
|
588
|
+
else None or async_args.get(verify)
|
589
|
+
if async_args
|
590
|
+
else None
|
534
591
|
)
|
535
592
|
|
536
593
|
if not ctx:
|
@@ -561,7 +618,12 @@ class BaseApiClient:
|
|
561
618
|
if not args or not args.get(verify):
|
562
619
|
args = (args or {}).copy()
|
563
620
|
args[verify] = ctx
|
564
|
-
|
621
|
+
# Drop the args that isn't used by the httpx client.
|
622
|
+
copied_args = args.copy()
|
623
|
+
for key in copied_args.copy():
|
624
|
+
if key not in inspect.signature(httpx.Client.__init__).parameters:
|
625
|
+
del copied_args[key]
|
626
|
+
return copied_args
|
565
627
|
|
566
628
|
return (
|
567
629
|
_maybe_set(args, ctx),
|
@@ -581,7 +643,7 @@ class BaseApiClient:
|
|
581
643
|
An async aiohttp ClientSession._request args.
|
582
644
|
"""
|
583
645
|
|
584
|
-
verify = '
|
646
|
+
verify = 'ssl' # keep it consistent with httpx.
|
585
647
|
async_args = options.async_client_args
|
586
648
|
ctx = async_args.get(verify) if async_args else None
|
587
649
|
|
@@ -613,10 +675,16 @@ class BaseApiClient:
|
|
613
675
|
"""
|
614
676
|
if not args or not args.get(verify):
|
615
677
|
args = (args or {}).copy()
|
616
|
-
args[
|
617
|
-
|
618
|
-
|
619
|
-
|
678
|
+
args[verify] = ctx
|
679
|
+
# Drop the args that isn't in the aiohttp RequestOptions.
|
680
|
+
copied_args = args.copy()
|
681
|
+
for key in copied_args.copy():
|
682
|
+
if (
|
683
|
+
key
|
684
|
+
not in inspect.signature(aiohttp.ClientSession._request).parameters
|
685
|
+
):
|
686
|
+
del copied_args[key]
|
687
|
+
return copied_args
|
620
688
|
|
621
689
|
return _maybe_set(async_args, ctx)
|
622
690
|
|
@@ -724,6 +792,14 @@ class BaseApiClient:
|
|
724
792
|
else:
|
725
793
|
base_url = patched_http_options.base_url
|
726
794
|
|
795
|
+
if (
|
796
|
+
hasattr(patched_http_options, 'extra_body')
|
797
|
+
and patched_http_options.extra_body
|
798
|
+
):
|
799
|
+
_common.recursive_dict_update(
|
800
|
+
request_dict, patched_http_options.extra_body
|
801
|
+
)
|
802
|
+
|
727
803
|
url = _join_url_path(
|
728
804
|
base_url,
|
729
805
|
versioned_path,
|
@@ -749,7 +825,7 @@ class BaseApiClient:
|
|
749
825
|
timeout=timeout_in_seconds,
|
750
826
|
)
|
751
827
|
|
752
|
-
def
|
828
|
+
def _request_once(
|
753
829
|
self,
|
754
830
|
http_request: HttpRequest,
|
755
831
|
stream: bool = False,
|
@@ -795,7 +871,14 @@ class BaseApiClient:
|
|
795
871
|
response.headers, response if stream else [response.text]
|
796
872
|
)
|
797
873
|
|
798
|
-
|
874
|
+
def _request(
|
875
|
+
self,
|
876
|
+
http_request: HttpRequest,
|
877
|
+
stream: bool = False,
|
878
|
+
) -> HttpResponse:
|
879
|
+
return self._retry(self._request_once, http_request, stream) # type: ignore[no-any-return]
|
880
|
+
|
881
|
+
async def _async_request_once(
|
799
882
|
self, http_request: HttpRequest, stream: bool = False
|
800
883
|
) -> HttpResponse:
|
801
884
|
data: Optional[Union[str, bytes]] = None
|
@@ -819,30 +902,16 @@ class BaseApiClient:
|
|
819
902
|
if has_aiohttp:
|
820
903
|
session = aiohttp.ClientSession(
|
821
904
|
headers=http_request.headers,
|
905
|
+
trust_env=True,
|
906
|
+
)
|
907
|
+
response = await session.request(
|
908
|
+
method=http_request.method,
|
909
|
+
url=http_request.url,
|
910
|
+
headers=http_request.headers,
|
911
|
+
data=data,
|
912
|
+
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
913
|
+
**self._async_client_session_request_args,
|
822
914
|
)
|
823
|
-
if self._http_options.async_client_args:
|
824
|
-
# When using aiohttp request options with ssl context, the latency will higher than using httpx.
|
825
|
-
# Use it only if necessary. Otherwise, httpx asyncclient is faster.
|
826
|
-
async_client_args = self._ensure_aiohttp_ssl_ctx(
|
827
|
-
self._http_options
|
828
|
-
)
|
829
|
-
response = await session.request(
|
830
|
-
method=http_request.method,
|
831
|
-
url=http_request.url,
|
832
|
-
headers=http_request.headers,
|
833
|
-
data=data,
|
834
|
-
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
835
|
-
**async_client_args,
|
836
|
-
)
|
837
|
-
else:
|
838
|
-
# Aiohttp performs better than httpx w/o ssl context.
|
839
|
-
response = await session.request(
|
840
|
-
method=http_request.method,
|
841
|
-
url=http_request.url,
|
842
|
-
headers=http_request.headers,
|
843
|
-
data=data,
|
844
|
-
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
845
|
-
)
|
846
915
|
await errors.APIError.raise_for_async_response(response)
|
847
916
|
return HttpResponse(response.headers, response)
|
848
917
|
else:
|
@@ -862,39 +931,20 @@ class BaseApiClient:
|
|
862
931
|
return HttpResponse(client_response.headers, client_response)
|
863
932
|
else:
|
864
933
|
if has_aiohttp:
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
934
|
+
async with aiohttp.ClientSession(
|
935
|
+
headers=http_request.headers,
|
936
|
+
trust_env=True,
|
937
|
+
) as session:
|
938
|
+
response = await session.request(
|
939
|
+
method=http_request.method,
|
940
|
+
url=http_request.url,
|
941
|
+
headers=http_request.headers,
|
942
|
+
data=data,
|
943
|
+
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
944
|
+
**self._async_client_session_request_args,
|
870
945
|
)
|
871
|
-
|
872
|
-
|
873
|
-
) as session:
|
874
|
-
response = await session.request(
|
875
|
-
method=http_request.method,
|
876
|
-
url=http_request.url,
|
877
|
-
headers=http_request.headers,
|
878
|
-
data=data,
|
879
|
-
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
880
|
-
**async_client_args,
|
881
|
-
)
|
882
|
-
await errors.APIError.raise_for_async_response(response)
|
883
|
-
return HttpResponse(response.headers, [await response.text()])
|
884
|
-
else:
|
885
|
-
# Aiohttp performs better than httpx if not using ssl context.
|
886
|
-
async with aiohttp.ClientSession(
|
887
|
-
headers=http_request.headers
|
888
|
-
) as session:
|
889
|
-
response = await session.request(
|
890
|
-
method=http_request.method,
|
891
|
-
url=http_request.url,
|
892
|
-
headers=http_request.headers,
|
893
|
-
data=data,
|
894
|
-
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
895
|
-
)
|
896
|
-
await errors.APIError.raise_for_async_response(response)
|
897
|
-
return HttpResponse(response.headers, [await response.text()])
|
946
|
+
await errors.APIError.raise_for_async_response(response)
|
947
|
+
return HttpResponse(response.headers, [await response.text()])
|
898
948
|
else:
|
899
949
|
# aiohttp is not available. Fall back to httpx.
|
900
950
|
client_response = await self._async_httpx_client.request(
|
@@ -907,6 +957,15 @@ class BaseApiClient:
|
|
907
957
|
await errors.APIError.raise_for_async_response(client_response)
|
908
958
|
return HttpResponse(client_response.headers, [client_response.text])
|
909
959
|
|
960
|
+
async def _async_request(
|
961
|
+
self,
|
962
|
+
http_request: HttpRequest,
|
963
|
+
stream: bool = False,
|
964
|
+
) -> HttpResponse:
|
965
|
+
return await self._async_retry( # type: ignore[no-any-return]
|
966
|
+
self._async_request_once, http_request, stream
|
967
|
+
)
|
968
|
+
|
910
969
|
def get_read_only_http_options(self) -> dict[str, Any]:
|
911
970
|
if isinstance(self._http_options, BaseModel):
|
912
971
|
copied = self._http_options.model_dump()
|
@@ -920,17 +979,16 @@ class BaseApiClient:
|
|
920
979
|
path: str,
|
921
980
|
request_dict: dict[str, object],
|
922
981
|
http_options: Optional[HttpOptionsOrDict] = None,
|
923
|
-
) ->
|
982
|
+
) -> SdkHttpResponse:
|
924
983
|
http_request = self._build_request(
|
925
984
|
http_method, path, request_dict, http_options
|
926
985
|
)
|
927
986
|
response = self._request(http_request, stream=False)
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
return json_response
|
987
|
+
response_body = response.response_stream[0] if response.response_stream else ''
|
988
|
+
return SdkHttpResponse(
|
989
|
+
headers=response.headers, body=response_body
|
990
|
+
)
|
991
|
+
|
934
992
|
|
935
993
|
def request_streamed(
|
936
994
|
self,
|
@@ -953,16 +1011,17 @@ class BaseApiClient:
|
|
953
1011
|
path: str,
|
954
1012
|
request_dict: dict[str, object],
|
955
1013
|
http_options: Optional[HttpOptionsOrDict] = None,
|
956
|
-
) ->
|
1014
|
+
) -> SdkHttpResponse:
|
957
1015
|
http_request = self._build_request(
|
958
1016
|
http_method, path, request_dict, http_options
|
959
1017
|
)
|
960
1018
|
|
961
1019
|
result = await self._async_request(http_request=http_request, stream=False)
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
1020
|
+
response_body = result.response_stream[0] if result.response_stream else ''
|
1021
|
+
return SdkHttpResponse(
|
1022
|
+
headers=result.headers, body=response_body
|
1023
|
+
)
|
1024
|
+
|
966
1025
|
|
967
1026
|
async def async_request_streamed(
|
968
1027
|
self,
|
@@ -1091,9 +1150,7 @@ class BaseApiClient:
|
|
1091
1150
|
)
|
1092
1151
|
|
1093
1152
|
if response.headers.get('x-goog-upload-status') != 'final':
|
1094
|
-
raise ValueError(
|
1095
|
-
'Failed to upload file: Upload status is not finalized.'
|
1096
|
-
)
|
1153
|
+
raise ValueError('Failed to upload file: Upload status is not finalized.')
|
1097
1154
|
return HttpResponse(response.headers, response_stream=[response.text])
|
1098
1155
|
|
1099
1156
|
def download_file(
|
@@ -1101,7 +1158,7 @@ class BaseApiClient:
|
|
1101
1158
|
path: str,
|
1102
1159
|
*,
|
1103
1160
|
http_options: Optional[HttpOptionsOrDict] = None,
|
1104
|
-
) -> Union[Any,bytes]:
|
1161
|
+
) -> Union[Any, bytes]:
|
1105
1162
|
"""Downloads the file data.
|
1106
1163
|
|
1107
1164
|
Args:
|
@@ -1192,7 +1249,8 @@ class BaseApiClient:
|
|
1192
1249
|
# Upload the file in chunks
|
1193
1250
|
if has_aiohttp: # pylint: disable=g-import-not-at-top
|
1194
1251
|
async with aiohttp.ClientSession(
|
1195
|
-
headers=self._http_options.headers
|
1252
|
+
headers=self._http_options.headers,
|
1253
|
+
trust_env=True,
|
1196
1254
|
) as session:
|
1197
1255
|
while True:
|
1198
1256
|
if isinstance(file, io.IOBase):
|
@@ -1313,7 +1371,11 @@ class BaseApiClient:
|
|
1313
1371
|
headers=upload_headers,
|
1314
1372
|
timeout=timeout_in_seconds,
|
1315
1373
|
)
|
1316
|
-
if
|
1374
|
+
if (
|
1375
|
+
client_response is not None
|
1376
|
+
and client_response.headers
|
1377
|
+
and client_response.headers.get('x-goog-upload-status')
|
1378
|
+
):
|
1317
1379
|
break
|
1318
1380
|
delay_seconds = INITIAL_RETRY_DELAY * (DELAY_MULTIPLIER**retry_count)
|
1319
1381
|
retry_count += 1
|
@@ -1338,7 +1400,9 @@ class BaseApiClient:
|
|
1338
1400
|
raise ValueError(
|
1339
1401
|
'Failed to upload file: Upload status is not finalized.'
|
1340
1402
|
)
|
1341
|
-
return HttpResponse(
|
1403
|
+
return HttpResponse(
|
1404
|
+
client_response.headers, response_stream=[client_response.text]
|
1405
|
+
)
|
1342
1406
|
|
1343
1407
|
async def async_download_file(
|
1344
1408
|
self,
|
@@ -1367,7 +1431,10 @@ class BaseApiClient:
|
|
1367
1431
|
data = http_request.data
|
1368
1432
|
|
1369
1433
|
if has_aiohttp:
|
1370
|
-
async with aiohttp.ClientSession(
|
1434
|
+
async with aiohttp.ClientSession(
|
1435
|
+
headers=http_request.headers,
|
1436
|
+
trust_env=True,
|
1437
|
+
) as session:
|
1371
1438
|
response = await session.request(
|
1372
1439
|
method=http_request.method,
|
1373
1440
|
url=http_request.url,
|