google-genai 1.12.1__py3-none-any.whl → 1.14.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.
@@ -58,7 +58,9 @@ from .types import HttpOptionsOrDict
58
58
 
59
59
  logger = logging.getLogger('google_genai._api_client')
60
60
  CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB chunk size
61
-
61
+ MAX_RETRY_COUNT = 3
62
+ INITIAL_RETRY_DELAY = 1 # second
63
+ DELAY_MULTIPLIER = 2
62
64
 
63
65
  def _append_library_version_headers(headers: dict[str, str]) -> None:
64
66
  """Appends the telemetry header to the headers dict."""
@@ -283,6 +285,18 @@ class SyncHttpxClient(httpx.Client):
283
285
  kwargs.setdefault('follow_redirects', True)
284
286
  super().__init__(**kwargs)
285
287
 
288
+ def __del__(self) -> None:
289
+ """Closes the httpx client."""
290
+ try:
291
+ if self.is_closed:
292
+ return
293
+ except Exception:
294
+ pass
295
+ try:
296
+ self.close()
297
+ except Exception:
298
+ pass
299
+
286
300
 
287
301
  class AsyncHttpxClient(httpx.AsyncClient):
288
302
  """Async httpx client."""
@@ -292,6 +306,17 @@ class AsyncHttpxClient(httpx.AsyncClient):
292
306
  kwargs.setdefault('follow_redirects', True)
293
307
  super().__init__(**kwargs)
294
308
 
309
+ def __del__(self) -> None:
310
+ try:
311
+ if self.is_closed:
312
+ return
313
+ except Exception:
314
+ pass
315
+ try:
316
+ asyncio.get_running_loop().create_task(self.aclose())
317
+ except Exception:
318
+ pass
319
+
295
320
 
296
321
  class BaseApiClient:
297
322
  """Client for calling HTTP APIs sending and receiving JSON."""
@@ -865,15 +890,23 @@ class BaseApiClient:
865
890
  'Content-Length': str(chunk_size),
866
891
  }
867
892
  _populate_server_timeout_header(upload_headers, timeout_in_seconds)
868
- response = self._httpx_client.request(
869
- method='POST',
870
- url=upload_url,
871
- headers=upload_headers,
872
- content=file_chunk,
873
- timeout=timeout_in_seconds,
874
- )
893
+ retry_count = 0
894
+ while retry_count < MAX_RETRY_COUNT:
895
+ response = self._httpx_client.request(
896
+ method='POST',
897
+ url=upload_url,
898
+ headers=upload_headers,
899
+ content=file_chunk,
900
+ timeout=timeout_in_seconds,
901
+ )
902
+ if response.headers.get('x-goog-upload-status'):
903
+ break
904
+ delay_seconds = INITIAL_RETRY_DELAY * (DELAY_MULTIPLIER**retry_count)
905
+ retry_count += 1
906
+ time.sleep(delay_seconds)
907
+
875
908
  offset += chunk_size
876
- if response.headers['x-goog-upload-status'] != 'active':
909
+ if response.headers.get('x-goog-upload-status') != 'active':
877
910
  break # upload is complete or it has been interrupted.
878
911
  if upload_size <= offset: # Status is not finalized.
879
912
  raise ValueError(
@@ -881,7 +914,7 @@ class BaseApiClient:
881
914
  f' finalized.'
882
915
  )
883
916
 
884
- if response.headers['x-goog-upload-status'] != 'final':
917
+ if response.headers.get('x-goog-upload-status') != 'final':
885
918
  raise ValueError(
886
919
  'Failed to upload file: Upload status is not finalized.'
887
920
  )
@@ -1013,13 +1046,22 @@ class BaseApiClient:
1013
1046
  'Content-Length': str(chunk_size),
1014
1047
  }
1015
1048
  _populate_server_timeout_header(upload_headers, timeout_in_seconds)
1016
- response = await self._async_httpx_client.request(
1017
- method='POST',
1018
- url=upload_url,
1019
- content=file_chunk,
1020
- headers=upload_headers,
1021
- timeout=timeout_in_seconds,
1022
- )
1049
+
1050
+ retry_count = 0
1051
+ while retry_count < MAX_RETRY_COUNT:
1052
+ response = await self._async_httpx_client.request(
1053
+ method='POST',
1054
+ url=upload_url,
1055
+ content=file_chunk,
1056
+ headers=upload_headers,
1057
+ timeout=timeout_in_seconds,
1058
+ )
1059
+ if response.headers.get('x-goog-upload-status'):
1060
+ break
1061
+ delay_seconds = INITIAL_RETRY_DELAY * (DELAY_MULTIPLIER**retry_count)
1062
+ retry_count += 1
1063
+ time.sleep(delay_seconds)
1064
+
1023
1065
  offset += chunk_size
1024
1066
  if response.headers.get('x-goog-upload-status') != 'active':
1025
1067
  break # upload is complete or it has been interrupted.
@@ -0,0 +1,50 @@
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
+ import os
17
+ from typing import Optional
18
+
19
+ from .types import HttpOptions
20
+
21
+ _default_base_gemini_url = None
22
+ _default_base_vertex_url = None
23
+
24
+
25
+ def set_default_base_urls(
26
+ gemini_url: Optional[str], vertex_url: Optional[str]
27
+ ) -> None:
28
+ """Overrides the base URLs for the Gemini API and Vertex AI API."""
29
+ global _default_base_gemini_url, _default_base_vertex_url
30
+ _default_base_gemini_url = gemini_url
31
+ _default_base_vertex_url = vertex_url
32
+
33
+
34
+ def get_base_url(
35
+ vertexai: bool,
36
+ http_options: Optional[HttpOptions] = None,
37
+ ) -> Optional[str]:
38
+ """Returns the default base URL based on the following priority.
39
+
40
+ 1. Base URLs set via HttpOptions.
41
+ 2. Base URLs set via the latest call to setDefaultBaseUrls.
42
+ 3. Base URLs set via environment variables.
43
+ """
44
+ if http_options and http_options.base_url:
45
+ return http_options.base_url
46
+
47
+ if vertexai:
48
+ return _default_base_vertex_url or os.getenv('GOOGLE_VERTEX_BASE_URL')
49
+ else:
50
+ return _default_base_gemini_url or os.getenv('GOOGLE_GEMINI_BASE_URL')