gemini-webapi 1.14.3__py3-none-any.whl → 1.14.4__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.
gemini_webapi/client.py CHANGED
@@ -1,6 +1,4 @@
1
1
  import asyncio
2
- import functools
3
- import itertools
4
2
  import re
5
3
  from asyncio import Task
6
4
  from pathlib import Path
@@ -9,7 +7,8 @@ from typing import Any, Optional
9
7
  import orjson as json
10
8
  from httpx import AsyncClient, ReadTimeout, Response
11
9
 
12
- from .constants import Endpoint, ErrorCode, Headers, Model, GRPC
10
+ from .components import GemMixin
11
+ from .constants import Endpoint, ErrorCode, Headers, Model
13
12
  from .exceptions import (
14
13
  AuthError,
15
14
  APIError,
@@ -26,7 +25,6 @@ from .types import (
26
25
  Candidate,
27
26
  ModelOutput,
28
27
  Gem,
29
- GemJar,
30
28
  RPCData,
31
29
  )
32
30
  from .utils import (
@@ -35,60 +33,13 @@ from .utils import (
35
33
  rotate_1psidts,
36
34
  get_access_token,
37
35
  load_browser_cookies,
36
+ running,
38
37
  rotate_tasks,
39
38
  logger,
40
39
  )
41
40
 
42
41
 
43
- def running(retry: int = 0) -> callable:
44
- """
45
- Decorator to check if client is running before making a request.
46
-
47
- Parameters
48
- ----------
49
- retry: `int`, optional
50
- Max number of retries when `gemini_webapi.APIError` is raised.
51
- """
52
-
53
- def decorator(func):
54
- @functools.wraps(func)
55
- async def wrapper(client: "GeminiClient", *args, retry=retry, **kwargs):
56
- try:
57
- if not client.running:
58
- await client.init(
59
- timeout=client.timeout,
60
- auto_close=client.auto_close,
61
- close_delay=client.close_delay,
62
- auto_refresh=client.auto_refresh,
63
- refresh_interval=client.refresh_interval,
64
- verbose=False,
65
- )
66
- if client.running:
67
- return await func(client, *args, **kwargs)
68
-
69
- # Should not reach here
70
- raise APIError(
71
- f"Invalid function call: GeminiClient.{func.__name__}. Client initialization failed."
72
- )
73
- else:
74
- return await func(client, *args, **kwargs)
75
- except APIError as e:
76
- # Image generation takes too long, only retry once
77
- if isinstance(e, ImageGenerationError):
78
- retry = min(1, retry)
79
-
80
- if retry > 0:
81
- await asyncio.sleep(1)
82
- return await wrapper(client, *args, retry=retry - 1, **kwargs)
83
-
84
- raise
85
-
86
- return wrapper
87
-
88
- return decorator
89
-
90
-
91
- class GeminiClient:
42
+ class GeminiClient(GemMixin):
92
43
  """
93
44
  Async httpx client interface for gemini.google.com.
94
45
 
@@ -125,7 +76,7 @@ class GeminiClient:
125
76
  "close_task",
126
77
  "auto_refresh",
127
78
  "refresh_interval",
128
- "_gems",
79
+ "_gems", # From GemMixin
129
80
  "kwargs",
130
81
  ]
131
82
 
@@ -136,6 +87,7 @@ class GeminiClient:
136
87
  proxy: str | None = None,
137
88
  **kwargs,
138
89
  ):
90
+ super().__init__()
139
91
  self.cookies = {}
140
92
  self.proxy = proxy
141
93
  self.running: bool = False
@@ -147,7 +99,6 @@ class GeminiClient:
147
99
  self.close_task: Task | None = None
148
100
  self.auto_refresh: bool = True
149
101
  self.refresh_interval: float = 540
150
- self._gems: GemJar | None = None
151
102
  self.kwargs = kwargs
152
103
 
153
104
  # Validate cookies
@@ -285,119 +236,6 @@ class GeminiClient:
285
236
  self.cookies["__Secure-1PSIDTS"] = new_1psidts
286
237
  await asyncio.sleep(self.refresh_interval)
287
238
 
288
- @property
289
- def gems(self) -> GemJar:
290
- """
291
- Returns a `GemJar` object containing cached gems.
292
- Only available after calling `GeminiClient.fetch_gems()`.
293
-
294
- Returns
295
- -------
296
- :class:`GemJar`
297
- Refer to `gemini_webapi.types.GemJar`.
298
-
299
- Raises
300
- ------
301
- `RuntimeError`
302
- If `GeminiClient.fetch_gems()` has not been called before accessing this property.
303
- """
304
-
305
- if self._gems is None:
306
- raise RuntimeError(
307
- "Gems not fetched yet. Call `GeminiClient.fetch_gems()` method to fetch gems from gemini.google.com."
308
- )
309
-
310
- return self._gems
311
-
312
- async def fetch_gems(self, include_hidden: bool = False, **kwargs) -> GemJar:
313
- """
314
- Get a list of available gems from gemini, including system predefined gems and user-created custom gems.
315
-
316
- Note that network request will be sent every time this method is called.
317
- Once the gems are fetched, they will be cached and accessible via `GeminiClient.gems` property.
318
-
319
- Parameters
320
- ----------
321
- include_hidden: `bool`, optional
322
- There are some predefined gems that by default are not shown to users (and therefore may not work properly).
323
- Set this parameter to `True` to include them in the fetched gem list.
324
-
325
- Returns
326
- -------
327
- :class:`GemJar`
328
- Refer to `gemini_webapi.types.GemJar`.
329
- """
330
-
331
- response = await self._batch_execute(
332
- [
333
- RPCData(
334
- rpcid=GRPC.LIST_GEMS,
335
- payload="[4]" if include_hidden else "[3]",
336
- identifier="system",
337
- ),
338
- RPCData(
339
- rpcid=GRPC.LIST_GEMS,
340
- payload="[2]",
341
- identifier="custom",
342
- ),
343
- ],
344
- **kwargs,
345
- )
346
-
347
- try:
348
- response_json = json.loads(response.text.split("\n")[2])
349
-
350
- predefined_gems, custom_gems = [], []
351
-
352
- for part in response_json:
353
- if part[-1] == "system":
354
- predefined_gems = json.loads(part[2])[2]
355
- elif part[-1] == "custom":
356
- if custom_gems_container := json.loads(part[2]):
357
- custom_gems = custom_gems_container[2]
358
-
359
- if not predefined_gems and not custom_gems:
360
- raise Exception
361
- except Exception:
362
- await self.close()
363
- logger.debug(f"Invalid response: {response.text}")
364
- raise APIError(
365
- "Failed to fetch gems. Invalid response data received. Client will try to re-initialize on next request."
366
- )
367
-
368
- self._gems = GemJar(
369
- itertools.chain(
370
- (
371
- (
372
- gem[0],
373
- Gem(
374
- id=gem[0],
375
- name=gem[1][0],
376
- description=gem[1][1],
377
- prompt=gem[2] and gem[2][0] or None,
378
- predefined=True,
379
- ),
380
- )
381
- for gem in predefined_gems
382
- ),
383
- (
384
- (
385
- gem[0],
386
- Gem(
387
- id=gem[0],
388
- name=gem[1][0],
389
- description=gem[1][1],
390
- prompt=gem[2] and gem[2][0] or None,
391
- predefined=False,
392
- ),
393
- )
394
- for gem in custom_gems
395
- ),
396
- )
397
- )
398
-
399
- return self._gems
400
-
401
239
  @running(retry=2)
402
240
  async def generate_content(
403
241
  self,
@@ -611,10 +449,21 @@ class GeminiClient:
611
449
  generated_images = [
612
450
  GeneratedImage(
613
451
  url=generated_image[0][3][3],
614
- title=f"[Generated Image {generated_image[3][6]}]",
615
- alt=len(generated_image[3][5]) > image_index
616
- and generated_image[3][5][image_index]
617
- or generated_image[3][5][0],
452
+ title=(
453
+ f"[Generated Image {generated_image[3][6]}]"
454
+ if generated_image[3][6]
455
+ else "[Generated Image]"
456
+ ),
457
+ alt=(
458
+ generated_image[3][5][image_index]
459
+ if generated_image[3][5]
460
+ and len(generated_image[3][5]) > image_index
461
+ else (
462
+ generated_image[3][5][0]
463
+ if generated_image[3][5]
464
+ else ""
465
+ )
466
+ ),
618
467
  proxy=self.proxy,
619
468
  cookies=self.cookies,
620
469
  )
@@ -667,7 +516,6 @@ class GeminiClient:
667
516
 
668
517
  return ChatSession(geminiclient=self, **kwargs)
669
518
 
670
- @running(retry=2)
671
519
  async def _batch_execute(self, payloads: list[RPCData], **kwargs) -> Response:
672
520
  """
673
521
  Execute a batch of requests to Gemini API.
@@ -0,0 +1,3 @@
1
+ # flake8: noqa
2
+
3
+ from .gem_mixin import GemMixin
@@ -0,0 +1,151 @@
1
+ import itertools
2
+
3
+ import orjson as json
4
+
5
+ from ..constants import GRPC
6
+ from ..exceptions import APIError
7
+ from ..types import Gem, GemJar, RPCData
8
+ from ..utils import running, logger
9
+
10
+
11
+ class GemMixin:
12
+ """
13
+ Mixin class providing gem-related functionality for GeminiClient.
14
+ """
15
+
16
+ def __init__(self, *args, **kwargs):
17
+ super().__init__(*args, **kwargs)
18
+ self._gems: GemJar | None = None
19
+
20
+ @property
21
+ def gems(self) -> GemJar:
22
+ """
23
+ Returns a `GemJar` object containing cached gems.
24
+ Only available after calling `GeminiClient.fetch_gems()`.
25
+
26
+ Returns
27
+ -------
28
+ :class:`GemJar`
29
+ Refer to `gemini_webapi.types.GemJar`.
30
+
31
+ Raises
32
+ ------
33
+ `RuntimeError`
34
+ If `GeminiClient.fetch_gems()` has not been called before accessing this property.
35
+ """
36
+
37
+ if self._gems is None:
38
+ raise RuntimeError(
39
+ "Gems not fetched yet. Call `GeminiClient.fetch_gems()` method to fetch gems from gemini.google.com."
40
+ )
41
+
42
+ return self._gems
43
+
44
+ @running(retry=2)
45
+ async def fetch_gems(self, include_hidden: bool = False, **kwargs) -> GemJar:
46
+ """
47
+ Get a list of available gems from gemini, including system predefined gems and user-created custom gems.
48
+
49
+ Note that network request will be sent every time this method is called.
50
+ Once the gems are fetched, they will be cached and accessible via `GeminiClient.gems` property.
51
+
52
+ Parameters
53
+ ----------
54
+ include_hidden: `bool`, optional
55
+ There are some predefined gems that by default are not shown to users (and therefore may not work properly).
56
+ Set this parameter to `True` to include them in the fetched gem list.
57
+
58
+ Returns
59
+ -------
60
+ :class:`GemJar`
61
+ Refer to `gemini_webapi.types.GemJar`.
62
+ """
63
+
64
+ response = await self._batch_execute(
65
+ [
66
+ RPCData(
67
+ rpcid=GRPC.LIST_GEMS,
68
+ payload="[4]" if include_hidden else "[3]",
69
+ identifier="system",
70
+ ),
71
+ RPCData(
72
+ rpcid=GRPC.LIST_GEMS,
73
+ payload="[2]",
74
+ identifier="custom",
75
+ ),
76
+ ],
77
+ **kwargs,
78
+ )
79
+
80
+ try:
81
+ response_json = json.loads(response.text.split("\n")[2])
82
+
83
+ predefined_gems, custom_gems = [], []
84
+
85
+ for part in response_json:
86
+ if part[-1] == "system":
87
+ predefined_gems = json.loads(part[2])[2]
88
+ elif part[-1] == "custom":
89
+ if custom_gems_container := json.loads(part[2]):
90
+ custom_gems = custom_gems_container[2]
91
+
92
+ if not predefined_gems and not custom_gems:
93
+ raise Exception
94
+ except Exception:
95
+ await self.close()
96
+ logger.debug(f"Invalid response: {response.text}")
97
+ raise APIError(
98
+ "Failed to fetch gems. Invalid response data received. Client will try to re-initialize on next request."
99
+ )
100
+
101
+ self._gems = GemJar(
102
+ itertools.chain(
103
+ (
104
+ (
105
+ gem[0],
106
+ Gem(
107
+ id=gem[0],
108
+ name=gem[1][0],
109
+ description=gem[1][1],
110
+ prompt=gem[2] and gem[2][0] or None,
111
+ predefined=True,
112
+ ),
113
+ )
114
+ for gem in predefined_gems
115
+ ),
116
+ (
117
+ (
118
+ gem[0],
119
+ Gem(
120
+ id=gem[0],
121
+ name=gem[1][0],
122
+ description=gem[1][1],
123
+ prompt=gem[2] and gem[2][0] or None,
124
+ predefined=False,
125
+ ),
126
+ )
127
+ for gem in custom_gems
128
+ ),
129
+ )
130
+ )
131
+
132
+ return self._gems
133
+
134
+ @running(retry=2)
135
+ async def delete_gem(self, gem: Gem | str, **kwargs) -> None:
136
+ """
137
+ Delete a custom gem from gemini.google.com.
138
+
139
+ Parameters
140
+ ----------
141
+ gem: `Gem | str`
142
+ Gem to delete, can be either a `gemini_webapi.types.Gem` object or a gem id string.
143
+ """
144
+
145
+ if isinstance(gem, Gem):
146
+ gem = gem.id
147
+
148
+ await self._batch_execute(
149
+ [RPCData(rpcid=GRPC.DELETE_GEM, payload=[gem])],
150
+ **kwargs,
151
+ )
@@ -20,6 +20,7 @@ class GRPC(StrEnum):
20
20
 
21
21
  # Gem methods
22
22
  LIST_GEMS = "CNgdBe"
23
+ DELETE_GEM = "UXcSJb"
23
24
 
24
25
 
25
26
  class Headers(Enum):
@@ -30,10 +30,10 @@ class Image(BaseModel):
30
30
  proxy: str | None = None
31
31
 
32
32
  def __str__(self):
33
- return f"{self.title}({self.url}) - {self.alt}"
34
-
35
- def __repr__(self):
36
- return f"Image(title='{self.title}', url='{len(self.url) <= 20 and self.url or self.url[:8] + '...' + self.url[-12:]}', alt='{self.alt}')"
33
+ return (
34
+ f"Image(title='{self.title}', alt='{self.alt}', "
35
+ f"url='{len(self.url) <= 20 and self.url or self.url[:8] + '...' + self.url[-12:]}')"
36
+ )
37
37
 
38
38
  async def save(
39
39
  self,
@@ -2,6 +2,7 @@
2
2
 
3
3
  from asyncio import Task
4
4
 
5
+ from .decorators import running
5
6
  from .upload_file import upload_file, parse_file_name
6
7
  from .rotate_1psidts import rotate_1psidts
7
8
  from .get_access_token import get_access_token
@@ -0,0 +1,52 @@
1
+ import asyncio
2
+ import functools
3
+
4
+ from ..exceptions import APIError, ImageGenerationError
5
+
6
+
7
+ def running(retry: int = 0) -> callable:
8
+ """
9
+ Decorator to check if GeminiClient is running before making a request.
10
+
11
+ Parameters
12
+ ----------
13
+ retry: `int`, optional
14
+ Max number of retries when `gemini_webapi.APIError` is raised.
15
+ """
16
+
17
+ def decorator(func):
18
+ @functools.wraps(func)
19
+ async def wrapper(client, *args, retry=retry, **kwargs):
20
+ try:
21
+ if not client.running:
22
+ await client.init(
23
+ timeout=client.timeout,
24
+ auto_close=client.auto_close,
25
+ close_delay=client.close_delay,
26
+ auto_refresh=client.auto_refresh,
27
+ refresh_interval=client.refresh_interval,
28
+ verbose=False,
29
+ )
30
+ if client.running:
31
+ return await func(client, *args, **kwargs)
32
+
33
+ # Should not reach here
34
+ raise APIError(
35
+ f"Invalid function call: GeminiClient.{func.__name__}. Client initialization failed."
36
+ )
37
+ else:
38
+ return await func(client, *args, **kwargs)
39
+ except APIError as e:
40
+ # Image generation takes too long, only retry once
41
+ if isinstance(e, ImageGenerationError):
42
+ retry = min(1, retry)
43
+
44
+ if retry > 0:
45
+ await asyncio.sleep(1)
46
+ return await wrapper(client, *args, retry=retry - 1, **kwargs)
47
+
48
+ raise
49
+
50
+ return wrapper
51
+
52
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.14.3
3
+ Version: 1.14.4
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
@@ -678,7 +678,7 @@ Description-Content-Type: text/markdown
678
678
  License-File: LICENSE
679
679
  Requires-Dist: httpx[http2]~=0.28.1
680
680
  Requires-Dist: loguru~=0.7.3
681
- Requires-Dist: orjson~=3.10.18
681
+ Requires-Dist: orjson~=3.11.1
682
682
  Requires-Dist: pydantic~=2.11.5
683
683
  Dynamic: license-file
684
684
 
@@ -1,21 +1,24 @@
1
1
  gemini_webapi/__init__.py,sha256=7ELCiUoI10ea3daeJxnv0UwqLVKpM7rxsgOZsPMstO8,150
2
- gemini_webapi/client.py,sha256=XrPG5LtJDYpEOMyjL8zxRO9ExFwp9EO5UpsNxhouSvk,31820
3
- gemini_webapi/constants.py,sha256=M2fbQSSVhF0ROv_9CTR4n8sDOujJmLRxwA6ARQ0Uf7k,2519
2
+ gemini_webapi/client.py,sha256=nnInjBRsBLLLcc07f2oFv4HjwMXXCBx5Y_RMwo8NYtM,26891
3
+ gemini_webapi/constants.py,sha256=Ulj38JVv8lPW7GZRTb25sLM2utuY-q7aDTrgm-bXbrU,2545
4
4
  gemini_webapi/exceptions.py,sha256=qkXrIpr0L7LtGbq3VcTO8D1xZ50pJtt0dDRp5I3uDSg,1038
5
+ gemini_webapi/components/__init__.py,sha256=wolxuAJJ32-jmHOKgpsesexP7hXea1JMo5vI52wysTI,48
6
+ gemini_webapi/components/gem_mixin.py,sha256=HUrTBHQC9Lzc53Le2droi_UoFxTOZMRLMbn6MRdcHfI,4727
5
7
  gemini_webapi/types/__init__.py,sha256=1DU4JEw2KHQJbtOgOuvoEXtzQCMwAkyo0koSFVdT9JY,192
6
8
  gemini_webapi/types/candidate.py,sha256=67BhY75toE5mVuB21cmHcTFtw332V_KmCjr3-9VTbJo,1477
7
9
  gemini_webapi/types/gem.py,sha256=3Ppjq9V22Zp4Lb9a9ZnDviDKQpfSQf8UZxqOEjeEWd4,4070
8
10
  gemini_webapi/types/grpc.py,sha256=S64h1oeC7ZJC50kmS_C2CQ7WVTanhJ4kqTFx5ZYayXI,917
9
- gemini_webapi/types/image.py,sha256=xCPaMzstIM5C09ZkJ0545vVPAsrF3y89XnRJrEWGz5k,5436
11
+ gemini_webapi/types/image.py,sha256=NhW1QY2Agzb6UbrIjZLnqxqukabDSGbmoCsVguzko10,5395
10
12
  gemini_webapi/types/modeloutput.py,sha256=h07kQOkL5r-oPLvZ59uVtO1eP4FGy5ZpzuYQzAeQdr8,1196
11
- gemini_webapi/utils/__init__.py,sha256=cJ9HQYxr8l0CsY61TFlho-5DdPxCaEOpRfjxPX-eils,320
13
+ gemini_webapi/utils/__init__.py,sha256=RIU1MBqYNjih0XMMt7wCxAA-X9KVF53nDbAjaxaGTo8,352
14
+ gemini_webapi/utils/decorators.py,sha256=AuY6sU1_6_ZqeL92dTAi3eRPQ7zubB5VuBZEiz16aBM,1741
12
15
  gemini_webapi/utils/get_access_token.py,sha256=eNn1omFO41wWXco1eM-KXR2CEi0Tb-chlph7H-PCNjg,6137
13
16
  gemini_webapi/utils/load_browser_cookies.py,sha256=A5n_VsB7Rm8ck5lpy856UNJEhv30l3dvQ3j0g3ln1fE,1535
14
17
  gemini_webapi/utils/logger.py,sha256=0VcxhVLhHBRDQutNCpapP1y_MhPoQ2ud1uIFLqxC3Z8,958
15
18
  gemini_webapi/utils/rotate_1psidts.py,sha256=NyQ9OYPLBOcvpc8bodvEYDIVFrsYN0kdfc831lPEctM,1680
16
19
  gemini_webapi/utils/upload_file.py,sha256=SJOMr6kryK_ClrKmqI96fqZBNFOMPsyAvFINAGAU3rk,1468
17
- gemini_webapi-1.14.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
18
- gemini_webapi-1.14.3.dist-info/METADATA,sha256=GNbHaHKIyQD0t3_MhkaxjHufYV7B5S1X5YUDG9-8e5o,59157
19
- gemini_webapi-1.14.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- gemini_webapi-1.14.3.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
21
- gemini_webapi-1.14.3.dist-info/RECORD,,
20
+ gemini_webapi-1.14.4.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
21
+ gemini_webapi-1.14.4.dist-info/METADATA,sha256=-d9j4G1z2fh6ohTAOeNW0I2O2HzlG0D48TKH67KCXFE,59156
22
+ gemini_webapi-1.14.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ gemini_webapi-1.14.4.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
24
+ gemini_webapi-1.14.4.dist-info/RECORD,,