gemini-webapi 1.9.0__tar.gz → 1.10.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.
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/PKG-INFO +32 -38
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/README.md +29 -36
- gemini_webapi-1.10.0/assets/sample.pdf +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/client.py +23 -23
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/constants.py +4 -4
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/utils/__init__.py +1 -1
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/utils/upload_file.py +27 -6
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi.egg-info/PKG-INFO +32 -38
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi.egg-info/SOURCES.txt +1 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/tests/test_client_features.py +9 -22
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/.github/dependabot.yml +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/.github/workflows/github-release.yml +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/.github/workflows/pypi-publish.yml +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/.gitignore +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/.vscode/launch.json +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/.vscode/settings.json +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/LICENSE +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/assets/banner.png +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/assets/favicon.png +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/assets/logo.svg +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/pyproject.toml +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/setup.cfg +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/__init__.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/exceptions.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/types/__init__.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/types/candidate.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/types/image.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/types/modeloutput.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/utils/get_access_token.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/utils/load_browser_cookies.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/utils/logger.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/utils/rotate_1psidts.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi.egg-info/dependency_links.txt +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi.egg-info/requires.txt +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi.egg-info/top_level.txt +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/tests/test_rotate_cookies.py +0 -0
- {gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/tests/test_save_image.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: gemini-webapi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.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
|
|
@@ -679,6 +679,7 @@ License-File: LICENSE
|
|
|
679
679
|
Requires-Dist: httpx[http2]~=0.28.1
|
|
680
680
|
Requires-Dist: pydantic~=2.10.5
|
|
681
681
|
Requires-Dist: loguru~=0.7.3
|
|
682
|
+
Dynamic: license-file
|
|
682
683
|
|
|
683
684
|
<p align="center">
|
|
684
685
|
<img src="https://raw.githubusercontent.com/HanaokaYuzu/Gemini-API/master/assets/banner.png" width="55%" alt="Gemini Banner" align="center">
|
|
@@ -711,7 +712,7 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
|
|
|
711
712
|
## Features
|
|
712
713
|
|
|
713
714
|
- **Persistent Cookies** - Automatically refreshes cookies in background. Optimized for always-on services.
|
|
714
|
-
- **
|
|
715
|
+
- **Image Generation** - Natively supports generating and modifying images with natural language.
|
|
715
716
|
- **Extension Support** - Supports generating contents with [Gemini extensions](https://gemini.google.com/extensions) on, like YouTube and Gmail.
|
|
716
717
|
- **Classified Outputs** - Automatically categorizes texts, web images and AI generated images in the response.
|
|
717
718
|
- **Official Flavor** - Provides a simple and elegant interface inspired by [Google Generative AI](https://ai.google.dev/tutorials/python_quickstart)'s official API.
|
|
@@ -727,13 +728,12 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
|
|
|
727
728
|
- [Initialization](#initialization)
|
|
728
729
|
- [Select language model](#select-language-model)
|
|
729
730
|
- [Generate contents from text](#generate-contents-from-text)
|
|
730
|
-
- [Generate contents
|
|
731
|
+
- [Generate contents with files](#generate-contents-with-files)
|
|
731
732
|
- [Conversations across multiple turns](#conversations-across-multiple-turns)
|
|
732
733
|
- [Continue previous conversations](#continue-previous-conversations)
|
|
733
734
|
- [Retrieve model's thought process](#retrieve-models-thought-process)
|
|
734
735
|
- [Retrieve images in response](#retrieve-images-in-response)
|
|
735
|
-
- [Generate images with
|
|
736
|
-
- [Save images to local files](#save-images-to-local-files)
|
|
736
|
+
- [Generate images with Imagen3](#generate-images-with-imagen3)
|
|
737
737
|
- [Generate contents with Gemini extensions](#generate-contents-with-gemini-extensions)
|
|
738
738
|
- [Check and switch to other reply candidates](#check-and-switch-to-other-reply-candidates)
|
|
739
739
|
- [Control log level](#control-log-level)
|
|
@@ -867,15 +867,15 @@ asyncio.run(main())
|
|
|
867
867
|
>
|
|
868
868
|
> Simply use `print(response)` to get the same output if you just want to see the response text
|
|
869
869
|
|
|
870
|
-
### Generate contents
|
|
870
|
+
### Generate contents with files
|
|
871
871
|
|
|
872
|
-
Gemini supports
|
|
872
|
+
Gemini supports file input, including images and documents. Optionally, you can pass files as a list of paths in `str` or `pathlib.Path` to `GeminiClient.generate_content` together with text prompt.
|
|
873
873
|
|
|
874
874
|
```python
|
|
875
875
|
async def main():
|
|
876
876
|
response = await client.generate_content(
|
|
877
|
-
"
|
|
878
|
-
|
|
877
|
+
"Introduce the contents of these two files. Is there any connection between them?",
|
|
878
|
+
files=["assets/sample.pdf", Path("assets/banner.png")],
|
|
879
879
|
)
|
|
880
880
|
print(response.text)
|
|
881
881
|
|
|
@@ -889,9 +889,15 @@ If you want to keep conversation continuous, please use `GeminiClient.start_chat
|
|
|
889
889
|
```python
|
|
890
890
|
async def main():
|
|
891
891
|
chat = client.start_chat()
|
|
892
|
-
response1 = await chat.send_message(
|
|
893
|
-
|
|
894
|
-
|
|
892
|
+
response1 = await chat.send_message(
|
|
893
|
+
"Introduce the contents of these two files. Is there any connection between them?",
|
|
894
|
+
files=["assets/sample.pdf", Path("assets/banner.png")],
|
|
895
|
+
)
|
|
896
|
+
print(response1.text)
|
|
897
|
+
response2 = await chat.send_message(
|
|
898
|
+
"Use image generation tool to modify the banner with another font and design."
|
|
899
|
+
)
|
|
900
|
+
print(response2.text, response2.images, sep="\n\n----------------------------------\n\n")
|
|
895
901
|
|
|
896
902
|
asyncio.run(main())
|
|
897
903
|
```
|
|
@@ -949,24 +955,27 @@ async def main():
|
|
|
949
955
|
asyncio.run(main())
|
|
950
956
|
```
|
|
951
957
|
|
|
952
|
-
### Generate images with
|
|
958
|
+
### Generate images with Imagen3
|
|
953
959
|
|
|
954
|
-
|
|
960
|
+
You can ask Gemini to generate and modify images with Imagen3, Google's latest AI image generator, simply by natural language.
|
|
955
961
|
|
|
956
962
|
> [!IMPORTANT]
|
|
957
963
|
>
|
|
958
|
-
> Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of
|
|
964
|
+
> Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of March 19th, 2025):
|
|
959
965
|
>
|
|
960
|
-
> > Image generation in Gemini Apps is available in most countries, except in the European Economic Area (EEA), Switzerland, and the UK. It’s only available for **English prompts**.
|
|
961
|
-
> >
|
|
962
966
|
> > This feature’s availability in any specific Gemini app is also limited to the supported languages and countries of that app.
|
|
963
967
|
> >
|
|
964
968
|
> > For now, this feature isn’t available to users under 18.
|
|
969
|
+
> >
|
|
970
|
+
> > To use this feature, you must be signed in to Gemini Apps.
|
|
971
|
+
|
|
972
|
+
You can save images returned from Gemini to local by calling `Image.save()`. Optionally, you can specify the file path and file name by passing `path` and `filename` arguments to the function and skip images with invalid file names by passing `skip_invalid_filename=True`. Works for both `WebImage` and `GeneratedImage`.
|
|
965
973
|
|
|
966
974
|
```python
|
|
967
975
|
async def main():
|
|
968
976
|
response = await client.generate_content("Generate some pictures of cats")
|
|
969
|
-
for image in response.images:
|
|
977
|
+
for i, image in enumerate(response.images):
|
|
978
|
+
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
|
|
970
979
|
print(image, "\n\n----------------------------------\n")
|
|
971
980
|
|
|
972
981
|
asyncio.run(main())
|
|
@@ -976,32 +985,17 @@ asyncio.run(main())
|
|
|
976
985
|
>
|
|
977
986
|
> by default, when asked to send images (like the previous example), Gemini will send images fetched from web instead of generating images with AI model, unless you specifically require to "generate" images in your prompt. In this package, web images and generated images are treated differently as `WebImage` and `GeneratedImage`, and will be automatically categorized in the output.
|
|
978
987
|
|
|
979
|
-
### Save images to local files
|
|
980
|
-
|
|
981
|
-
You can save images returned from Gemini to local files under `/temp` by calling `Image.save()`. Optionally, you can specify the file path and file name by passing `path` and `filename` arguments to the function and skip images with invalid file names by passing `skip_invalid_filename=True`. Works for both `WebImage` and `GeneratedImage`.
|
|
982
|
-
|
|
983
|
-
```python
|
|
984
|
-
async def main():
|
|
985
|
-
response = await client.generate_content("Generate some pictures of cats")
|
|
986
|
-
for i, image in enumerate(response.images):
|
|
987
|
-
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
|
|
988
|
-
|
|
989
|
-
asyncio.run(main())
|
|
990
|
-
```
|
|
991
|
-
|
|
992
988
|
### Generate contents with Gemini extensions
|
|
993
989
|
|
|
994
990
|
> [!IMPORTANT]
|
|
995
991
|
>
|
|
996
|
-
> To access Gemini extensions in API, you must activate them on the [Gemini website](https://gemini.google.com/extensions) first. Same as image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/13695044) (as of
|
|
992
|
+
> To access Gemini extensions in API, you must activate them on the [Gemini website](https://gemini.google.com/extensions) first. Same as image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/13695044) (as of March 19th, 2025):
|
|
997
993
|
>
|
|
998
|
-
> > To
|
|
999
|
-
> >
|
|
1000
|
-
> > Sign in with your personal Google Account that you manage on your own. Extensions, including the Google Workspace extension, are currently not available to Google Workspace accounts for school, business, or other organizations.
|
|
994
|
+
> > To connect apps to Gemini, you must have Gemini Apps Activity on.
|
|
1001
995
|
> >
|
|
1002
|
-
> >
|
|
996
|
+
> > To use this feature, you must be signed in to Gemini Apps.
|
|
1003
997
|
> >
|
|
1004
|
-
> > Important:
|
|
998
|
+
> > Important: If you’re under 18, Google Workspace and Maps apps currently only work with English prompts in Gemini.
|
|
1005
999
|
|
|
1006
1000
|
After activating extensions for your account, you can access them in your prompts either by natural language or by starting your prompt with "@" followed by the extension keyword.
|
|
1007
1001
|
|
|
@@ -29,7 +29,7 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
|
|
|
29
29
|
## Features
|
|
30
30
|
|
|
31
31
|
- **Persistent Cookies** - Automatically refreshes cookies in background. Optimized for always-on services.
|
|
32
|
-
- **
|
|
32
|
+
- **Image Generation** - Natively supports generating and modifying images with natural language.
|
|
33
33
|
- **Extension Support** - Supports generating contents with [Gemini extensions](https://gemini.google.com/extensions) on, like YouTube and Gmail.
|
|
34
34
|
- **Classified Outputs** - Automatically categorizes texts, web images and AI generated images in the response.
|
|
35
35
|
- **Official Flavor** - Provides a simple and elegant interface inspired by [Google Generative AI](https://ai.google.dev/tutorials/python_quickstart)'s official API.
|
|
@@ -45,13 +45,12 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
|
|
|
45
45
|
- [Initialization](#initialization)
|
|
46
46
|
- [Select language model](#select-language-model)
|
|
47
47
|
- [Generate contents from text](#generate-contents-from-text)
|
|
48
|
-
- [Generate contents
|
|
48
|
+
- [Generate contents with files](#generate-contents-with-files)
|
|
49
49
|
- [Conversations across multiple turns](#conversations-across-multiple-turns)
|
|
50
50
|
- [Continue previous conversations](#continue-previous-conversations)
|
|
51
51
|
- [Retrieve model's thought process](#retrieve-models-thought-process)
|
|
52
52
|
- [Retrieve images in response](#retrieve-images-in-response)
|
|
53
|
-
- [Generate images with
|
|
54
|
-
- [Save images to local files](#save-images-to-local-files)
|
|
53
|
+
- [Generate images with Imagen3](#generate-images-with-imagen3)
|
|
55
54
|
- [Generate contents with Gemini extensions](#generate-contents-with-gemini-extensions)
|
|
56
55
|
- [Check and switch to other reply candidates](#check-and-switch-to-other-reply-candidates)
|
|
57
56
|
- [Control log level](#control-log-level)
|
|
@@ -185,15 +184,15 @@ asyncio.run(main())
|
|
|
185
184
|
>
|
|
186
185
|
> Simply use `print(response)` to get the same output if you just want to see the response text
|
|
187
186
|
|
|
188
|
-
### Generate contents
|
|
187
|
+
### Generate contents with files
|
|
189
188
|
|
|
190
|
-
Gemini supports
|
|
189
|
+
Gemini supports file input, including images and documents. Optionally, you can pass files as a list of paths in `str` or `pathlib.Path` to `GeminiClient.generate_content` together with text prompt.
|
|
191
190
|
|
|
192
191
|
```python
|
|
193
192
|
async def main():
|
|
194
193
|
response = await client.generate_content(
|
|
195
|
-
"
|
|
196
|
-
|
|
194
|
+
"Introduce the contents of these two files. Is there any connection between them?",
|
|
195
|
+
files=["assets/sample.pdf", Path("assets/banner.png")],
|
|
197
196
|
)
|
|
198
197
|
print(response.text)
|
|
199
198
|
|
|
@@ -207,9 +206,15 @@ If you want to keep conversation continuous, please use `GeminiClient.start_chat
|
|
|
207
206
|
```python
|
|
208
207
|
async def main():
|
|
209
208
|
chat = client.start_chat()
|
|
210
|
-
response1 = await chat.send_message(
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
response1 = await chat.send_message(
|
|
210
|
+
"Introduce the contents of these two files. Is there any connection between them?",
|
|
211
|
+
files=["assets/sample.pdf", Path("assets/banner.png")],
|
|
212
|
+
)
|
|
213
|
+
print(response1.text)
|
|
214
|
+
response2 = await chat.send_message(
|
|
215
|
+
"Use image generation tool to modify the banner with another font and design."
|
|
216
|
+
)
|
|
217
|
+
print(response2.text, response2.images, sep="\n\n----------------------------------\n\n")
|
|
213
218
|
|
|
214
219
|
asyncio.run(main())
|
|
215
220
|
```
|
|
@@ -267,24 +272,27 @@ async def main():
|
|
|
267
272
|
asyncio.run(main())
|
|
268
273
|
```
|
|
269
274
|
|
|
270
|
-
### Generate images with
|
|
275
|
+
### Generate images with Imagen3
|
|
271
276
|
|
|
272
|
-
|
|
277
|
+
You can ask Gemini to generate and modify images with Imagen3, Google's latest AI image generator, simply by natural language.
|
|
273
278
|
|
|
274
279
|
> [!IMPORTANT]
|
|
275
280
|
>
|
|
276
|
-
> Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of
|
|
281
|
+
> Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of March 19th, 2025):
|
|
277
282
|
>
|
|
278
|
-
> > Image generation in Gemini Apps is available in most countries, except in the European Economic Area (EEA), Switzerland, and the UK. It’s only available for **English prompts**.
|
|
279
|
-
> >
|
|
280
283
|
> > This feature’s availability in any specific Gemini app is also limited to the supported languages and countries of that app.
|
|
281
284
|
> >
|
|
282
285
|
> > For now, this feature isn’t available to users under 18.
|
|
286
|
+
> >
|
|
287
|
+
> > To use this feature, you must be signed in to Gemini Apps.
|
|
288
|
+
|
|
289
|
+
You can save images returned from Gemini to local by calling `Image.save()`. Optionally, you can specify the file path and file name by passing `path` and `filename` arguments to the function and skip images with invalid file names by passing `skip_invalid_filename=True`. Works for both `WebImage` and `GeneratedImage`.
|
|
283
290
|
|
|
284
291
|
```python
|
|
285
292
|
async def main():
|
|
286
293
|
response = await client.generate_content("Generate some pictures of cats")
|
|
287
|
-
for image in response.images:
|
|
294
|
+
for i, image in enumerate(response.images):
|
|
295
|
+
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
|
|
288
296
|
print(image, "\n\n----------------------------------\n")
|
|
289
297
|
|
|
290
298
|
asyncio.run(main())
|
|
@@ -294,32 +302,17 @@ asyncio.run(main())
|
|
|
294
302
|
>
|
|
295
303
|
> by default, when asked to send images (like the previous example), Gemini will send images fetched from web instead of generating images with AI model, unless you specifically require to "generate" images in your prompt. In this package, web images and generated images are treated differently as `WebImage` and `GeneratedImage`, and will be automatically categorized in the output.
|
|
296
304
|
|
|
297
|
-
### Save images to local files
|
|
298
|
-
|
|
299
|
-
You can save images returned from Gemini to local files under `/temp` by calling `Image.save()`. Optionally, you can specify the file path and file name by passing `path` and `filename` arguments to the function and skip images with invalid file names by passing `skip_invalid_filename=True`. Works for both `WebImage` and `GeneratedImage`.
|
|
300
|
-
|
|
301
|
-
```python
|
|
302
|
-
async def main():
|
|
303
|
-
response = await client.generate_content("Generate some pictures of cats")
|
|
304
|
-
for i, image in enumerate(response.images):
|
|
305
|
-
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
|
|
306
|
-
|
|
307
|
-
asyncio.run(main())
|
|
308
|
-
```
|
|
309
|
-
|
|
310
305
|
### Generate contents with Gemini extensions
|
|
311
306
|
|
|
312
307
|
> [!IMPORTANT]
|
|
313
308
|
>
|
|
314
|
-
> To access Gemini extensions in API, you must activate them on the [Gemini website](https://gemini.google.com/extensions) first. Same as image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/13695044) (as of
|
|
309
|
+
> To access Gemini extensions in API, you must activate them on the [Gemini website](https://gemini.google.com/extensions) first. Same as image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/13695044) (as of March 19th, 2025):
|
|
315
310
|
>
|
|
316
|
-
> > To
|
|
317
|
-
> >
|
|
318
|
-
> > Sign in with your personal Google Account that you manage on your own. Extensions, including the Google Workspace extension, are currently not available to Google Workspace accounts for school, business, or other organizations.
|
|
311
|
+
> > To connect apps to Gemini, you must have Gemini Apps Activity on.
|
|
319
312
|
> >
|
|
320
|
-
> >
|
|
313
|
+
> > To use this feature, you must be signed in to Gemini Apps.
|
|
321
314
|
> >
|
|
322
|
-
> > Important:
|
|
315
|
+
> > Important: If you’re under 18, Google Workspace and Maps apps currently only work with English prompts in Gemini.
|
|
323
316
|
|
|
324
317
|
After activating extensions for your account, you can access them in your prompts either by natural language or by starting your prompt with "@" followed by the extension keyword.
|
|
325
318
|
|
|
Binary file
|
|
@@ -13,6 +13,7 @@ from .exceptions import AuthError, APIError, TimeoutError, GeminiError
|
|
|
13
13
|
from .types import WebImage, GeneratedImage, Candidate, ModelOutput
|
|
14
14
|
from .utils import (
|
|
15
15
|
upload_file,
|
|
16
|
+
parse_file_name,
|
|
16
17
|
rotate_1psidts,
|
|
17
18
|
get_access_token,
|
|
18
19
|
load_browser_cookies,
|
|
@@ -263,7 +264,7 @@ class GeminiClient:
|
|
|
263
264
|
async def generate_content(
|
|
264
265
|
self,
|
|
265
266
|
prompt: str,
|
|
266
|
-
|
|
267
|
+
files: list[str | Path] | None = None,
|
|
267
268
|
model: Model | str = Model.UNSPECIFIED,
|
|
268
269
|
chat: Optional["ChatSession"] = None,
|
|
269
270
|
**kwargs,
|
|
@@ -275,8 +276,8 @@ class GeminiClient:
|
|
|
275
276
|
----------
|
|
276
277
|
prompt: `str`
|
|
277
278
|
Prompt provided by user.
|
|
278
|
-
|
|
279
|
-
List of
|
|
279
|
+
files: `list[str | Path]`, optional
|
|
280
|
+
List of file paths to be attached.
|
|
280
281
|
model: `Model` | `str`, optional
|
|
281
282
|
Specify the model to use for generation.
|
|
282
283
|
Pass either a `gemini_webapi.constants.Model` enum or a model name string.
|
|
@@ -324,17 +325,17 @@ class GeminiClient:
|
|
|
324
325
|
None,
|
|
325
326
|
json.dumps(
|
|
326
327
|
[
|
|
327
|
-
|
|
328
|
+
files
|
|
328
329
|
and [
|
|
329
330
|
prompt,
|
|
330
331
|
0,
|
|
331
332
|
None,
|
|
332
333
|
[
|
|
333
334
|
[
|
|
334
|
-
[await upload_file(
|
|
335
|
-
|
|
335
|
+
[await upload_file(file, self.proxy)],
|
|
336
|
+
parse_file_name(file),
|
|
336
337
|
]
|
|
337
|
-
for
|
|
338
|
+
for file in files
|
|
338
339
|
],
|
|
339
340
|
]
|
|
340
341
|
or [prompt],
|
|
@@ -361,18 +362,17 @@ class GeminiClient:
|
|
|
361
362
|
try:
|
|
362
363
|
response_json = json.loads(response.text.split("\n")[2])
|
|
363
364
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if not body[4]:
|
|
365
|
+
body = None
|
|
366
|
+
for part in response_json:
|
|
367
|
+
try:
|
|
368
|
+
main_part = json.loads(part[2])
|
|
369
|
+
if main_part[4]:
|
|
370
|
+
body = main_part
|
|
371
|
+
break
|
|
372
|
+
except (IndexError, TypeError, ValueError):
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
if not body:
|
|
376
376
|
raise Exception
|
|
377
377
|
except Exception:
|
|
378
378
|
await self.close()
|
|
@@ -551,7 +551,7 @@ class ChatSession:
|
|
|
551
551
|
async def send_message(
|
|
552
552
|
self,
|
|
553
553
|
prompt: str,
|
|
554
|
-
|
|
554
|
+
files: list[str | Path] | None = None,
|
|
555
555
|
**kwargs,
|
|
556
556
|
) -> ModelOutput:
|
|
557
557
|
"""
|
|
@@ -562,8 +562,8 @@ class ChatSession:
|
|
|
562
562
|
----------
|
|
563
563
|
prompt: `str`
|
|
564
564
|
Prompt provided by user.
|
|
565
|
-
|
|
566
|
-
List of
|
|
565
|
+
files: `list[str | Path]`, optional
|
|
566
|
+
List of file paths to be attached.
|
|
567
567
|
kwargs: `dict`, optional
|
|
568
568
|
Additional arguments which will be passed to the post request.
|
|
569
569
|
Refer to `httpx.AsyncClient.request` for more information.
|
|
@@ -588,7 +588,7 @@ class ChatSession:
|
|
|
588
588
|
"""
|
|
589
589
|
|
|
590
590
|
return await self.geminiclient.generate_content(
|
|
591
|
-
prompt=prompt,
|
|
591
|
+
prompt=prompt, files=files, model=self.model, chat=self, **kwargs
|
|
592
592
|
)
|
|
593
593
|
|
|
594
594
|
def choose_candidate(self, index: int) -> ModelOutput:
|
|
@@ -44,7 +44,7 @@ class Model(Enum):
|
|
|
44
44
|
"gemini-2.0-flash-thinking-with-apps",
|
|
45
45
|
{"x-goog-ext-525001261-jspb": '[null,null,null,null,"f8f8f5ea629f5d37"]'},
|
|
46
46
|
False,
|
|
47
|
-
)
|
|
47
|
+
) # Deprecated, should be removed in the future
|
|
48
48
|
G_2_0_EXP_ADVANCED = (
|
|
49
49
|
"gemini-2.0-exp-advanced",
|
|
50
50
|
{"x-goog-ext-525001261-jspb": '[null,null,null,null,"b1e46a6037e6aa9f"]'},
|
|
@@ -54,17 +54,17 @@ class Model(Enum):
|
|
|
54
54
|
"gemini-1.5-flash",
|
|
55
55
|
{"x-goog-ext-525001261-jspb": '[null,null,null,null,"418ab5ea040b5c43"]'},
|
|
56
56
|
False,
|
|
57
|
-
)
|
|
57
|
+
) # Deprecated, should be removed in the future
|
|
58
58
|
G_1_5_PRO = (
|
|
59
59
|
"gemini-1.5-pro",
|
|
60
60
|
{"x-goog-ext-525001261-jspb": '[null,null,null,null,"9d60dfae93c9ff1f"]'},
|
|
61
61
|
True,
|
|
62
|
-
)
|
|
62
|
+
) # Deprecated, should be removed in the future
|
|
63
63
|
G_1_5_PRO_RESEARCH = (
|
|
64
64
|
"gemini-1.5-pro-research",
|
|
65
65
|
{"x-goog-ext-525001261-jspb": '[null,null,null,null,"e5a44cb1dae2b489"]'},
|
|
66
66
|
True,
|
|
67
|
-
)
|
|
67
|
+
) # Deprecated, should be removed in the future
|
|
68
68
|
|
|
69
69
|
def __init__(self, name, header, advanced_only):
|
|
70
70
|
self.model_name = name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from asyncio import Task
|
|
2
2
|
|
|
3
|
-
from .upload_file import upload_file # noqa: F401
|
|
3
|
+
from .upload_file import upload_file, parse_file_name # noqa: F401
|
|
4
4
|
from .rotate_1psidts import rotate_1psidts # noqa: F401
|
|
5
5
|
from .get_access_token import get_access_token # noqa: F401
|
|
6
6
|
from .load_browser_cookies import load_browser_cookies # noqa: F401
|
|
@@ -7,14 +7,14 @@ from ..constants import Endpoint, Headers
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@validate_call
|
|
10
|
-
async def upload_file(file:
|
|
10
|
+
async def upload_file(file: str | Path, proxy: str | None = None) -> str:
|
|
11
11
|
"""
|
|
12
12
|
Upload a file to Google's server and return its identifier.
|
|
13
13
|
|
|
14
14
|
Parameters
|
|
15
15
|
----------
|
|
16
|
-
file : `
|
|
17
|
-
|
|
16
|
+
file : `str` | `Path`
|
|
17
|
+
Path to the file to be uploaded.
|
|
18
18
|
proxy: `str`, optional
|
|
19
19
|
Proxy URL.
|
|
20
20
|
|
|
@@ -30,9 +30,8 @@ async def upload_file(file: bytes | str | Path, proxy: str | None = None) -> str
|
|
|
30
30
|
If the upload request failed.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
file = f.read()
|
|
33
|
+
with open(file, "rb") as f:
|
|
34
|
+
file = f.read()
|
|
36
35
|
|
|
37
36
|
async with AsyncClient(http2=True, proxy=proxy) as client:
|
|
38
37
|
response = await client.post(
|
|
@@ -43,3 +42,25 @@ async def upload_file(file: bytes | str | Path, proxy: str | None = None) -> str
|
|
|
43
42
|
)
|
|
44
43
|
response.raise_for_status()
|
|
45
44
|
return response.text
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_file_name(file: str | Path) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Parse the file name from the given path.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
file : `str` | `Path`
|
|
54
|
+
Path to the file.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
`str`
|
|
59
|
+
File name with extension.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
file = Path(file)
|
|
63
|
+
if not file.is_file():
|
|
64
|
+
raise ValueError(f"{file} is not a valid file.")
|
|
65
|
+
|
|
66
|
+
return file.name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: gemini-webapi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.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
|
|
@@ -679,6 +679,7 @@ License-File: LICENSE
|
|
|
679
679
|
Requires-Dist: httpx[http2]~=0.28.1
|
|
680
680
|
Requires-Dist: pydantic~=2.10.5
|
|
681
681
|
Requires-Dist: loguru~=0.7.3
|
|
682
|
+
Dynamic: license-file
|
|
682
683
|
|
|
683
684
|
<p align="center">
|
|
684
685
|
<img src="https://raw.githubusercontent.com/HanaokaYuzu/Gemini-API/master/assets/banner.png" width="55%" alt="Gemini Banner" align="center">
|
|
@@ -711,7 +712,7 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
|
|
|
711
712
|
## Features
|
|
712
713
|
|
|
713
714
|
- **Persistent Cookies** - Automatically refreshes cookies in background. Optimized for always-on services.
|
|
714
|
-
- **
|
|
715
|
+
- **Image Generation** - Natively supports generating and modifying images with natural language.
|
|
715
716
|
- **Extension Support** - Supports generating contents with [Gemini extensions](https://gemini.google.com/extensions) on, like YouTube and Gmail.
|
|
716
717
|
- **Classified Outputs** - Automatically categorizes texts, web images and AI generated images in the response.
|
|
717
718
|
- **Official Flavor** - Provides a simple and elegant interface inspired by [Google Generative AI](https://ai.google.dev/tutorials/python_quickstart)'s official API.
|
|
@@ -727,13 +728,12 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
|
|
|
727
728
|
- [Initialization](#initialization)
|
|
728
729
|
- [Select language model](#select-language-model)
|
|
729
730
|
- [Generate contents from text](#generate-contents-from-text)
|
|
730
|
-
- [Generate contents
|
|
731
|
+
- [Generate contents with files](#generate-contents-with-files)
|
|
731
732
|
- [Conversations across multiple turns](#conversations-across-multiple-turns)
|
|
732
733
|
- [Continue previous conversations](#continue-previous-conversations)
|
|
733
734
|
- [Retrieve model's thought process](#retrieve-models-thought-process)
|
|
734
735
|
- [Retrieve images in response](#retrieve-images-in-response)
|
|
735
|
-
- [Generate images with
|
|
736
|
-
- [Save images to local files](#save-images-to-local-files)
|
|
736
|
+
- [Generate images with Imagen3](#generate-images-with-imagen3)
|
|
737
737
|
- [Generate contents with Gemini extensions](#generate-contents-with-gemini-extensions)
|
|
738
738
|
- [Check and switch to other reply candidates](#check-and-switch-to-other-reply-candidates)
|
|
739
739
|
- [Control log level](#control-log-level)
|
|
@@ -867,15 +867,15 @@ asyncio.run(main())
|
|
|
867
867
|
>
|
|
868
868
|
> Simply use `print(response)` to get the same output if you just want to see the response text
|
|
869
869
|
|
|
870
|
-
### Generate contents
|
|
870
|
+
### Generate contents with files
|
|
871
871
|
|
|
872
|
-
Gemini supports
|
|
872
|
+
Gemini supports file input, including images and documents. Optionally, you can pass files as a list of paths in `str` or `pathlib.Path` to `GeminiClient.generate_content` together with text prompt.
|
|
873
873
|
|
|
874
874
|
```python
|
|
875
875
|
async def main():
|
|
876
876
|
response = await client.generate_content(
|
|
877
|
-
"
|
|
878
|
-
|
|
877
|
+
"Introduce the contents of these two files. Is there any connection between them?",
|
|
878
|
+
files=["assets/sample.pdf", Path("assets/banner.png")],
|
|
879
879
|
)
|
|
880
880
|
print(response.text)
|
|
881
881
|
|
|
@@ -889,9 +889,15 @@ If you want to keep conversation continuous, please use `GeminiClient.start_chat
|
|
|
889
889
|
```python
|
|
890
890
|
async def main():
|
|
891
891
|
chat = client.start_chat()
|
|
892
|
-
response1 = await chat.send_message(
|
|
893
|
-
|
|
894
|
-
|
|
892
|
+
response1 = await chat.send_message(
|
|
893
|
+
"Introduce the contents of these two files. Is there any connection between them?",
|
|
894
|
+
files=["assets/sample.pdf", Path("assets/banner.png")],
|
|
895
|
+
)
|
|
896
|
+
print(response1.text)
|
|
897
|
+
response2 = await chat.send_message(
|
|
898
|
+
"Use image generation tool to modify the banner with another font and design."
|
|
899
|
+
)
|
|
900
|
+
print(response2.text, response2.images, sep="\n\n----------------------------------\n\n")
|
|
895
901
|
|
|
896
902
|
asyncio.run(main())
|
|
897
903
|
```
|
|
@@ -949,24 +955,27 @@ async def main():
|
|
|
949
955
|
asyncio.run(main())
|
|
950
956
|
```
|
|
951
957
|
|
|
952
|
-
### Generate images with
|
|
958
|
+
### Generate images with Imagen3
|
|
953
959
|
|
|
954
|
-
|
|
960
|
+
You can ask Gemini to generate and modify images with Imagen3, Google's latest AI image generator, simply by natural language.
|
|
955
961
|
|
|
956
962
|
> [!IMPORTANT]
|
|
957
963
|
>
|
|
958
|
-
> Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of
|
|
964
|
+
> Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of March 19th, 2025):
|
|
959
965
|
>
|
|
960
|
-
> > Image generation in Gemini Apps is available in most countries, except in the European Economic Area (EEA), Switzerland, and the UK. It’s only available for **English prompts**.
|
|
961
|
-
> >
|
|
962
966
|
> > This feature’s availability in any specific Gemini app is also limited to the supported languages and countries of that app.
|
|
963
967
|
> >
|
|
964
968
|
> > For now, this feature isn’t available to users under 18.
|
|
969
|
+
> >
|
|
970
|
+
> > To use this feature, you must be signed in to Gemini Apps.
|
|
971
|
+
|
|
972
|
+
You can save images returned from Gemini to local by calling `Image.save()`. Optionally, you can specify the file path and file name by passing `path` and `filename` arguments to the function and skip images with invalid file names by passing `skip_invalid_filename=True`. Works for both `WebImage` and `GeneratedImage`.
|
|
965
973
|
|
|
966
974
|
```python
|
|
967
975
|
async def main():
|
|
968
976
|
response = await client.generate_content("Generate some pictures of cats")
|
|
969
|
-
for image in response.images:
|
|
977
|
+
for i, image in enumerate(response.images):
|
|
978
|
+
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
|
|
970
979
|
print(image, "\n\n----------------------------------\n")
|
|
971
980
|
|
|
972
981
|
asyncio.run(main())
|
|
@@ -976,32 +985,17 @@ asyncio.run(main())
|
|
|
976
985
|
>
|
|
977
986
|
> by default, when asked to send images (like the previous example), Gemini will send images fetched from web instead of generating images with AI model, unless you specifically require to "generate" images in your prompt. In this package, web images and generated images are treated differently as `WebImage` and `GeneratedImage`, and will be automatically categorized in the output.
|
|
978
987
|
|
|
979
|
-
### Save images to local files
|
|
980
|
-
|
|
981
|
-
You can save images returned from Gemini to local files under `/temp` by calling `Image.save()`. Optionally, you can specify the file path and file name by passing `path` and `filename` arguments to the function and skip images with invalid file names by passing `skip_invalid_filename=True`. Works for both `WebImage` and `GeneratedImage`.
|
|
982
|
-
|
|
983
|
-
```python
|
|
984
|
-
async def main():
|
|
985
|
-
response = await client.generate_content("Generate some pictures of cats")
|
|
986
|
-
for i, image in enumerate(response.images):
|
|
987
|
-
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
|
|
988
|
-
|
|
989
|
-
asyncio.run(main())
|
|
990
|
-
```
|
|
991
|
-
|
|
992
988
|
### Generate contents with Gemini extensions
|
|
993
989
|
|
|
994
990
|
> [!IMPORTANT]
|
|
995
991
|
>
|
|
996
|
-
> To access Gemini extensions in API, you must activate them on the [Gemini website](https://gemini.google.com/extensions) first. Same as image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/13695044) (as of
|
|
992
|
+
> To access Gemini extensions in API, you must activate them on the [Gemini website](https://gemini.google.com/extensions) first. Same as image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/13695044) (as of March 19th, 2025):
|
|
997
993
|
>
|
|
998
|
-
> > To
|
|
999
|
-
> >
|
|
1000
|
-
> > Sign in with your personal Google Account that you manage on your own. Extensions, including the Google Workspace extension, are currently not available to Google Workspace accounts for school, business, or other organizations.
|
|
994
|
+
> > To connect apps to Gemini, you must have Gemini Apps Activity on.
|
|
1001
995
|
> >
|
|
1002
|
-
> >
|
|
996
|
+
> > To use this feature, you must be signed in to Gemini Apps.
|
|
1003
997
|
> >
|
|
1004
|
-
> > Important:
|
|
998
|
+
> > Important: If you’re under 18, Google Workspace and Maps apps currently only work with English prompts in Gemini.
|
|
1005
999
|
|
|
1006
1000
|
After activating extensions for your account, you can access them in your prompts either by natural language or by starting your prompt with "@" followed by the extension keyword.
|
|
1007
1001
|
|
|
@@ -32,17 +32,9 @@ class TestGeminiClient(unittest.IsolatedAsyncioTestCase):
|
|
|
32
32
|
|
|
33
33
|
@logger.catch(reraise=True)
|
|
34
34
|
async def test_thinking_model(self):
|
|
35
|
-
response = await self.geminiclient.generate_content(
|
|
36
|
-
"What's 1+1?", model=Model.G_2_0_FLASH_THINKING
|
|
37
|
-
)
|
|
38
|
-
logger.debug(response.thoughts)
|
|
39
|
-
logger.debug(response.text)
|
|
40
|
-
|
|
41
|
-
@logger.catch(reraise=True)
|
|
42
|
-
async def test_thinking_with_apps(self):
|
|
43
35
|
response = await self.geminiclient.generate_content(
|
|
44
36
|
"Tell me a fact about today in history and illustrate it with a youtube video",
|
|
45
|
-
model=Model.
|
|
37
|
+
model=Model.G_2_0_FLASH_THINKING,
|
|
46
38
|
)
|
|
47
39
|
logger.debug(response.thoughts)
|
|
48
40
|
logger.debug(response.text)
|
|
@@ -61,10 +53,10 @@ class TestGeminiClient(unittest.IsolatedAsyncioTestCase):
|
|
|
61
53
|
logger.debug(f"Model version ({model.model_name}): {response.text}")
|
|
62
54
|
|
|
63
55
|
@logger.catch(reraise=True)
|
|
64
|
-
async def
|
|
56
|
+
async def test_upload_files(self):
|
|
65
57
|
response = await self.geminiclient.generate_content(
|
|
66
|
-
"
|
|
67
|
-
|
|
58
|
+
"Introduce the contents of these two files. Is there any connection between them?",
|
|
59
|
+
files=["assets/sample.pdf", Path("assets/banner.png")],
|
|
68
60
|
)
|
|
69
61
|
logger.debug(response.text)
|
|
70
62
|
|
|
@@ -92,11 +84,14 @@ class TestGeminiClient(unittest.IsolatedAsyncioTestCase):
|
|
|
92
84
|
chat = self.geminiclient.start_chat()
|
|
93
85
|
response1 = await chat.send_message(
|
|
94
86
|
"What's the difference between these two images?",
|
|
95
|
-
|
|
87
|
+
files=["assets/banner.png", "assets/favicon.png"],
|
|
96
88
|
)
|
|
97
89
|
logger.debug(response1.text)
|
|
98
|
-
response2 = await chat.send_message(
|
|
90
|
+
response2 = await chat.send_message(
|
|
91
|
+
"Use image generation tool to modify the banner with another font and design."
|
|
92
|
+
)
|
|
99
93
|
logger.debug(response2.text)
|
|
94
|
+
logger.debug(response2.images)
|
|
100
95
|
|
|
101
96
|
@logger.catch(reraise=True)
|
|
102
97
|
async def test_send_web_image(self):
|
|
@@ -120,14 +115,6 @@ class TestGeminiClient(unittest.IsolatedAsyncioTestCase):
|
|
|
120
115
|
self.assertTrue(image.url)
|
|
121
116
|
logger.debug(image)
|
|
122
117
|
|
|
123
|
-
@logger.catch(reraise=True)
|
|
124
|
-
async def test_image_generation_failure(self):
|
|
125
|
-
response = await self.geminiclient.generate_content(
|
|
126
|
-
"Generate some pictures of people"
|
|
127
|
-
)
|
|
128
|
-
self.assertFalse(response.images)
|
|
129
|
-
logger.debug(response.text)
|
|
130
|
-
|
|
131
118
|
@logger.catch(reraise=True)
|
|
132
119
|
async def test_card_content(self):
|
|
133
120
|
response = await self.geminiclient.generate_content("How is today's weather?")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi/utils/load_browser_cookies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gemini_webapi-1.9.0 → gemini_webapi-1.10.0}/src/gemini_webapi.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|