google-genai 1.56.0__py3-none-any.whl → 1.58.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. google/genai/_api_client.py +49 -26
  2. google/genai/_interactions/__init__.py +3 -0
  3. google/genai/_interactions/_base_client.py +1 -1
  4. google/genai/_interactions/_client.py +57 -3
  5. google/genai/_interactions/_client_adapter.py +48 -0
  6. google/genai/_interactions/types/__init__.py +6 -0
  7. google/genai/_interactions/types/audio_content.py +2 -0
  8. google/genai/_interactions/types/audio_content_param.py +2 -0
  9. google/genai/_interactions/types/content.py +65 -0
  10. google/genai/_interactions/types/content_delta.py +10 -2
  11. google/genai/_interactions/types/content_param.py +63 -0
  12. google/genai/_interactions/types/content_start.py +5 -46
  13. google/genai/_interactions/types/content_stop.py +1 -2
  14. google/genai/_interactions/types/document_content.py +2 -0
  15. google/genai/_interactions/types/document_content_param.py +2 -0
  16. google/genai/_interactions/types/error_event.py +1 -2
  17. google/genai/_interactions/types/file_search_call_content.py +32 -0
  18. google/genai/_interactions/types/file_search_call_content_param.py +31 -0
  19. google/genai/_interactions/types/generation_config.py +4 -0
  20. google/genai/_interactions/types/generation_config_param.py +4 -0
  21. google/genai/_interactions/types/image_config.py +31 -0
  22. google/genai/_interactions/types/image_config_param.py +30 -0
  23. google/genai/_interactions/types/image_content.py +2 -0
  24. google/genai/_interactions/types/image_content_param.py +2 -0
  25. google/genai/_interactions/types/interaction.py +6 -52
  26. google/genai/_interactions/types/interaction_create_params.py +4 -22
  27. google/genai/_interactions/types/interaction_event.py +1 -2
  28. google/genai/_interactions/types/interaction_sse_event.py +5 -3
  29. google/genai/_interactions/types/interaction_status_update.py +1 -2
  30. google/genai/_interactions/types/model.py +1 -0
  31. google/genai/_interactions/types/model_param.py +1 -0
  32. google/genai/_interactions/types/turn.py +3 -44
  33. google/genai/_interactions/types/turn_param.py +4 -40
  34. google/genai/_interactions/types/usage.py +1 -1
  35. google/genai/_interactions/types/usage_param.py +1 -1
  36. google/genai/_interactions/types/video_content.py +2 -0
  37. google/genai/_interactions/types/video_content_param.py +2 -0
  38. google/genai/_live_converters.py +118 -34
  39. google/genai/_local_tokenizer_loader.py +1 -0
  40. google/genai/_tokens_converters.py +14 -14
  41. google/genai/_transformers.py +15 -21
  42. google/genai/batches.py +27 -22
  43. google/genai/caches.py +42 -42
  44. google/genai/chats.py +0 -2
  45. google/genai/client.py +61 -55
  46. google/genai/files.py +224 -0
  47. google/genai/live.py +1 -1
  48. google/genai/models.py +56 -44
  49. google/genai/tests/__init__.py +21 -0
  50. google/genai/tests/afc/__init__.py +21 -0
  51. google/genai/tests/afc/test_convert_if_exist_pydantic_model.py +309 -0
  52. google/genai/tests/afc/test_convert_number_values_for_function_call_args.py +63 -0
  53. google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py +240 -0
  54. google/genai/tests/afc/test_generate_content_stream_afc.py +530 -0
  55. google/genai/tests/afc/test_generate_content_stream_afc_thoughts.py +77 -0
  56. google/genai/tests/afc/test_get_function_map.py +176 -0
  57. google/genai/tests/afc/test_get_function_response_parts.py +277 -0
  58. google/genai/tests/afc/test_get_max_remote_calls_for_afc.py +130 -0
  59. google/genai/tests/afc/test_invoke_function_from_dict_args.py +241 -0
  60. google/genai/tests/afc/test_raise_error_for_afc_incompatible_config.py +159 -0
  61. google/genai/tests/afc/test_should_append_afc_history.py +53 -0
  62. google/genai/tests/afc/test_should_disable_afc.py +214 -0
  63. google/genai/tests/batches/__init__.py +17 -0
  64. google/genai/tests/batches/test_cancel.py +77 -0
  65. google/genai/tests/batches/test_create.py +78 -0
  66. google/genai/tests/batches/test_create_with_bigquery.py +113 -0
  67. google/genai/tests/batches/test_create_with_file.py +82 -0
  68. google/genai/tests/batches/test_create_with_gcs.py +125 -0
  69. google/genai/tests/batches/test_create_with_inlined_requests.py +255 -0
  70. google/genai/tests/batches/test_delete.py +86 -0
  71. google/genai/tests/batches/test_embedding.py +157 -0
  72. google/genai/tests/batches/test_get.py +78 -0
  73. google/genai/tests/batches/test_list.py +79 -0
  74. google/genai/tests/caches/__init__.py +17 -0
  75. google/genai/tests/caches/constants.py +29 -0
  76. google/genai/tests/caches/test_create.py +210 -0
  77. google/genai/tests/caches/test_create_custom_url.py +105 -0
  78. google/genai/tests/caches/test_delete.py +54 -0
  79. google/genai/tests/caches/test_delete_custom_url.py +52 -0
  80. google/genai/tests/caches/test_get.py +94 -0
  81. google/genai/tests/caches/test_get_custom_url.py +52 -0
  82. google/genai/tests/caches/test_list.py +68 -0
  83. google/genai/tests/caches/test_update.py +70 -0
  84. google/genai/tests/caches/test_update_custom_url.py +58 -0
  85. google/genai/tests/chats/__init__.py +1 -0
  86. google/genai/tests/chats/test_get_history.py +598 -0
  87. google/genai/tests/chats/test_send_message.py +844 -0
  88. google/genai/tests/chats/test_validate_response.py +90 -0
  89. google/genai/tests/client/__init__.py +17 -0
  90. google/genai/tests/client/test_async_stream.py +427 -0
  91. google/genai/tests/client/test_client_close.py +197 -0
  92. google/genai/tests/client/test_client_initialization.py +1687 -0
  93. google/genai/tests/client/test_client_requests.py +221 -0
  94. google/genai/tests/client/test_custom_client.py +104 -0
  95. google/genai/tests/client/test_http_options.py +178 -0
  96. google/genai/tests/client/test_replay_client_equality.py +168 -0
  97. google/genai/tests/client/test_retries.py +846 -0
  98. google/genai/tests/client/test_upload_errors.py +136 -0
  99. google/genai/tests/common/__init__.py +17 -0
  100. google/genai/tests/common/test_common.py +954 -0
  101. google/genai/tests/conftest.py +162 -0
  102. google/genai/tests/documents/__init__.py +17 -0
  103. google/genai/tests/documents/test_delete.py +51 -0
  104. google/genai/tests/documents/test_get.py +85 -0
  105. google/genai/tests/documents/test_list.py +72 -0
  106. google/genai/tests/errors/__init__.py +1 -0
  107. google/genai/tests/errors/test_api_error.py +417 -0
  108. google/genai/tests/file_search_stores/__init__.py +17 -0
  109. google/genai/tests/file_search_stores/test_create.py +66 -0
  110. google/genai/tests/file_search_stores/test_delete.py +64 -0
  111. google/genai/tests/file_search_stores/test_get.py +94 -0
  112. google/genai/tests/file_search_stores/test_import_file.py +112 -0
  113. google/genai/tests/file_search_stores/test_list.py +57 -0
  114. google/genai/tests/file_search_stores/test_upload_to_file_search_store.py +141 -0
  115. google/genai/tests/files/__init__.py +17 -0
  116. google/genai/tests/files/test_delete.py +46 -0
  117. google/genai/tests/files/test_download.py +85 -0
  118. google/genai/tests/files/test_get.py +46 -0
  119. google/genai/tests/files/test_list.py +72 -0
  120. google/genai/tests/files/test_register.py +272 -0
  121. google/genai/tests/files/test_register_table.py +70 -0
  122. google/genai/tests/files/test_upload.py +255 -0
  123. google/genai/tests/imports/test_no_optional_imports.py +28 -0
  124. google/genai/tests/interactions/test_auth.py +476 -0
  125. google/genai/tests/interactions/test_integration.py +84 -0
  126. google/genai/tests/interactions/test_paths.py +105 -0
  127. google/genai/tests/live/__init__.py +16 -0
  128. google/genai/tests/live/test_live.py +2143 -0
  129. google/genai/tests/live/test_live_music.py +362 -0
  130. google/genai/tests/live/test_live_response.py +163 -0
  131. google/genai/tests/live/test_send_client_content.py +147 -0
  132. google/genai/tests/live/test_send_realtime_input.py +268 -0
  133. google/genai/tests/live/test_send_tool_response.py +222 -0
  134. google/genai/tests/local_tokenizer/__init__.py +17 -0
  135. google/genai/tests/local_tokenizer/test_local_tokenizer.py +343 -0
  136. google/genai/tests/local_tokenizer/test_local_tokenizer_loader.py +235 -0
  137. google/genai/tests/mcp/__init__.py +17 -0
  138. google/genai/tests/mcp/test_has_mcp_tool_usage.py +89 -0
  139. google/genai/tests/mcp/test_mcp_to_gemini_tools.py +191 -0
  140. google/genai/tests/mcp/test_parse_config_for_mcp_sessions.py +201 -0
  141. google/genai/tests/mcp/test_parse_config_for_mcp_usage.py +130 -0
  142. google/genai/tests/mcp/test_set_mcp_usage_header.py +72 -0
  143. google/genai/tests/models/__init__.py +17 -0
  144. google/genai/tests/models/constants.py +8 -0
  145. google/genai/tests/models/test_compute_tokens.py +120 -0
  146. google/genai/tests/models/test_count_tokens.py +159 -0
  147. google/genai/tests/models/test_delete.py +107 -0
  148. google/genai/tests/models/test_edit_image.py +264 -0
  149. google/genai/tests/models/test_embed_content.py +94 -0
  150. google/genai/tests/models/test_function_call_streaming.py +442 -0
  151. google/genai/tests/models/test_generate_content.py +2501 -0
  152. google/genai/tests/models/test_generate_content_cached_content.py +132 -0
  153. google/genai/tests/models/test_generate_content_config_zero_value.py +103 -0
  154. google/genai/tests/models/test_generate_content_from_apikey.py +44 -0
  155. google/genai/tests/models/test_generate_content_http_options.py +40 -0
  156. google/genai/tests/models/test_generate_content_image_generation.py +143 -0
  157. google/genai/tests/models/test_generate_content_mcp.py +343 -0
  158. google/genai/tests/models/test_generate_content_media_resolution.py +97 -0
  159. google/genai/tests/models/test_generate_content_model.py +139 -0
  160. google/genai/tests/models/test_generate_content_part.py +821 -0
  161. google/genai/tests/models/test_generate_content_thought.py +76 -0
  162. google/genai/tests/models/test_generate_content_tools.py +1761 -0
  163. google/genai/tests/models/test_generate_images.py +191 -0
  164. google/genai/tests/models/test_generate_videos.py +759 -0
  165. google/genai/tests/models/test_get.py +104 -0
  166. google/genai/tests/models/test_list.py +233 -0
  167. google/genai/tests/models/test_recontext_image.py +189 -0
  168. google/genai/tests/models/test_segment_image.py +148 -0
  169. google/genai/tests/models/test_update.py +95 -0
  170. google/genai/tests/models/test_upscale_image.py +157 -0
  171. google/genai/tests/operations/__init__.py +17 -0
  172. google/genai/tests/operations/test_get.py +38 -0
  173. google/genai/tests/public_samples/__init__.py +17 -0
  174. google/genai/tests/public_samples/test_gemini_text_only.py +34 -0
  175. google/genai/tests/pytest_helper.py +246 -0
  176. google/genai/tests/shared/__init__.py +16 -0
  177. google/genai/tests/shared/batches/__init__.py +14 -0
  178. google/genai/tests/shared/batches/test_create_delete.py +57 -0
  179. google/genai/tests/shared/batches/test_create_get_cancel.py +56 -0
  180. google/genai/tests/shared/batches/test_list.py +40 -0
  181. google/genai/tests/shared/caches/__init__.py +14 -0
  182. google/genai/tests/shared/caches/test_create_get_delete.py +67 -0
  183. google/genai/tests/shared/caches/test_create_update_get.py +71 -0
  184. google/genai/tests/shared/caches/test_list.py +40 -0
  185. google/genai/tests/shared/chats/__init__.py +14 -0
  186. google/genai/tests/shared/chats/test_send_message.py +48 -0
  187. google/genai/tests/shared/chats/test_send_message_stream.py +50 -0
  188. google/genai/tests/shared/files/__init__.py +14 -0
  189. google/genai/tests/shared/files/test_list.py +41 -0
  190. google/genai/tests/shared/files/test_upload_get_delete.py +54 -0
  191. google/genai/tests/shared/models/__init__.py +14 -0
  192. google/genai/tests/shared/models/test_compute_tokens.py +41 -0
  193. google/genai/tests/shared/models/test_count_tokens.py +40 -0
  194. google/genai/tests/shared/models/test_edit_image.py +67 -0
  195. google/genai/tests/shared/models/test_embed.py +40 -0
  196. google/genai/tests/shared/models/test_generate_content.py +39 -0
  197. google/genai/tests/shared/models/test_generate_content_stream.py +54 -0
  198. google/genai/tests/shared/models/test_generate_images.py +40 -0
  199. google/genai/tests/shared/models/test_generate_videos.py +38 -0
  200. google/genai/tests/shared/models/test_list.py +37 -0
  201. google/genai/tests/shared/models/test_recontext_image.py +55 -0
  202. google/genai/tests/shared/models/test_segment_image.py +52 -0
  203. google/genai/tests/shared/models/test_upscale_image.py +52 -0
  204. google/genai/tests/shared/tunings/__init__.py +16 -0
  205. google/genai/tests/shared/tunings/test_create.py +46 -0
  206. google/genai/tests/shared/tunings/test_create_get_cancel.py +56 -0
  207. google/genai/tests/shared/tunings/test_list.py +39 -0
  208. google/genai/tests/tokens/__init__.py +16 -0
  209. google/genai/tests/tokens/test_create.py +154 -0
  210. google/genai/tests/transformers/__init__.py +17 -0
  211. google/genai/tests/transformers/test_blobs.py +84 -0
  212. google/genai/tests/transformers/test_bytes.py +15 -0
  213. google/genai/tests/transformers/test_duck_type.py +96 -0
  214. google/genai/tests/transformers/test_function_responses.py +72 -0
  215. google/genai/tests/transformers/test_schema.py +653 -0
  216. google/genai/tests/transformers/test_t_batch.py +286 -0
  217. google/genai/tests/transformers/test_t_content.py +160 -0
  218. google/genai/tests/transformers/test_t_contents.py +398 -0
  219. google/genai/tests/transformers/test_t_part.py +85 -0
  220. google/genai/tests/transformers/test_t_parts.py +87 -0
  221. google/genai/tests/transformers/test_t_tool.py +157 -0
  222. google/genai/tests/transformers/test_t_tools.py +195 -0
  223. google/genai/tests/tunings/__init__.py +16 -0
  224. google/genai/tests/tunings/test_cancel.py +39 -0
  225. google/genai/tests/tunings/test_end_to_end.py +106 -0
  226. google/genai/tests/tunings/test_get.py +67 -0
  227. google/genai/tests/tunings/test_list.py +75 -0
  228. google/genai/tests/tunings/test_tune.py +268 -0
  229. google/genai/tests/types/__init__.py +16 -0
  230. google/genai/tests/types/test_bytes_internal.py +271 -0
  231. google/genai/tests/types/test_bytes_type.py +152 -0
  232. google/genai/tests/types/test_future.py +101 -0
  233. google/genai/tests/types/test_optional_types.py +36 -0
  234. google/genai/tests/types/test_part_type.py +616 -0
  235. google/genai/tests/types/test_schema_from_json_schema.py +417 -0
  236. google/genai/tests/types/test_schema_json_schema.py +468 -0
  237. google/genai/tests/types/test_types.py +2903 -0
  238. google/genai/types.py +631 -488
  239. google/genai/version.py +1 -1
  240. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/METADATA +6 -11
  241. google_genai-1.58.0.dist-info/RECORD +358 -0
  242. google_genai-1.56.0.dist-info/RECORD +0 -162
  243. /google/genai/{_interactions/py.typed → tests/interactions/__init__.py} +0 -0
  244. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/WHEEL +0 -0
  245. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/licenses/LICENSE +0 -0
  246. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,476 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+
16
+
17
+ """Tests for Interactions API."""
18
+
19
+ from ... import Client
20
+ from ... import _base_url
21
+ from unittest import mock
22
+ import pytest
23
+ from httpx import Request, Response
24
+ from ..._api_client import AsyncHttpxClient, BaseApiClient
25
+ from httpx import Client as HTTPClient
26
+ import os
27
+
28
+ ENV_VARS = [
29
+ "GOOGLE_CLOUD_PROJECT",
30
+ "GEMINI_API_KEY",
31
+ "GOOGLE_API_KEY",
32
+ "GOOGLE_CLOUD_LOCATION",
33
+ ]
34
+
35
+ @pytest.fixture(autouse=True)
36
+ def clear_env_vars(monkeypatch):
37
+ for var in ENV_VARS:
38
+ monkeypatch.delenv(var, raising=False)
39
+
40
+ def test_interactions_gemini_url(monkeypatch):
41
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
42
+ client = Client()
43
+
44
+
45
+ with mock.patch.object(HTTPClient, "send") as mock_send:
46
+ mock_send.return_value = Response(200, request=Request('POST', ''))
47
+ client.interactions.create(
48
+ model='gemini-1.5-flash',
49
+ input='Hello',
50
+ )
51
+ mock_send.assert_called_once()
52
+ request = mock_send.call_args[0][0]
53
+ assert str(request.url).endswith('/v1beta/interactions')
54
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
55
+
56
+
57
+ def test_interactions_gemini_no_vertex_auth(monkeypatch):
58
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
59
+ client = Client()
60
+
61
+ with (
62
+ mock.patch.object(BaseApiClient, "_access_token") as mock_access_token,
63
+ mock.patch.object(HTTPClient, "send") as mock_send,
64
+ ):
65
+ mock_send.return_value = Response(200, request=Request('POST', ''))
66
+ client.interactions.create(
67
+ model='gemini-1.5-flash',
68
+ input='Hello',
69
+ )
70
+ mock_access_token.assert_not_called()
71
+
72
+ def test_interactions_gemini_retry(monkeypatch):
73
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
74
+ client = Client()
75
+ client._api_client.max_retries = 2
76
+
77
+ with mock.patch.object(HTTPClient, "send") as mock_send:
78
+ mock_send.side_effect = [
79
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
80
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
81
+ Response(200, request=Request('POST', '')),
82
+ ]
83
+ client.interactions.create(model='gemini-1.5-flash', input='Hello')
84
+ assert mock_send.call_count == 3
85
+
86
+ def test_interactions_gemini_extra_headers(monkeypatch):
87
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
88
+ client = Client()
89
+
90
+ with mock.patch.object(HTTPClient, "send") as mock_send:
91
+ mock_send.return_value = Response(200, request=Request('POST', ''))
92
+ client.interactions.create(
93
+ model='gemini-1.5-flash',
94
+ input='Hello',
95
+ extra_headers={'X-Custom-Header': 'TestValue'}
96
+ )
97
+ mock_send.assert_called_once()
98
+ request = mock_send.call_args[0][0]
99
+ assert request.headers['x-custom-header'] == 'TestValue'
100
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
101
+
102
+
103
+ def test_interactions_vertex_auth_header():
104
+ from ..._api_client import BaseApiClient
105
+ from ..._interactions._base_client import SyncAPIClient
106
+ from httpx import Client as HTTPClient
107
+
108
+ creds = mock.Mock()
109
+ creds.quota_project_id = "test-quota-project"
110
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
111
+
112
+ with (
113
+ mock.patch.object(
114
+ BaseApiClient, "_access_token", return_value='fake-vertex-token'
115
+ ) as mock_access_token,
116
+ mock.patch.object(
117
+ HTTPClient, "send",
118
+ return_value=mock.Mock(),
119
+ ) as mock_send,
120
+ ):
121
+
122
+ response = client.interactions.create(
123
+ model='gemini-2.5-flash',
124
+ input='What is the largest planet in our solar system?',
125
+ )
126
+
127
+ mock_send.assert_called_once()
128
+ mock_access_token.assert_called_once()
129
+ args, kwargs = mock_send.call_args
130
+ headers = args[0].headers
131
+ assert any(
132
+ key == "authorization" and value == 'Bearer fake-vertex-token'
133
+ for key, value in headers.items())
134
+ assert any(
135
+ key == "x-goog-user-project" and value == 'test-quota-project'
136
+ for key, value in headers.items())
137
+
138
+ def test_interactions_vertex_key_no_auth_header():
139
+ from ..._api_client import BaseApiClient
140
+ from httpx import Client as HTTPClient
141
+
142
+ creds = mock.Mock()
143
+ client = Client(vertexai=True, api_key='test-api-key')
144
+
145
+ with (
146
+ mock.patch.object(
147
+ BaseApiClient, "_access_token", return_value='fake-vertex-token'
148
+ ) as mock_access_token,
149
+ mock.patch.object(
150
+ HTTPClient, "send",
151
+ return_value=mock.Mock(),
152
+ ) as mock_send,
153
+ ):
154
+
155
+ response = client.interactions.create(
156
+ model='gemini-2.5-flash',
157
+ input='What is the largest planet in our solar system?',
158
+ )
159
+
160
+ mock_send.assert_called_once()
161
+ mock_access_token.assert_not_called()
162
+ args, kwargs = mock_send.call_args
163
+ headers = args[0].headers
164
+ assert any(
165
+ key == "x-goog-api-key" and value == 'test-api-key'
166
+ for key, value in headers.items())
167
+
168
+ def test_interactions_vertex_url():
169
+ creds = mock.Mock()
170
+ creds.quota_project_id = "test-quota-project"
171
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
172
+
173
+ with mock.patch("httpx.Client.send") as mock_send:
174
+ mock_send.return_value = Response(200, request=Request('POST', ''))
175
+ client.interactions.create(
176
+ model='gemini-1.5-flash',
177
+ input='Hello',
178
+ )
179
+ mock_send.assert_called_once()
180
+ request = mock_send.call_args[0][0]
181
+ assert str(request.url) == 'https://us-central1-aiplatform.googleapis.com/v1beta1/projects/test-project/locations/us-central1/interactions'
182
+
183
+ def test_interactions_vertex_auth_refresh_on_retry():
184
+ from ..._api_client import BaseApiClient
185
+ from httpx import Client as HTTPClient
186
+
187
+ creds = mock.Mock()
188
+ creds.quota_project_id = "test-quota-project"
189
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
190
+ client._api_client.max_retries = 2
191
+
192
+ token_values = ['token1', 'token2', 'token3']
193
+ token_iter = iter(token_values)
194
+
195
+ def get_token():
196
+ return next(token_iter)
197
+
198
+ with (
199
+ mock.patch.object(BaseApiClient, "_access_token", side_effect=get_token) as mock_access_token,
200
+ mock.patch.object(HTTPClient, "send") as mock_send,
201
+ ):
202
+ mock_send.side_effect = [
203
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
204
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
205
+ Response(200, request=Request('POST', '')),
206
+ ]
207
+
208
+ client.interactions.create(model='gemini-1.5-flash', input='Hello')
209
+
210
+ assert mock_access_token.call_count == 3
211
+ assert mock_send.call_count == 3
212
+ # Check headers of each call
213
+ for i in range(3):
214
+ headers = mock_send.call_args_list[i][0][0].headers
215
+ assert headers['authorization'] == f'Bearer {token_values[i]}'
216
+
217
+
218
+ def test_interactions_vertex_extra_headers_override():
219
+ from ..._api_client import BaseApiClient
220
+ from httpx import Client as HTTPClient
221
+
222
+ creds = mock.Mock()
223
+ creds.quota_project_id = "test-quota-project"
224
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
225
+
226
+ with (
227
+ mock.patch.object(BaseApiClient, "_access_token", return_value='default-token') as mock_access_token,
228
+ mock.patch.object(HTTPClient, "send") as mock_send,
229
+ ):
230
+ mock_send.return_value = Response(200, request=Request('POST', ''))
231
+
232
+ # Override Authorization
233
+ client.interactions.create(
234
+ model='gemini-1.5-flash',
235
+ input='Hello',
236
+ extra_headers={'Authorization': 'Bearer manual-token'}
237
+ )
238
+ mock_send.assert_called_once()
239
+ headers = mock_send.call_args[0][0].headers
240
+ assert headers['authorization'] == 'Bearer manual-token'
241
+ mock_access_token.assert_not_called() # Should not fetch default token
242
+
243
+ mock_send.reset_mock()
244
+ mock_access_token.reset_mock()
245
+
246
+ # Provide API Key
247
+ client.interactions.create(
248
+ model='gemini-1.5-flash',
249
+ input='Hello',
250
+ extra_headers={'x-goog-api-key': 'manual-key'}
251
+ )
252
+ mock_send.assert_called_once()
253
+ headers = mock_send.call_args[0][0].headers
254
+ assert headers['x-goog-api-key'] == 'manual-key'
255
+ assert 'authorization' not in headers
256
+ mock_access_token.assert_not_called()
257
+
258
+ @pytest.mark.asyncio
259
+ async def test_async_interactions_gemini_url(monkeypatch):
260
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
261
+ client = Client()
262
+
263
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
264
+ mock_send.return_value = Response(200, request=Request('POST', ''))
265
+ await client.aio.interactions.create(
266
+ model='gemini-1.5-flash',
267
+ input='Hello',
268
+ )
269
+ mock_send.assert_called_once()
270
+ request = mock_send.call_args[0][0]
271
+ assert str(request.url).endswith('/v1beta/interactions')
272
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
273
+
274
+ @pytest.mark.asyncio
275
+ async def test_async_interactions_gemini_no_vertex_auth(monkeypatch):
276
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
277
+ client = Client()
278
+
279
+ with (
280
+ mock.patch.object(BaseApiClient, "_async_access_token") as mock_access_token,
281
+ mock.patch.object(AsyncHttpxClient, "send") as mock_send,
282
+ ):
283
+ mock_send.return_value = Response(200, request=Request('POST', ''))
284
+ await client.aio.interactions.create(
285
+ model='gemini-1.5-flash',
286
+ input='Hello',
287
+ )
288
+ mock_access_token.assert_not_called()
289
+
290
+ @pytest.mark.asyncio
291
+ async def test_async_interactions_gemini_retry(monkeypatch):
292
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
293
+ client = Client()
294
+ client.aio._api_client.max_retries = 2
295
+
296
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
297
+ mock_send.side_effect = [
298
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
299
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
300
+ Response(200, request=Request('POST', '')),
301
+ ]
302
+ await client.aio.interactions.create(model='gemini-1.5-flash', input='Hello')
303
+ assert mock_send.call_count == 3
304
+
305
+ @pytest.mark.asyncio
306
+ async def test_async_interactions_gemini_extra_headers(monkeypatch):
307
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
308
+ client = Client()
309
+
310
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
311
+ mock_send.return_value = Response(200, request=Request('POST', ''))
312
+ await client.aio.interactions.create(
313
+ model='gemini-1.5-flash',
314
+ input='Hello',
315
+ extra_headers={'X-Custom-Header': 'TestValue'}
316
+ )
317
+ mock_send.assert_called_once()
318
+ request = mock_send.call_args[0][0]
319
+ assert request.headers['x-custom-header'] == 'TestValue'
320
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
321
+
322
+ @pytest.mark.asyncio
323
+ async def test_async_interactions_vertex_auth_header():
324
+ from ..._api_client import BaseApiClient
325
+ from ..._interactions._base_client import SyncAPIClient
326
+ from ..._api_client import AsyncHttpxClient
327
+
328
+ creds = mock.Mock()
329
+ creds.quota_project_id = "test-quota-project"
330
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
331
+
332
+ with (
333
+ mock.patch.object(
334
+ BaseApiClient, "_async_access_token", return_value='fake-vertex-token'
335
+ ) as mock_access_token,
336
+ mock.patch.object(
337
+ AsyncHttpxClient, "send",
338
+ return_value=mock.Mock(),
339
+ ) as mock_send,
340
+ ):
341
+
342
+ response = await client.aio.interactions.create(
343
+ model='gemini-2.5-flash',
344
+ input='What is the largest planet in our solar system?',
345
+ )
346
+
347
+ mock_send.assert_called_once()
348
+ mock_access_token.assert_called_once()
349
+ args, kwargs = mock_send.call_args
350
+ headers = args[0].headers
351
+ assert any(
352
+ key == "authorization" and value == 'Bearer fake-vertex-token'
353
+ for key, value in headers.items())
354
+ assert any(
355
+ key == "x-goog-user-project" and value == 'test-quota-project'
356
+ for key, value in headers.items())
357
+
358
+ @pytest.mark.asyncio
359
+ async def test_async_interactions_vertex_key_no_auth_header():
360
+ from ..._api_client import BaseApiClient
361
+ client = Client(vertexai=True, api_key='test-api-key')
362
+
363
+ with (
364
+ mock.patch.object(
365
+ BaseApiClient, "_async_access_token", return_value='fake-vertex-token'
366
+ ) as mock_access_token,
367
+ mock.patch.object(
368
+ AsyncHttpxClient, "send",
369
+ return_value=mock.Mock(),
370
+ ) as mock_send,
371
+ ):
372
+
373
+ response = await client.aio.interactions.create(
374
+ model='gemini-2.5-flash',
375
+ input='What is the largest planet in our solar system?',
376
+ )
377
+
378
+ mock_send.assert_called_once()
379
+ mock_access_token.assert_not_called()
380
+ args, kwargs = mock_send.call_args
381
+ headers = args[0].headers
382
+ assert any(
383
+ key == "x-goog-api-key" and value == 'test-api-key'
384
+ for key, value in headers.items())
385
+
386
+ @pytest.mark.asyncio
387
+ async def test_async_interactions_vertex_url():
388
+ from ..._api_client import AsyncHttpxClient
389
+ creds = mock.Mock()
390
+ creds.quota_project_id = "test-quota-project"
391
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
392
+
393
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
394
+ mock_send.return_value = Response(200, request=Request('POST', ''))
395
+ await client.aio.interactions.create(
396
+ model='gemini-1.5-flash',
397
+ input='Hello',
398
+ )
399
+ mock_send.assert_called_once()
400
+ request = mock_send.call_args[0][0]
401
+ assert str(request.url) == 'https://us-central1-aiplatform.googleapis.com/v1beta1/projects/test-project/locations/us-central1/interactions'
402
+
403
+ @pytest.mark.asyncio
404
+ async def test_async_interactions_vertex_auth_refresh_on_retry():
405
+ from ..._api_client import BaseApiClient
406
+ from ..._api_client import AsyncHttpxClient
407
+
408
+ creds = mock.Mock()
409
+ creds.quota_project_id = "test-quota-project"
410
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
411
+ client.aio._api_client.max_retries = 2
412
+
413
+ token_values = ['token1', 'token2', 'token3']
414
+ token_iter = iter(token_values)
415
+
416
+ async def get_token():
417
+ return next(token_iter)
418
+
419
+ with (
420
+ mock.patch.object(BaseApiClient, "_async_access_token", side_effect=get_token) as mock_access_token,
421
+ mock.patch.object(AsyncHttpxClient, "send") as mock_send,
422
+ ):
423
+ mock_send.side_effect = [
424
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
425
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
426
+ Response(200, request=Request('POST', '')),
427
+ ]
428
+
429
+ await client.aio.interactions.create(model='gemini-1.5-flash', input='Hello')
430
+
431
+ assert mock_access_token.call_count == 3
432
+ assert mock_send.call_count == 3
433
+ for i in range(3):
434
+ headers = mock_send.call_args_list[i][0][0].headers
435
+ assert headers['authorization'] == f'Bearer {token_values[i]}'
436
+
437
+ @pytest.mark.asyncio
438
+ async def test_async_interactions_vertex_extra_headers_override():
439
+ from ..._api_client import BaseApiClient
440
+ from ..._api_client import AsyncHttpxClient
441
+
442
+ creds = mock.Mock()
443
+ creds.quota_project_id = "test-quota-project"
444
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
445
+
446
+ with (
447
+ mock.patch.object(BaseApiClient, "_async_access_token", return_value='default-token') as mock_access_token,
448
+ mock.patch.object(AsyncHttpxClient, "send") as mock_send,
449
+ ):
450
+ mock_send.return_value = Response(200, request=Request('POST', ''))
451
+
452
+ # Override Authorization
453
+ await client.aio.interactions.create(
454
+ model='gemini-1.5-flash',
455
+ input='Hello',
456
+ extra_headers={'Authorization': 'Bearer manual-token'}
457
+ )
458
+ mock_send.assert_called_once()
459
+ headers = mock_send.call_args[0][0].headers
460
+ assert headers['authorization'] == 'Bearer manual-token'
461
+ mock_access_token.assert_not_called()
462
+
463
+ mock_send.reset_mock()
464
+ mock_access_token.reset_mock()
465
+
466
+ # Provide API Key
467
+ await client.aio.interactions.create(
468
+ model='gemini-1.5-flash',
469
+ input='Hello',
470
+ extra_headers={'x-goog-api-key': 'manual-key'}
471
+ )
472
+ mock_send.assert_called_once()
473
+ headers = mock_send.call_args[0][0].headers
474
+ assert headers['x-goog-api-key'] == 'manual-key'
475
+ assert 'authorization' not in headers
476
+ mock_access_token.assert_not_called()
@@ -0,0 +1,84 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from unittest import mock
16
+ import pytest
17
+ from ... import client as client_lib
18
+
19
+ pytest_plugins = ("pytest_asyncio",)
20
+
21
+
22
+ def test_client_future_warning():
23
+ with mock.patch.object(
24
+ client_lib, "_interactions_experimental_warned", new=False
25
+ ):
26
+ client = client_lib.Client(
27
+ api_key="placeholder",
28
+ http_options={
29
+ "api_version": "v1alpha",
30
+ }
31
+ )
32
+ with pytest.warns(
33
+ UserWarning, match="Interactions.*experimental"
34
+ ):
35
+ _ = client.interactions
36
+
37
+
38
+ def test_client_timeout():
39
+ with mock.patch.object(
40
+ client_lib, "GeminiNextGenAPIClient", spec_set=True
41
+ ) as mock_nextgen_client:
42
+
43
+ client = client_lib.Client(
44
+ api_key="placeholder",
45
+ http_options={"api_version": "v1alpha", "timeout": 5000},
46
+ )
47
+
48
+ _ = client.interactions
49
+
50
+ mock_nextgen_client.assert_called_once_with(
51
+ base_url=mock.ANY,
52
+ api_key="placeholder",
53
+ api_version="v1alpha",
54
+ default_headers=mock.ANY,
55
+ http_client=mock.ANY,
56
+ timeout=5.0,
57
+ max_retries=mock.ANY,
58
+ client_adapter=mock.ANY,
59
+ )
60
+
61
+
62
+ @pytest.mark.asyncio
63
+ async def test_async_client_timeout():
64
+ with mock.patch.object(
65
+ client_lib, "AsyncGeminiNextGenAPIClient", spec_set=True
66
+ ) as mock_nextgen_client:
67
+
68
+ client = client_lib.Client(
69
+ api_key="placeholder",
70
+ http_options={"api_version": "v1alpha", "timeout": 5000},
71
+ )
72
+
73
+ _ = client.aio.interactions
74
+
75
+ mock_nextgen_client.assert_called_once_with(
76
+ base_url=mock.ANY,
77
+ api_key="placeholder",
78
+ api_version="v1alpha",
79
+ default_headers=mock.ANY,
80
+ http_client=mock.ANY,
81
+ timeout=5.0,
82
+ max_retries=mock.ANY,
83
+ client_adapter=mock.ANY,
84
+ )
@@ -0,0 +1,105 @@
1
+
2
+ # Copyright 2025 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ """Tests for Interactions API URL paths."""
19
+
20
+ from unittest import mock
21
+ import pytest
22
+ from httpx import Request, Response
23
+ from ..._api_client import AsyncHttpxClient
24
+ from httpx import Client as HTTPClient
25
+ from .. import pytest_helper
26
+ import google.auth
27
+
28
+ @mock.patch.object(google.auth, "default", autospec=True)
29
+ def test_interactions_paths(mock_auth_default, client):
30
+ interaction_id = "test-interaction-id"
31
+
32
+ mock_creds = mock.Mock()
33
+ mock_creds.token = "test-token"
34
+ mock_creds.expired = False
35
+ mock_creds.quota_project_id = "test-quota-project"
36
+ mock_auth_default.return_value = (mock_creds, "test-project")
37
+
38
+ if client._api_client.vertexai:
39
+ expected_base_url = f'https://{client._api_client.location}-aiplatform.googleapis.com/v1beta1/projects/{client._api_client.project}/locations/{client._api_client.location}'
40
+ else:
41
+ expected_base_url = "https://generativelanguage.googleapis.com/v1beta"
42
+
43
+ with mock.patch.object(HTTPClient, "send") as mock_send:
44
+ mock_send.return_value = Response(200, request=Request('GET', ''))
45
+ client.interactions.get(id=interaction_id)
46
+ mock_send.assert_called_once()
47
+ request = mock_send.call_args[0][0]
48
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
49
+
50
+ mock_send.reset_mock()
51
+ mock_send.return_value = Response(200, request=Request('POST', ''))
52
+ client.interactions.cancel(id=interaction_id)
53
+ mock_send.assert_called_once()
54
+ request = mock_send.call_args[0][0]
55
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}/cancel'
56
+
57
+ mock_send.reset_mock()
58
+ mock_send.return_value = Response(200, request=Request('DELETE', ''))
59
+ client.interactions.delete(id=interaction_id)
60
+ mock_send.assert_called_once()
61
+ request = mock_send.call_args[0][0]
62
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
63
+
64
+ @pytest.mark.asyncio
65
+ @mock.patch.object(google.auth, "default", autospec=True)
66
+ async def test_async_interactions_paths(mock_auth_default, client):
67
+ interaction_id = "test-interaction-id"
68
+
69
+ mock_creds = mock.Mock()
70
+ mock_creds.token = "test-token"
71
+ mock_creds.expired = False
72
+ mock_creds.quota_project_id = "test-quota-project"
73
+ mock_auth_default.return_value = (mock_creds, "test-project")
74
+
75
+ if client._api_client.vertexai:
76
+ expected_base_url = f'https://{client._api_client.location}-aiplatform.googleapis.com/v1beta1/projects/{client._api_client.project}/locations/{client._api_client.location}'
77
+ else:
78
+ expected_base_url = "https://generativelanguage.googleapis.com/v1beta"
79
+
80
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
81
+ mock_send.return_value = Response(200, request=Request('GET', ''))
82
+ await client.aio.interactions.get(id=interaction_id)
83
+ mock_send.assert_called_once()
84
+ request = mock_send.call_args[0][0]
85
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
86
+
87
+ mock_send.reset_mock()
88
+ mock_send.return_value = Response(200, request=Request('POST', ''))
89
+ await client.aio.interactions.cancel(id=interaction_id)
90
+ mock_send.assert_called_once()
91
+ request = mock_send.call_args[0][0]
92
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}/cancel'
93
+
94
+ mock_send.reset_mock()
95
+ mock_send.return_value = Response(200, request=Request('DELETE', ''))
96
+ await client.aio.interactions.delete(id=interaction_id)
97
+ mock_send.assert_called_once()
98
+ request = mock_send.call_args[0][0]
99
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
100
+
101
+ pytestmark = pytest_helper.setup(
102
+ file=__file__,
103
+ globals_for_file=globals(),
104
+ test_table=[],
105
+ )
@@ -0,0 +1,16 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+
16
+