gemini-webapi 1.17.1__tar.gz → 1.17.3__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 (43) hide show
  1. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/.github/workflows/github-release.yml +1 -1
  2. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/.github/workflows/pypi-publish.yml +1 -1
  3. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/PKG-INFO +2 -2
  4. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/pyproject.toml +1 -1
  5. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/client.py +6 -7
  6. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/constants.py +1 -0
  7. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/types/image.py +28 -11
  8. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/decorators.py +4 -3
  9. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/get_access_token.py +1 -7
  10. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/parsing.py +4 -3
  11. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/rotate_1psidts.py +1 -1
  12. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/upload_file.py +1 -1
  13. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi.egg-info/PKG-INFO +2 -2
  14. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi.egg-info/requires.txt +1 -1
  15. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/.github/dependabot.yml +0 -0
  16. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/.gitignore +0 -0
  17. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/.vscode/launch.json +0 -0
  18. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/.vscode/settings.json +0 -0
  19. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/LICENSE +0 -0
  20. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/README.md +0 -0
  21. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/assets/banner.png +0 -0
  22. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/assets/favicon.png +0 -0
  23. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/assets/logo.svg +0 -0
  24. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/assets/sample.pdf +0 -0
  25. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/setup.cfg +0 -0
  26. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/__init__.py +0 -0
  27. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/components/__init__.py +0 -0
  28. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/components/gem_mixin.py +0 -0
  29. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/exceptions.py +0 -0
  30. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/types/__init__.py +0 -0
  31. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/types/candidate.py +0 -0
  32. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/types/gem.py +0 -0
  33. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/types/grpc.py +0 -0
  34. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/types/modeloutput.py +0 -0
  35. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/__init__.py +0 -0
  36. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/load_browser_cookies.py +0 -0
  37. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi/utils/logger.py +0 -0
  38. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi.egg-info/SOURCES.txt +0 -0
  39. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi.egg-info/dependency_links.txt +0 -0
  40. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/src/gemini_webapi.egg-info/top_level.txt +0 -0
  41. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/tests/test_client_features.py +0 -0
  42. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/tests/test_gem_mixin.py +0 -0
  43. {gemini_webapi-1.17.1 → gemini_webapi-1.17.3}/tests/test_save_image.py +0 -0
@@ -11,7 +11,7 @@ jobs:
11
11
  permissions:
12
12
  contents: write
13
13
  steps:
14
- - uses: actions/checkout@v5
14
+ - uses: actions/checkout@v6
15
15
  - uses: ncipollo/release-action@v1
16
16
  with:
17
17
  body: ${{ github.event.head_commit.message }}
@@ -24,7 +24,7 @@ jobs:
24
24
  name: Build package
25
25
  runs-on: ubuntu-latest
26
26
  steps:
27
- - uses: actions/checkout@v5
27
+ - uses: actions/checkout@v6
28
28
  - name: Set up Python
29
29
  uses: actions/setup-python@v6
30
30
  with:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.17.1
3
+ Version: 1.17.3
4
4
  Summary: ✨ An elegant async Python wrapper for Google Gemini web app
5
5
  Author: UZQueen
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -676,7 +676,7 @@ Classifier: Programming Language :: Python :: 3.12
676
676
  Requires-Python: >=3.10
677
677
  Description-Content-Type: text/markdown
678
678
  License-File: LICENSE
679
- Requires-Dist: httpx[http2]~=0.28.1
679
+ Requires-Dist: httpx~=0.28.1
680
680
  Requires-Dist: loguru~=0.7.3
681
681
  Requires-Dist: orjson~=3.11.1
682
682
  Requires-Dist: pydantic~=2.12.2
@@ -20,7 +20,7 @@ classifiers = [
20
20
  ]
21
21
  requires-python = ">=3.10"
22
22
  dependencies = [
23
- "httpx[http2]~=0.28.1",
23
+ "httpx~=0.28.1",
24
24
  "loguru~=0.7.3",
25
25
  "orjson~=3.11.1",
26
26
  "pydantic~=2.12.2",
@@ -68,7 +68,7 @@ class GeminiClient(GemMixin):
68
68
  __slots__ = [
69
69
  "cookies",
70
70
  "proxy",
71
- "running",
71
+ "_running",
72
72
  "client",
73
73
  "access_token",
74
74
  "timeout",
@@ -91,7 +91,7 @@ class GeminiClient(GemMixin):
91
91
  super().__init__()
92
92
  self.cookies = {}
93
93
  self.proxy = proxy
94
- self.running: bool = False
94
+ self._running: bool = False
95
95
  self.client: AsyncClient | None = None
96
96
  self.access_token: str | None = None
97
97
  self.timeout: float = 300
@@ -142,7 +142,6 @@ class GeminiClient(GemMixin):
142
142
  )
143
143
 
144
144
  self.client = AsyncClient(
145
- http2=True,
146
145
  timeout=timeout,
147
146
  proxy=self.proxy,
148
147
  follow_redirects=True,
@@ -152,7 +151,7 @@ class GeminiClient(GemMixin):
152
151
  )
153
152
  self.access_token = access_token
154
153
  self.cookies = valid_cookies
155
- self.running = True
154
+ self._running = True
156
155
 
157
156
  self.timeout = timeout
158
157
  self.auto_close = auto_close
@@ -188,7 +187,7 @@ class GeminiClient(GemMixin):
188
187
  if delay:
189
188
  await asyncio.sleep(delay)
190
189
 
191
- self.running = False
190
+ self._running = False
192
191
 
193
192
  if self.close_task:
194
193
  self.close_task.cancel()
@@ -229,7 +228,7 @@ class GeminiClient(GemMixin):
229
228
 
230
229
  if new_1psidts:
231
230
  self.cookies["__Secure-1PSIDTS"] = new_1psidts
232
- if self.running:
231
+ if self._running:
233
232
  self.client.cookies.set("__Secure-1PSIDTS", new_1psidts)
234
233
  logger.debug("Cookies refreshed. New __Secure-1PSIDTS applied.")
235
234
 
@@ -380,7 +379,7 @@ class GeminiClient(GemMixin):
380
379
 
381
380
  try:
382
381
  error_code = get_nested_value(response_json, [0, 5, 2, 0, 1, 0], -1)
383
- match ErrorCode(error_code):
382
+ match error_code:
384
383
  case ErrorCode.USAGE_LIMIT_EXCEEDED:
385
384
  raise UsageLimitExceeded(
386
385
  f"Failed to generate contents. Usage limit of {model.model_name} model has exceeded. Please try switching to another model."
@@ -103,6 +103,7 @@ class ErrorCode(IntEnum):
103
103
  Known error codes returned from server.
104
104
  """
105
105
 
106
+ TEMPORARY_ERROR_1013 = 1013 # Randomly raised when generating with certain models, but disappears soon after
106
107
  USAGE_LIMIT_EXCEEDED = 1037
107
108
  MODEL_INCONSISTENT = 1050
108
109
  MODEL_HEADER_INVALID = 1052
@@ -71,16 +71,17 @@ class Image(BaseModel):
71
71
  """
72
72
 
73
73
  filename = filename or self.url.split("/")[-1].split("?")[0]
74
- try:
75
- filename = re.search(r"^(.*\.\w+)", filename).group()
76
- except AttributeError:
74
+ match = re.search(r"^(.*\.\w+)", filename)
75
+ if match:
76
+ filename = match.group()
77
+ else:
77
78
  if verbose:
78
79
  logger.warning(f"Invalid filename: {filename}")
79
80
  if skip_invalid_filename:
80
81
  return None
81
82
 
82
83
  async with AsyncClient(
83
- http2=True, follow_redirects=True, cookies=cookies, proxy=self.proxy
84
+ follow_redirects=True, cookies=cookies, proxy=self.proxy
84
85
  ) as client:
85
86
  response = await client.get(self.url)
86
87
  if response.status_code == 200:
@@ -137,19 +138,33 @@ class GeneratedImage(Image):
137
138
  return v
138
139
 
139
140
  # @override
140
- async def save(self, full_size=True, **kwargs) -> str | None:
141
+ async def save(
142
+ self,
143
+ path: str = "temp",
144
+ filename: str | None = None,
145
+ cookies: dict | None = None,
146
+ verbose: bool = False,
147
+ skip_invalid_filename: bool = False,
148
+ full_size: bool = True,
149
+ ) -> str | None:
141
150
  """
142
151
  Save the image to disk.
143
152
 
144
153
  Parameters
145
154
  ----------
155
+ path: `str`, optional
156
+ Path to save the image, by default will save to "./temp".
146
157
  filename: `str`, optional
147
158
  Filename to save the image, generated images are always in .png format, but file extension will not be included in the URL.
148
159
  And since the URL ends with a long hash, by default will use timestamp + end of the hash as the filename.
160
+ cookies: `dict`, optional
161
+ Cookies used for requesting the content of the image. If not provided, will use the cookies from the GeneratedImage instance.
162
+ verbose : `bool`, optional
163
+ If True, will print the path of the saved file or warning for invalid file name, by default False.
164
+ skip_invalid_filename: `bool`, optional
165
+ If True, will only save the image if the file name and extension are valid, by default False.
149
166
  full_size: `bool`, optional
150
- If True, will modify the default preview (512*512) URL to get the full size image.
151
- kwargs: `dict`, optional
152
- Other arguments to pass to `Image.save`.
167
+ If True, will modify the default preview (512*512) URL to get the full size image, by default True.
153
168
 
154
169
  Returns
155
170
  -------
@@ -161,8 +176,10 @@ class GeneratedImage(Image):
161
176
  self.url += "=s2048"
162
177
 
163
178
  return await super().save(
164
- filename=kwargs.pop("filename", None)
179
+ path=path,
180
+ filename=filename
165
181
  or f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{self.url[-10:]}.png",
166
- cookies=self.cookies,
167
- **kwargs,
182
+ cookies=cookies or self.cookies,
183
+ verbose=verbose,
184
+ skip_invalid_filename=skip_invalid_filename,
168
185
  )
@@ -1,10 +1,11 @@
1
1
  import asyncio
2
2
  import functools
3
+ from collections.abc import Callable
3
4
 
4
5
  from ..exceptions import APIError, ImageGenerationError
5
6
 
6
7
 
7
- def running(retry: int = 0) -> callable:
8
+ def running(retry: int = 0) -> Callable:
8
9
  """
9
10
  Decorator to check if GeminiClient is running before making a request.
10
11
 
@@ -18,7 +19,7 @@ def running(retry: int = 0) -> callable:
18
19
  @functools.wraps(func)
19
20
  async def wrapper(client, *args, retry=retry, **kwargs):
20
21
  try:
21
- if not client.running:
22
+ if not client._running:
22
23
  await client.init(
23
24
  timeout=client.timeout,
24
25
  auto_close=client.auto_close,
@@ -27,7 +28,7 @@ def running(retry: int = 0) -> callable:
27
28
  refresh_interval=client.refresh_interval,
28
29
  verbose=False,
29
30
  )
30
- if client.running:
31
+ if client._running:
31
32
  return await func(client, *args, **kwargs)
32
33
 
33
34
  # Should not reach here
@@ -20,7 +20,6 @@ async def send_request(
20
20
  """
21
21
 
22
22
  async with AsyncClient(
23
- http2=True,
24
23
  proxy=proxy,
25
24
  headers=Headers.GEMINI.value,
26
25
  cookies=cookies,
@@ -66,12 +65,7 @@ async def get_access_token(
66
65
  If all requests failed.
67
66
  """
68
67
 
69
- async with AsyncClient(
70
- http2=True,
71
- proxy=proxy,
72
- follow_redirects=True,
73
- verify=False,
74
- ) as client:
68
+ async with AsyncClient(proxy=proxy, follow_redirects=True, verify=False) as client:
75
69
  response = await client.get(Endpoint.GOOGLE.value)
76
70
 
77
71
  extra_cookies = {}
@@ -24,15 +24,16 @@ def get_nested_value(data: list, path: list[int], default: Any = None) -> Any:
24
24
  for i, key in enumerate(path):
25
25
  try:
26
26
  current = current[key]
27
- except (IndexError, TypeError, KeyError) as e:
27
+ except (IndexError, TypeError, KeyError):
28
28
  current_repr = repr(current)
29
29
  if len(current_repr) > 200:
30
30
  current_repr = f"{current_repr[:197]}..."
31
31
 
32
32
  logger.debug(
33
- f"{type(e).__name__}: parsing failed at path {path} (index {i}, key '{key}') "
34
- f"while attempting to get value from `{current_repr}`"
33
+ f"Safe navigation: path {path} ended at index {i} (key '{key}'), "
34
+ f"returning default. Context: {current_repr}"
35
35
  )
36
+
36
37
  return default
37
38
 
38
39
  if current is None and default is not None:
@@ -43,7 +43,7 @@ async def rotate_1psidts(cookies: dict, proxy: str | None = None) -> str:
43
43
 
44
44
  # Check if the cache file was modified in the last minute to avoid 429 Too Many Requests
45
45
  if not (path.is_file() and time.time() - os.path.getmtime(path) <= 60):
46
- async with AsyncClient(http2=True, proxy=proxy) as client:
46
+ async with AsyncClient(proxy=proxy) as client:
47
47
  response = await client.post(
48
48
  url=Endpoint.ROTATE_COOKIES.value,
49
49
  headers=Headers.ROTATE_COOKIES.value,
@@ -33,7 +33,7 @@ async def upload_file(file: str | Path, proxy: str | None = None) -> str:
33
33
  with open(file, "rb") as f:
34
34
  file = f.read()
35
35
 
36
- async with AsyncClient(http2=True, proxy=proxy) as client:
36
+ async with AsyncClient(proxy=proxy) as client:
37
37
  response = await client.post(
38
38
  url=Endpoint.UPLOAD.value,
39
39
  headers=Headers.UPLOAD.value,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.17.1
3
+ Version: 1.17.3
4
4
  Summary: ✨ An elegant async Python wrapper for Google Gemini web app
5
5
  Author: UZQueen
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -676,7 +676,7 @@ Classifier: Programming Language :: Python :: 3.12
676
676
  Requires-Python: >=3.10
677
677
  Description-Content-Type: text/markdown
678
678
  License-File: LICENSE
679
- Requires-Dist: httpx[http2]~=0.28.1
679
+ Requires-Dist: httpx~=0.28.1
680
680
  Requires-Dist: loguru~=0.7.3
681
681
  Requires-Dist: orjson~=3.11.1
682
682
  Requires-Dist: pydantic~=2.12.2
@@ -1,4 +1,4 @@
1
- httpx[http2]~=0.28.1
1
+ httpx~=0.28.1
2
2
  loguru~=0.7.3
3
3
  orjson~=3.11.1
4
4
  pydantic~=2.12.2
File without changes
File without changes
File without changes