gemini-webapi 1.14.4__tar.gz → 1.15.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 (43) hide show
  1. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/.github/workflows/github-release.yml +1 -1
  2. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/.github/workflows/pypi-publish.yml +1 -1
  3. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/PKG-INFO +74 -1
  4. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/README.md +73 -0
  5. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/client.py +7 -7
  6. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/components/gem_mixin.py +140 -3
  7. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/constants.py +2 -0
  8. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi.egg-info/PKG-INFO +74 -1
  9. gemini_webapi-1.15.0/tests/test_gem_mixin.py +88 -0
  10. gemini_webapi-1.14.4/tests/test_gem_mixin.py +0 -33
  11. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/.github/dependabot.yml +0 -0
  12. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/.gitignore +0 -0
  13. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/.vscode/launch.json +0 -0
  14. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/.vscode/settings.json +0 -0
  15. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/LICENSE +0 -0
  16. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/assets/banner.png +0 -0
  17. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/assets/favicon.png +0 -0
  18. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/assets/logo.svg +0 -0
  19. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/assets/sample.pdf +0 -0
  20. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/pyproject.toml +0 -0
  21. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/setup.cfg +0 -0
  22. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/__init__.py +0 -0
  23. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/components/__init__.py +0 -0
  24. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/exceptions.py +0 -0
  25. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/types/__init__.py +0 -0
  26. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/types/candidate.py +0 -0
  27. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/types/gem.py +0 -0
  28. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/types/grpc.py +0 -0
  29. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/types/image.py +0 -0
  30. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/types/modeloutput.py +0 -0
  31. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/utils/__init__.py +0 -0
  32. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/utils/decorators.py +0 -0
  33. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/utils/get_access_token.py +0 -0
  34. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/utils/load_browser_cookies.py +0 -0
  35. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/utils/logger.py +0 -0
  36. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/utils/rotate_1psidts.py +0 -0
  37. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi/utils/upload_file.py +0 -0
  38. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi.egg-info/SOURCES.txt +0 -0
  39. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi.egg-info/dependency_links.txt +0 -0
  40. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi.egg-info/requires.txt +0 -0
  41. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/src/gemini_webapi.egg-info/top_level.txt +0 -0
  42. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/tests/test_client_features.py +0 -0
  43. {gemini_webapi-1.14.4 → gemini_webapi-1.15.0}/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@v4
14
+ - uses: actions/checkout@v5
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@v4
27
+ - uses: actions/checkout@v5
28
28
  - name: Set up Python
29
29
  uses: actions/setup-python@v5
30
30
  with:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.14.4
3
+ Version: 1.15.0
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
@@ -734,6 +734,10 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
734
734
  - [Continue previous conversations](#continue-previous-conversations)
735
735
  - [Select language model](#select-language-model)
736
736
  - [Apply system prompt with Gemini Gems](#apply-system-prompt-with-gemini-gems)
737
+ - [Manage Custom Gems](#manage-custom-gems)
738
+ - [Create a custom gem](#create-a-custom-gem)
739
+ - [Update an existing gem](#update-an-existing-gem)
740
+ - [Delete a custom gem](#delete-a-custom-gem)
737
741
  - [Retrieve model's thought process](#retrieve-models-thought-process)
738
742
  - [Retrieve images in response](#retrieve-images-in-response)
739
743
  - [Generate images with Imagen4](#generate-images-with-imagen4)
@@ -963,6 +967,75 @@ async def main():
963
967
  print(response2)
964
968
  ```
965
969
 
970
+ ### Manage Custom Gems
971
+
972
+ You can create, update, and delete your custom gems programmatically with the API. Note that predefined system gems cannot be modified or deleted.
973
+
974
+ #### Create a custom gem
975
+
976
+ Create a new custom gem with a name, system prompt (instructions), and optional description:
977
+
978
+ ```python
979
+ async def main():
980
+ # Create a new custom gem
981
+ new_gem = await client.create_gem(
982
+ name="Python Tutor",
983
+ prompt="You are a helpful Python programming tutor.",
984
+ description="A specialized gem for Python programming"
985
+ )
986
+
987
+ print(f"Custom gem created: {new_gem}")
988
+
989
+ # Use the newly created gem in a conversation
990
+ response = await client.generate_content(
991
+ "Explain how list comprehensions work in Python",
992
+ gem=new_gem
993
+ )
994
+ print(response.text)
995
+
996
+ asyncio.run(main())
997
+ ```
998
+
999
+ #### Update an existing gem
1000
+
1001
+ > [!NOTE]
1002
+ >
1003
+ > When updating a gem, you must provide all parameters (name, prompt, description) even if you only want to change one of them.
1004
+
1005
+ ```python
1006
+ async def main():
1007
+ # Get a custom gem (assuming you have one named "Python Tutor")
1008
+ await client.fetch_gems()
1009
+ python_tutor = client.gems.get(name="Python Tutor")
1010
+
1011
+ # Update the gem with new instructions
1012
+ updated_gem = await client.update_gem(
1013
+ gem=python_tutor, # Can also pass gem ID string
1014
+ name="Advanced Python Tutor",
1015
+ prompt="You are an expert Python programming tutor.",
1016
+ description="An advanced Python programming assistant"
1017
+ )
1018
+
1019
+ print(f"Custom gem updated: {updated_gem}")
1020
+
1021
+ asyncio.run(main())
1022
+ ```
1023
+
1024
+ #### Delete a custom gem
1025
+
1026
+ ```python
1027
+ async def main():
1028
+ # Get the gem to delete
1029
+ await client.fetch_gems()
1030
+ gem_to_delete = client.gems.get(name="Advanced Python Tutor")
1031
+
1032
+ # Delete the gem
1033
+ await client.delete_gem(gem_to_delete) # Can also pass gem ID string
1034
+ print(f"Custom gem deleted: {gem_to_delete.name}")
1035
+
1036
+ asyncio.run(main())
1037
+ ```
1038
+
966
1039
  ### Retrieve model's thought process
967
1040
 
968
1041
  When using models with thinking capabilities, the model's thought process will be populated in `ModelOutput.thoughts`.
@@ -50,6 +50,10 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
50
50
  - [Continue previous conversations](#continue-previous-conversations)
51
51
  - [Select language model](#select-language-model)
52
52
  - [Apply system prompt with Gemini Gems](#apply-system-prompt-with-gemini-gems)
53
+ - [Manage Custom Gems](#manage-custom-gems)
54
+ - [Create a custom gem](#create-a-custom-gem)
55
+ - [Update an existing gem](#update-an-existing-gem)
56
+ - [Delete a custom gem](#delete-a-custom-gem)
53
57
  - [Retrieve model's thought process](#retrieve-models-thought-process)
54
58
  - [Retrieve images in response](#retrieve-images-in-response)
55
59
  - [Generate images with Imagen4](#generate-images-with-imagen4)
@@ -279,6 +283,75 @@ async def main():
279
283
  print(response2)
280
284
  ```
281
285
 
286
+ ### Manage Custom Gems
287
+
288
+ You can create, update, and delete your custom gems programmatically with the API. Note that predefined system gems cannot be modified or deleted.
289
+
290
+ #### Create a custom gem
291
+
292
+ Create a new custom gem with a name, system prompt (instructions), and optional description:
293
+
294
+ ```python
295
+ async def main():
296
+ # Create a new custom gem
297
+ new_gem = await client.create_gem(
298
+ name="Python Tutor",
299
+ prompt="You are a helpful Python programming tutor.",
300
+ description="A specialized gem for Python programming"
301
+ )
302
+
303
+ print(f"Custom gem created: {new_gem}")
304
+
305
+ # Use the newly created gem in a conversation
306
+ response = await client.generate_content(
307
+ "Explain how list comprehensions work in Python",
308
+ gem=new_gem
309
+ )
310
+ print(response.text)
311
+
312
+ asyncio.run(main())
313
+ ```
314
+
315
+ #### Update an existing gem
316
+
317
+ > [!NOTE]
318
+ >
319
+ > When updating a gem, you must provide all parameters (name, prompt, description) even if you only want to change one of them.
320
+
321
+ ```python
322
+ async def main():
323
+ # Get a custom gem (assuming you have one named "Python Tutor")
324
+ await client.fetch_gems()
325
+ python_tutor = client.gems.get(name="Python Tutor")
326
+
327
+ # Update the gem with new instructions
328
+ updated_gem = await client.update_gem(
329
+ gem=python_tutor, # Can also pass gem ID string
330
+ name="Advanced Python Tutor",
331
+ prompt="You are an expert Python programming tutor.",
332
+ description="An advanced Python programming assistant"
333
+ )
334
+
335
+ print(f"Custom gem updated: {updated_gem}")
336
+
337
+ asyncio.run(main())
338
+ ```
339
+
340
+ #### Delete a custom gem
341
+
342
+ ```python
343
+ async def main():
344
+ # Get the gem to delete
345
+ await client.fetch_gems()
346
+ gem_to_delete = client.gems.get(name="Advanced Python Tutor")
347
+
348
+ # Delete the gem
349
+ await client.delete_gem(gem_to_delete) # Can also pass gem ID string
350
+ print(f"Custom gem deleted: {gem_to_delete.name}")
351
+
352
+ asyncio.run(main())
353
+ ```
354
+
282
355
  ### Retrieve model's thought process
283
356
 
284
357
  When using models with thinking capabilities, the model's thought process will be populated in `ModelOutput.thoughts`.
@@ -292,7 +292,9 @@ class GeminiClient(GemMixin):
292
292
  model = Model.from_name(model)
293
293
 
294
294
  if isinstance(gem, Gem):
295
- gem = gem.id
295
+ gem_id = gem.id
296
+ else:
297
+ gem_id = gem
296
298
 
297
299
  if self.auto_close:
298
300
  await self.reset_close_task()
@@ -325,7 +327,7 @@ class GeminiClient(GemMixin):
325
327
  None,
326
328
  chat and chat.metadata,
327
329
  ]
328
- + (gem and [None] * 16 + [gem] or [])
330
+ + (gem_id and [None] * 16 + [gem_id] or [])
329
331
  ).decode(),
330
332
  ]
331
333
  ).decode(),
@@ -551,12 +553,10 @@ class GeminiClient(GemMixin):
551
553
  "consider setting a higher `timeout` value when initializing GeminiClient."
552
554
  )
553
555
 
556
+ # ? Seems like batch execution will immediately invalidate the current access token,
557
+ # ? causing the next request to fail with 401 Unauthorized.
554
558
  if response.status_code != 200:
555
- logger.debug(
556
- f"Batch execution failed with status code {response.status_code}. "
557
- f"RPC: {', '.join({payload.rpcid.name for payload in payloads})}; "
558
- f"Invalid response: {response.text}"
559
- )
559
+ await self.close()
560
560
  raise APIError(
561
561
  f"Batch execution failed with status code {response.status_code}"
562
562
  )
@@ -131,10 +131,145 @@ class GemMixin:
131
131
 
132
132
  return self._gems
133
133
 
134
+ @running(retry=2)
135
+ async def create_gem(self, name: str, prompt: str, description: str = "") -> Gem:
136
+ """
137
+ Create a new custom gem.
138
+
139
+ Parameters
140
+ ----------
141
+ name: `str`
142
+ Name of the custom gem.
143
+ prompt: `str`
144
+ System instructions for the custom gem.
145
+ description: `str`, optional
146
+ Description of the custom gem (has no effect on the model's behavior).
147
+
148
+ Returns
149
+ -------
150
+ :class:`Gem`
151
+ The created gem.
152
+ """
153
+
154
+ response = await self._batch_execute(
155
+ [
156
+ RPCData(
157
+ rpcid=GRPC.CREATE_GEM,
158
+ payload=json.dumps(
159
+ [
160
+ [
161
+ name,
162
+ description,
163
+ prompt,
164
+ None,
165
+ None,
166
+ None,
167
+ None,
168
+ None,
169
+ 0,
170
+ None,
171
+ 1,
172
+ None,
173
+ None,
174
+ None,
175
+ [],
176
+ ]
177
+ ]
178
+ ).decode(),
179
+ )
180
+ ]
181
+ )
182
+
183
+ try:
184
+ response_json = json.loads(response.text.split("\n")[2])
185
+ gem_id = json.loads(response_json[0][2])[0]
186
+ except Exception:
187
+ await self.close()
188
+ logger.debug(f"Invalid response: {response.text}")
189
+ raise APIError(
190
+ "Failed to create gem. Invalid response data received. Client will try to re-initialize on next request."
191
+ )
192
+
193
+ return Gem(
194
+ id=gem_id,
195
+ name=name,
196
+ description=description,
197
+ prompt=prompt,
198
+ predefined=False,
199
+ )
200
+
201
+ @running(retry=2)
202
+ async def update_gem(
203
+ self, gem: Gem | str, name: str, prompt: str, description: str = ""
204
+ ) -> Gem:
205
+ """
206
+ Update an existing custom gem.
207
+
208
+ Parameters
209
+ ----------
210
+ gem: `Gem | str`
211
+ Gem to update, can be either a `gemini_webapi.types.Gem` object or a gem id string.
212
+ name: `str`
213
+ New name for the custom gem.
214
+ prompt: `str`
215
+ New system instructions for the custom gem.
216
+ description: `str`, optional
217
+ New description of the custom gem (has no effect on the model's behavior).
218
+
219
+ Returns
220
+ -------
221
+ :class:`Gem`
222
+ The updated gem.
223
+ """
224
+
225
+ if isinstance(gem, Gem):
226
+ gem_id = gem.id
227
+ else:
228
+ gem_id = gem
229
+
230
+ await self._batch_execute(
231
+ [
232
+ RPCData(
233
+ rpcid=GRPC.UPDATE_GEM,
234
+ payload=json.dumps(
235
+ [
236
+ gem_id,
237
+ [
238
+ name,
239
+ description,
240
+ prompt,
241
+ None,
242
+ None,
243
+ None,
244
+ None,
245
+ None,
246
+ 0,
247
+ None,
248
+ 1,
249
+ None,
250
+ None,
251
+ None,
252
+ [],
253
+ 0,
254
+ ],
255
+ ]
256
+ ).decode(),
257
+ )
258
+ ]
259
+ )
260
+
261
+ return Gem(
262
+ id=gem_id,
263
+ name=name,
264
+ description=description,
265
+ prompt=prompt,
266
+ predefined=False,
267
+ )
268
+
134
269
  @running(retry=2)
135
270
  async def delete_gem(self, gem: Gem | str, **kwargs) -> None:
136
271
  """
137
- Delete a custom gem from gemini.google.com.
272
+ Delete a custom gem.
138
273
 
139
274
  Parameters
140
275
  ----------
@@ -143,9 +278,11 @@ class GemMixin:
143
278
  """
144
279
 
145
280
  if isinstance(gem, Gem):
146
- gem = gem.id
281
+ gem_id = gem.id
282
+ else:
283
+ gem_id = gem
147
284
 
148
285
  await self._batch_execute(
149
- [RPCData(rpcid=GRPC.DELETE_GEM, payload=[gem])],
286
+ [RPCData(rpcid=GRPC.DELETE_GEM, payload=json.dumps([gem_id]).decode())],
150
287
  **kwargs,
151
288
  )
@@ -20,6 +20,8 @@ class GRPC(StrEnum):
20
20
 
21
21
  # Gem methods
22
22
  LIST_GEMS = "CNgdBe"
23
+ CREATE_GEM = "oMH3Zd"
24
+ UPDATE_GEM = "kHv0Vd"
23
25
  DELETE_GEM = "UXcSJb"
24
26
 
25
27
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.14.4
3
+ Version: 1.15.0
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
@@ -734,6 +734,10 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
734
734
  - [Continue previous conversations](#continue-previous-conversations)
735
735
  - [Select language model](#select-language-model)
736
736
  - [Apply system prompt with Gemini Gems](#apply-system-prompt-with-gemini-gems)
737
+ - [Manage Custom Gems](#manage-custom-gems)
738
+ - [Create a custom gem](#create-a-custom-gem)
739
+ - [Update an existing gem](#update-an-existing-gem)
740
+ - [Delete a custom gem](#delete-a-custom-gem)
737
741
  - [Retrieve model's thought process](#retrieve-models-thought-process)
738
742
  - [Retrieve images in response](#retrieve-images-in-response)
739
743
  - [Generate images with Imagen4](#generate-images-with-imagen4)
@@ -963,6 +967,75 @@ async def main():
963
967
  print(response2)
964
968
  ```
965
969
 
970
+ ### Manage Custom Gems
971
+
972
+ You can create, update, and delete your custom gems programmatically with the API. Note that predefined system gems cannot be modified or deleted.
973
+
974
+ #### Create a custom gem
975
+
976
+ Create a new custom gem with a name, system prompt (instructions), and optional description:
977
+
978
+ ```python
979
+ async def main():
980
+ # Create a new custom gem
981
+ new_gem = await client.create_gem(
982
+ name="Python Tutor",
983
+ prompt="You are a helpful Python programming tutor.",
984
+ description="A specialized gem for Python programming"
985
+ )
986
+
987
+ print(f"Custom gem created: {new_gem}")
988
+
989
+ # Use the newly created gem in a conversation
990
+ response = await client.generate_content(
991
+ "Explain how list comprehensions work in Python",
992
+ gem=new_gem
993
+ )
994
+ print(response.text)
995
+
996
+ asyncio.run(main())
997
+ ```
998
+
999
+ #### Update an existing gem
1000
+
1001
+ > [!NOTE]
1002
+ >
1003
+ > When updating a gem, you must provide all parameters (name, prompt, description) even if you only want to change one of them.
1004
+
1005
+ ```python
1006
+ async def main():
1007
+ # Get a custom gem (assuming you have one named "Python Tutor")
1008
+ await client.fetch_gems()
1009
+ python_tutor = client.gems.get(name="Python Tutor")
1010
+
1011
+ # Update the gem with new instructions
1012
+ updated_gem = await client.update_gem(
1013
+ gem=python_tutor, # Can also pass gem ID string
1014
+ name="Advanced Python Tutor",
1015
+ prompt="You are an expert Python programming tutor.",
1016
+ description="An advanced Python programming assistant"
1017
+ )
1018
+
1019
+ print(f"Custom gem updated: {updated_gem}")
1020
+
1021
+ asyncio.run(main())
1022
+ ```
1023
+
1024
+ #### Delete a custom gem
1025
+
1026
+ ```python
1027
+ async def main():
1028
+ # Get the gem to delete
1029
+ await client.fetch_gems()
1030
+ gem_to_delete = client.gems.get(name="Advanced Python Tutor")
1031
+
1032
+ # Delete the gem
1033
+ await client.delete_gem(gem_to_delete) # Can also pass gem ID string
1034
+ print(f"Custom gem deleted: {gem_to_delete.name}")
1035
+
1036
+ asyncio.run(main())
1037
+ ```
1038
+
966
1039
  ### Retrieve model's thought process
967
1040
 
968
1041
  When using models with thinking capabilities, the model's thought process will be populated in `ModelOutput.thoughts`.
@@ -0,0 +1,88 @@
1
+ import os
2
+ import random
3
+ import unittest
4
+ import logging
5
+
6
+ from gemini_webapi import GeminiClient, set_log_level, logger
7
+ from gemini_webapi.exceptions import AuthError
8
+
9
+ logging.getLogger("asyncio").setLevel(logging.ERROR)
10
+ set_log_level("DEBUG")
11
+
12
+
13
+ class TestGemMixin(unittest.IsolatedAsyncioTestCase):
14
+ async def asyncSetUp(self):
15
+ self.geminiclient = GeminiClient(
16
+ os.getenv("SECURE_1PSID"), os.getenv("SECURE_1PSIDTS"), verify=False
17
+ )
18
+
19
+ try:
20
+ await self.geminiclient.init(timeout=60, auto_refresh=False)
21
+ except AuthError as e:
22
+ self.skipTest(e)
23
+
24
+ @logger.catch(reraise=True)
25
+ async def test_fetch_gems(self):
26
+ await self.geminiclient.fetch_gems(include_hidden=True)
27
+ gems = self.geminiclient.gems
28
+ self.assertTrue(len(gems.filter(predefined=True)) > 0)
29
+ for gem in gems:
30
+ logger.debug(gem.name)
31
+
32
+ custom_gems = gems.filter(predefined=False)
33
+ if custom_gems:
34
+ logger.debug(f"Found {len(custom_gems)} custom gems:")
35
+ for gem in custom_gems:
36
+ logger.debug(gem)
37
+
38
+ @logger.catch(reraise=True)
39
+ async def test_create_gem(self):
40
+ gem = await self.geminiclient.create_gem(
41
+ name="Test Gem",
42
+ prompt="Gemini API has launched creating gem functionality on Aug 1st, 2025",
43
+ description="This gem is used for testing the functionality of Gemini API",
44
+ )
45
+ logger.debug(f"Gem created: {gem}")
46
+
47
+ @logger.catch(reraise=True)
48
+ async def test_update_gem(self):
49
+ await self.geminiclient.fetch_gems()
50
+ custom_gems = self.geminiclient.gems.filter(predefined=False)
51
+ if not custom_gems:
52
+ self.skipTest("No custom gems available to update.")
53
+
54
+ last_created_gem = next(iter(custom_gems.values()))
55
+ randint = random.randint(0, 100)
56
+ updated_gem = await self.geminiclient.update_gem(
57
+ last_created_gem.id,
58
+ name="Updated Test Gem",
59
+ prompt="Updated prompt for the gem.",
60
+ description=f"{randint}",
61
+ )
62
+ logger.debug(f"Gem updated: {updated_gem}")
63
+
64
+ await self.geminiclient.fetch_gems()
65
+ custom_gems = self.geminiclient.gems.filter(predefined=False)
66
+ last_created_gem = next(iter(custom_gems.values()))
67
+ self.assertEqual(last_created_gem.description, updated_gem.description)
68
+
69
+ @logger.catch(reraise=True)
70
+ async def test_delete_gem(self):
71
+ await self.geminiclient.fetch_gems()
72
+ custom_gems = self.geminiclient.gems.filter(predefined=False)
73
+ total_before_deletion = len(custom_gems)
74
+ if total_before_deletion == 0:
75
+ self.skipTest("No custom gems available to delete.")
76
+
77
+ last_created_gem = next(iter(custom_gems.values()))
78
+ await self.geminiclient.delete_gem(last_created_gem.id)
79
+ logger.debug(f"Gem deleted: {last_created_gem}")
80
+
81
+ await self.geminiclient.fetch_gems()
82
+ custom_gems = self.geminiclient.gems.filter(predefined=False)
83
+ total_after_deletion = len(custom_gems)
84
+ self.assertEqual(total_after_deletion, total_before_deletion - 1)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ unittest.main()
@@ -1,33 +0,0 @@
1
- import os
2
- import unittest
3
- import logging
4
-
5
- from gemini_webapi import GeminiClient, set_log_level, logger
6
- from gemini_webapi.exceptions import AuthError
7
-
8
- logging.getLogger("asyncio").setLevel(logging.ERROR)
9
- set_log_level("DEBUG")
10
-
11
-
12
- class TestGemMixin(unittest.IsolatedAsyncioTestCase):
13
- async def asyncSetUp(self):
14
- self.geminiclient = GeminiClient(
15
- os.getenv("SECURE_1PSID"), os.getenv("SECURE_1PSIDTS"), verify=False
16
- )
17
-
18
- try:
19
- await self.geminiclient.init(timeout=60, auto_refresh=False)
20
- except AuthError as e:
21
- self.skipTest(e)
22
-
23
- @logger.catch(reraise=True)
24
- async def test_fetch_gems(self):
25
- await self.geminiclient.fetch_gems(include_hidden=True)
26
- gems = self.geminiclient.gems
27
- self.assertTrue(len(gems.filter(predefined=True)) > 0)
28
- for gem in gems:
29
- logger.debug(gem.name)
30
-
31
-
32
- if __name__ == "__main__":
33
- unittest.main()
File without changes
File without changes