google-genai 1.20.0__tar.gz → 1.21.0__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.
Files changed (41) hide show
  1. {google_genai-1.20.0 → google_genai-1.21.0}/PKG-INFO +45 -1
  2. google_genai-1.20.0/google_genai.egg-info/PKG-INFO → google_genai-1.21.0/README.md +43 -33
  3. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_api_client.py +170 -103
  4. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_common.py +73 -0
  5. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_live_converters.py +174 -414
  6. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_replay_api_client.py +9 -3
  7. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_tokens_converters.py +81 -176
  8. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_transformers.py +19 -40
  9. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/batches.py +46 -64
  10. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/caches.py +131 -222
  11. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/chats.py +4 -4
  12. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/client.py +1 -1
  13. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/files.py +88 -106
  14. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/live.py +15 -20
  15. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/live_music.py +4 -5
  16. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/models.py +317 -560
  17. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/operations.py +35 -68
  18. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/tokens.py +11 -6
  19. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/tunings.py +64 -113
  20. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/types.py +132 -9
  21. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/version.py +1 -1
  22. google_genai-1.20.0/README.md → google_genai-1.21.0/google_genai.egg-info/PKG-INFO +77 -0
  23. {google_genai-1.20.0 → google_genai-1.21.0}/google_genai.egg-info/requires.txt +1 -0
  24. {google_genai-1.20.0 → google_genai-1.21.0}/pyproject.toml +2 -1
  25. {google_genai-1.20.0 → google_genai-1.21.0}/LICENSE +0 -0
  26. {google_genai-1.20.0 → google_genai-1.21.0}/MANIFEST.in +0 -0
  27. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/__init__.py +0 -0
  28. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_adapters.py +0 -0
  29. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_api_module.py +0 -0
  30. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_automatic_function_calling_util.py +0 -0
  31. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_base_url.py +0 -0
  32. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_extra_utils.py +0 -0
  33. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_mcp_utils.py +0 -0
  34. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/_test_api_client.py +0 -0
  35. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/errors.py +0 -0
  36. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/pagers.py +0 -0
  37. {google_genai-1.20.0 → google_genai-1.21.0}/google/genai/py.typed +0 -0
  38. {google_genai-1.20.0 → google_genai-1.21.0}/google_genai.egg-info/SOURCES.txt +0 -0
  39. {google_genai-1.20.0 → google_genai-1.21.0}/google_genai.egg-info/dependency_links.txt +0 -0
  40. {google_genai-1.20.0 → google_genai-1.21.0}/google_genai.egg-info/top_level.txt +0 -0
  41. {google_genai-1.20.0 → google_genai-1.21.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-genai
3
- Version: 1.20.0
3
+ Version: 1.21.0
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
  [![PyPI version](https://img.shields.io/pypi/v/google-genai.svg)](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, Tuple, TYPE_CHECKING, Union
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
- has_aiohttp = False # exclude from copybar export
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'] = f'{version_header_value} ' + 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(f'Invalid http_options: {e}')
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)
565
+ self._async_retry = tenacity.AsyncRetrying(**retry_kwargs)
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) if args else None
533
- or async_args.get(verify) if async_args else None
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
- return args
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 = '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['ssl'] = ctx
617
- else:
618
- args['ssl'] = args.pop(verify)
619
- return args
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 _request(
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
- async def _async_request(
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
- if self._http_options.async_client_args:
866
- # Note that when using aiohttp request options with ssl context, the
867
- # latency will higher than using httpx async client with ssl context.
868
- async_client_args = self._ensure_aiohttp_ssl_ctx(
869
- self._http_options
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
- async with aiohttp.ClientSession(
872
- headers=http_request.headers
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
- ) -> Union[BaseResponse, Any]:
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
- json_response = response.json
929
- if not json_response:
930
- return BaseResponse(http_headers=response.headers).model_dump(
931
- by_alias=True
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
- ) -> Union[BaseResponse, Any]:
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
- json_response = result.json
963
- if not json_response:
964
- return BaseResponse(http_headers=result.headers).model_dump(by_alias=True)
965
- return json_response
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 client_response is not None and client_response.headers and client_response.headers.get('x-goog-upload-status'):
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(client_response.headers, response_stream=[client_response.text])
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(headers=http_request.headers) as session:
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,