google-genai 1.55.0__py3-none-any.whl → 1.56.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 (241) hide show
  1. google/genai/_interactions/_base_client.py +8 -2
  2. google/genai/_interactions/resources/interactions.py +6 -6
  3. google/genai/_interactions/types/__init__.py +2 -0
  4. google/genai/_interactions/types/audio_content.py +0 -1
  5. google/genai/_interactions/types/audio_content_param.py +0 -1
  6. google/genai/_interactions/types/code_execution_call_content.py +0 -1
  7. google/genai/_interactions/types/code_execution_call_content_param.py +0 -1
  8. google/genai/_interactions/types/code_execution_result_content.py +0 -1
  9. google/genai/_interactions/types/code_execution_result_content_param.py +0 -1
  10. google/genai/_interactions/types/content_delta.py +7 -23
  11. google/genai/_interactions/types/deep_research_agent_config.py +0 -1
  12. google/genai/_interactions/types/deep_research_agent_config_param.py +0 -1
  13. google/genai/_interactions/types/document_content.py +3 -2
  14. google/genai/_interactions/types/document_content_param.py +3 -2
  15. google/genai/{tests/__init__.py → _interactions/types/document_mime_type.py} +5 -3
  16. google/genai/{tests/afc/__init__.py → _interactions/types/document_mime_type_param.py} +8 -4
  17. google/genai/_interactions/types/dynamic_agent_config.py +0 -1
  18. google/genai/_interactions/types/dynamic_agent_config_param.py +0 -1
  19. google/genai/_interactions/types/file_search_result_content.py +0 -1
  20. google/genai/_interactions/types/file_search_result_content_param.py +0 -1
  21. google/genai/_interactions/types/function_call_content.py +0 -1
  22. google/genai/_interactions/types/function_call_content_param.py +0 -1
  23. google/genai/_interactions/types/function_result_content.py +1 -2
  24. google/genai/_interactions/types/function_result_content_param.py +1 -2
  25. google/genai/_interactions/types/google_search_call_content.py +0 -1
  26. google/genai/_interactions/types/google_search_call_content_param.py +0 -1
  27. google/genai/_interactions/types/google_search_result_content.py +0 -1
  28. google/genai/_interactions/types/google_search_result_content_param.py +0 -1
  29. google/genai/_interactions/types/image_content.py +1 -2
  30. google/genai/_interactions/types/image_content_param.py +1 -2
  31. google/genai/_interactions/types/mcp_server_tool_call_content.py +0 -1
  32. google/genai/_interactions/types/mcp_server_tool_call_content_param.py +0 -1
  33. google/genai/_interactions/types/mcp_server_tool_result_content.py +1 -2
  34. google/genai/_interactions/types/mcp_server_tool_result_content_param.py +1 -2
  35. google/genai/_interactions/types/text_content.py +0 -1
  36. google/genai/_interactions/types/text_content_param.py +0 -1
  37. google/genai/_interactions/types/thinking_level.py +1 -1
  38. google/genai/_interactions/types/thought_content.py +0 -1
  39. google/genai/_interactions/types/thought_content_param.py +0 -1
  40. google/genai/_interactions/types/url_context_call_content.py +0 -1
  41. google/genai/_interactions/types/url_context_call_content_param.py +0 -1
  42. google/genai/_interactions/types/url_context_result_content.py +0 -1
  43. google/genai/_interactions/types/url_context_result_content_param.py +0 -1
  44. google/genai/_interactions/types/video_content.py +1 -2
  45. google/genai/_interactions/types/video_content_param.py +1 -2
  46. google/genai/_live_converters.py +2 -30
  47. google/genai/client.py +3 -1
  48. google/genai/models.py +2 -29
  49. google/genai/tunings.py +1 -27
  50. google/genai/types.py +20 -22
  51. google/genai/version.py +1 -1
  52. {google_genai-1.55.0.dist-info → google_genai-1.56.0.dist-info}/METADATA +224 -22
  53. google_genai-1.56.0.dist-info/RECORD +162 -0
  54. google/genai/tests/afc/test_convert_if_exist_pydantic_model.py +0 -309
  55. google/genai/tests/afc/test_convert_number_values_for_function_call_args.py +0 -63
  56. google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py +0 -240
  57. google/genai/tests/afc/test_generate_content_stream_afc.py +0 -530
  58. google/genai/tests/afc/test_generate_content_stream_afc_thoughts.py +0 -77
  59. google/genai/tests/afc/test_get_function_map.py +0 -176
  60. google/genai/tests/afc/test_get_function_response_parts.py +0 -277
  61. google/genai/tests/afc/test_get_max_remote_calls_for_afc.py +0 -130
  62. google/genai/tests/afc/test_invoke_function_from_dict_args.py +0 -241
  63. google/genai/tests/afc/test_raise_error_for_afc_incompatible_config.py +0 -159
  64. google/genai/tests/afc/test_should_append_afc_history.py +0 -53
  65. google/genai/tests/afc/test_should_disable_afc.py +0 -214
  66. google/genai/tests/batches/__init__.py +0 -17
  67. google/genai/tests/batches/test_cancel.py +0 -77
  68. google/genai/tests/batches/test_create.py +0 -78
  69. google/genai/tests/batches/test_create_with_bigquery.py +0 -113
  70. google/genai/tests/batches/test_create_with_file.py +0 -82
  71. google/genai/tests/batches/test_create_with_gcs.py +0 -125
  72. google/genai/tests/batches/test_create_with_inlined_requests.py +0 -255
  73. google/genai/tests/batches/test_delete.py +0 -86
  74. google/genai/tests/batches/test_embedding.py +0 -157
  75. google/genai/tests/batches/test_get.py +0 -78
  76. google/genai/tests/batches/test_list.py +0 -79
  77. google/genai/tests/caches/__init__.py +0 -17
  78. google/genai/tests/caches/constants.py +0 -29
  79. google/genai/tests/caches/test_create.py +0 -210
  80. google/genai/tests/caches/test_create_custom_url.py +0 -105
  81. google/genai/tests/caches/test_delete.py +0 -54
  82. google/genai/tests/caches/test_delete_custom_url.py +0 -52
  83. google/genai/tests/caches/test_get.py +0 -94
  84. google/genai/tests/caches/test_get_custom_url.py +0 -52
  85. google/genai/tests/caches/test_list.py +0 -68
  86. google/genai/tests/caches/test_update.py +0 -70
  87. google/genai/tests/caches/test_update_custom_url.py +0 -58
  88. google/genai/tests/chats/__init__.py +0 -1
  89. google/genai/tests/chats/test_get_history.py +0 -597
  90. google/genai/tests/chats/test_send_message.py +0 -844
  91. google/genai/tests/chats/test_validate_response.py +0 -90
  92. google/genai/tests/client/__init__.py +0 -17
  93. google/genai/tests/client/test_async_stream.py +0 -427
  94. google/genai/tests/client/test_client_close.py +0 -197
  95. google/genai/tests/client/test_client_initialization.py +0 -1687
  96. google/genai/tests/client/test_client_requests.py +0 -355
  97. google/genai/tests/client/test_custom_client.py +0 -77
  98. google/genai/tests/client/test_http_options.py +0 -178
  99. google/genai/tests/client/test_replay_client_equality.py +0 -168
  100. google/genai/tests/client/test_retries.py +0 -846
  101. google/genai/tests/client/test_upload_errors.py +0 -136
  102. google/genai/tests/common/__init__.py +0 -17
  103. google/genai/tests/common/test_common.py +0 -954
  104. google/genai/tests/conftest.py +0 -162
  105. google/genai/tests/documents/__init__.py +0 -17
  106. google/genai/tests/documents/test_delete.py +0 -51
  107. google/genai/tests/documents/test_get.py +0 -85
  108. google/genai/tests/documents/test_list.py +0 -72
  109. google/genai/tests/errors/__init__.py +0 -1
  110. google/genai/tests/errors/test_api_error.py +0 -417
  111. google/genai/tests/file_search_stores/__init__.py +0 -17
  112. google/genai/tests/file_search_stores/test_create.py +0 -66
  113. google/genai/tests/file_search_stores/test_delete.py +0 -64
  114. google/genai/tests/file_search_stores/test_get.py +0 -94
  115. google/genai/tests/file_search_stores/test_import_file.py +0 -112
  116. google/genai/tests/file_search_stores/test_list.py +0 -57
  117. google/genai/tests/file_search_stores/test_upload_to_file_search_store.py +0 -141
  118. google/genai/tests/files/__init__.py +0 -17
  119. google/genai/tests/files/test_delete.py +0 -46
  120. google/genai/tests/files/test_download.py +0 -85
  121. google/genai/tests/files/test_get.py +0 -46
  122. google/genai/tests/files/test_list.py +0 -72
  123. google/genai/tests/files/test_upload.py +0 -255
  124. google/genai/tests/imports/test_no_optional_imports.py +0 -28
  125. google/genai/tests/interactions/test_integration.py +0 -80
  126. google/genai/tests/live/__init__.py +0 -16
  127. google/genai/tests/live/test_live.py +0 -2177
  128. google/genai/tests/live/test_live_music.py +0 -362
  129. google/genai/tests/live/test_live_response.py +0 -163
  130. google/genai/tests/live/test_send_client_content.py +0 -147
  131. google/genai/tests/live/test_send_realtime_input.py +0 -268
  132. google/genai/tests/live/test_send_tool_response.py +0 -222
  133. google/genai/tests/local_tokenizer/__init__.py +0 -17
  134. google/genai/tests/local_tokenizer/test_local_tokenizer.py +0 -343
  135. google/genai/tests/local_tokenizer/test_local_tokenizer_loader.py +0 -235
  136. google/genai/tests/mcp/__init__.py +0 -17
  137. google/genai/tests/mcp/test_has_mcp_tool_usage.py +0 -89
  138. google/genai/tests/mcp/test_mcp_to_gemini_tools.py +0 -191
  139. google/genai/tests/mcp/test_parse_config_for_mcp_sessions.py +0 -201
  140. google/genai/tests/mcp/test_parse_config_for_mcp_usage.py +0 -130
  141. google/genai/tests/mcp/test_set_mcp_usage_header.py +0 -72
  142. google/genai/tests/models/__init__.py +0 -17
  143. google/genai/tests/models/constants.py +0 -8
  144. google/genai/tests/models/test_compute_tokens.py +0 -120
  145. google/genai/tests/models/test_count_tokens.py +0 -159
  146. google/genai/tests/models/test_delete.py +0 -107
  147. google/genai/tests/models/test_edit_image.py +0 -264
  148. google/genai/tests/models/test_embed_content.py +0 -94
  149. google/genai/tests/models/test_function_call_streaming.py +0 -442
  150. google/genai/tests/models/test_generate_content.py +0 -2502
  151. google/genai/tests/models/test_generate_content_cached_content.py +0 -132
  152. google/genai/tests/models/test_generate_content_config_zero_value.py +0 -103
  153. google/genai/tests/models/test_generate_content_from_apikey.py +0 -44
  154. google/genai/tests/models/test_generate_content_http_options.py +0 -40
  155. google/genai/tests/models/test_generate_content_image_generation.py +0 -143
  156. google/genai/tests/models/test_generate_content_mcp.py +0 -343
  157. google/genai/tests/models/test_generate_content_media_resolution.py +0 -97
  158. google/genai/tests/models/test_generate_content_model.py +0 -139
  159. google/genai/tests/models/test_generate_content_part.py +0 -821
  160. google/genai/tests/models/test_generate_content_thought.py +0 -76
  161. google/genai/tests/models/test_generate_content_tools.py +0 -1761
  162. google/genai/tests/models/test_generate_images.py +0 -191
  163. google/genai/tests/models/test_generate_videos.py +0 -759
  164. google/genai/tests/models/test_get.py +0 -104
  165. google/genai/tests/models/test_list.py +0 -233
  166. google/genai/tests/models/test_recontext_image.py +0 -189
  167. google/genai/tests/models/test_segment_image.py +0 -148
  168. google/genai/tests/models/test_update.py +0 -95
  169. google/genai/tests/models/test_upscale_image.py +0 -157
  170. google/genai/tests/operations/__init__.py +0 -17
  171. google/genai/tests/operations/test_get.py +0 -38
  172. google/genai/tests/public_samples/__init__.py +0 -17
  173. google/genai/tests/public_samples/test_gemini_text_only.py +0 -34
  174. google/genai/tests/pytest_helper.py +0 -229
  175. google/genai/tests/shared/__init__.py +0 -16
  176. google/genai/tests/shared/batches/__init__.py +0 -14
  177. google/genai/tests/shared/batches/test_create_delete.py +0 -57
  178. google/genai/tests/shared/batches/test_create_get_cancel.py +0 -56
  179. google/genai/tests/shared/batches/test_list.py +0 -40
  180. google/genai/tests/shared/caches/__init__.py +0 -14
  181. google/genai/tests/shared/caches/test_create_get_delete.py +0 -67
  182. google/genai/tests/shared/caches/test_create_update_get.py +0 -71
  183. google/genai/tests/shared/caches/test_list.py +0 -40
  184. google/genai/tests/shared/chats/__init__.py +0 -14
  185. google/genai/tests/shared/chats/test_send_message.py +0 -48
  186. google/genai/tests/shared/chats/test_send_message_stream.py +0 -50
  187. google/genai/tests/shared/files/__init__.py +0 -14
  188. google/genai/tests/shared/files/test_list.py +0 -41
  189. google/genai/tests/shared/files/test_upload_get_delete.py +0 -54
  190. google/genai/tests/shared/models/__init__.py +0 -14
  191. google/genai/tests/shared/models/test_compute_tokens.py +0 -41
  192. google/genai/tests/shared/models/test_count_tokens.py +0 -40
  193. google/genai/tests/shared/models/test_edit_image.py +0 -67
  194. google/genai/tests/shared/models/test_embed.py +0 -40
  195. google/genai/tests/shared/models/test_generate_content.py +0 -39
  196. google/genai/tests/shared/models/test_generate_content_stream.py +0 -54
  197. google/genai/tests/shared/models/test_generate_images.py +0 -40
  198. google/genai/tests/shared/models/test_generate_videos.py +0 -38
  199. google/genai/tests/shared/models/test_list.py +0 -37
  200. google/genai/tests/shared/models/test_recontext_image.py +0 -55
  201. google/genai/tests/shared/models/test_segment_image.py +0 -52
  202. google/genai/tests/shared/models/test_upscale_image.py +0 -52
  203. google/genai/tests/shared/tunings/__init__.py +0 -16
  204. google/genai/tests/shared/tunings/test_create.py +0 -46
  205. google/genai/tests/shared/tunings/test_create_get_cancel.py +0 -56
  206. google/genai/tests/shared/tunings/test_list.py +0 -39
  207. google/genai/tests/tokens/__init__.py +0 -16
  208. google/genai/tests/tokens/test_create.py +0 -154
  209. google/genai/tests/transformers/__init__.py +0 -17
  210. google/genai/tests/transformers/test_blobs.py +0 -71
  211. google/genai/tests/transformers/test_bytes.py +0 -15
  212. google/genai/tests/transformers/test_duck_type.py +0 -96
  213. google/genai/tests/transformers/test_function_responses.py +0 -72
  214. google/genai/tests/transformers/test_schema.py +0 -653
  215. google/genai/tests/transformers/test_t_batch.py +0 -286
  216. google/genai/tests/transformers/test_t_content.py +0 -160
  217. google/genai/tests/transformers/test_t_contents.py +0 -398
  218. google/genai/tests/transformers/test_t_part.py +0 -85
  219. google/genai/tests/transformers/test_t_parts.py +0 -87
  220. google/genai/tests/transformers/test_t_tool.py +0 -157
  221. google/genai/tests/transformers/test_t_tools.py +0 -195
  222. google/genai/tests/tunings/__init__.py +0 -16
  223. google/genai/tests/tunings/test_cancel.py +0 -39
  224. google/genai/tests/tunings/test_end_to_end.py +0 -106
  225. google/genai/tests/tunings/test_get.py +0 -67
  226. google/genai/tests/tunings/test_list.py +0 -75
  227. google/genai/tests/tunings/test_tune.py +0 -268
  228. google/genai/tests/types/__init__.py +0 -16
  229. google/genai/tests/types/test_bytes_internal.py +0 -271
  230. google/genai/tests/types/test_bytes_type.py +0 -152
  231. google/genai/tests/types/test_future.py +0 -101
  232. google/genai/tests/types/test_optional_types.py +0 -36
  233. google/genai/tests/types/test_part_type.py +0 -616
  234. google/genai/tests/types/test_schema_from_json_schema.py +0 -417
  235. google/genai/tests/types/test_schema_json_schema.py +0 -468
  236. google/genai/tests/types/test_types.py +0 -2903
  237. google_genai-1.55.0.dist-info/RECORD +0 -345
  238. /google/genai/{tests/interactions/__init__.py → _interactions/py.typed} +0 -0
  239. {google_genai-1.55.0.dist-info → google_genai-1.56.0.dist-info}/WHEEL +0 -0
  240. {google_genai-1.55.0.dist-info → google_genai-1.56.0.dist-info}/licenses/LICENSE +0 -0
  241. {google_genai-1.55.0.dist-info → google_genai-1.56.0.dist-info}/top_level.txt +0 -0
@@ -1,90 +0,0 @@
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
- from ... import types
18
- from ...chats import _validate_response
19
-
20
-
21
- def test_validate_response_default_response():
22
- response = types.GenerateContentResponse()
23
-
24
- assert not _validate_response(response)
25
-
26
-
27
- def test_validate_response_empty_content():
28
- response = types.GenerateContentResponse(candidates=[])
29
-
30
- assert not _validate_response(response)
31
-
32
-
33
- def test_validate_response_empty_parts():
34
- response = types.GenerateContentResponse(
35
- candidates=[types.Candidate(content=types.Content(parts=[]))]
36
- )
37
-
38
- assert not _validate_response(response)
39
-
40
-
41
- def test_validate_response_empty_part():
42
- response = types.GenerateContentResponse(
43
- candidates=[types.Candidate(content=types.Content(parts=[types.Part()]))]
44
- )
45
-
46
- assert not _validate_response(response)
47
-
48
-
49
- def test_validate_response_part_with_empty_text():
50
- response = types.GenerateContentResponse(
51
- candidates=[
52
- types.Candidate(content=types.Content(parts=[types.Part(text='')]))
53
- ]
54
- )
55
-
56
- assert not _validate_response(response)
57
-
58
-
59
- def test_validate_response_part_with_text():
60
- response = types.GenerateContentResponse(
61
- candidates=[
62
- types.Candidate(
63
- content=types.Content(
64
- parts=[types.Part(text='response from model')]
65
- )
66
- )
67
- ]
68
- )
69
-
70
- assert _validate_response(response)
71
-
72
-
73
- def test_validate_response_part_with_function_call():
74
- response = types.GenerateContentResponse(
75
- candidates=[
76
- types.Candidate(
77
- content=types.Content(
78
- parts=[
79
- types.Part(
80
- function_call=types.FunctionCall(
81
- name='foo', args={'bar': 'baz'}
82
- )
83
- )
84
- ]
85
- )
86
- )
87
- ]
88
- )
89
-
90
- assert _validate_response(response)
@@ -1,17 +0,0 @@
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 the Google GenAI SDK."""
@@ -1,427 +0,0 @@
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
- """Tests for async stream."""
17
-
18
- import asyncio
19
- from typing import List
20
- from unittest import mock
21
- from unittest.mock import AsyncMock
22
- from unittest.mock import MagicMock
23
- from unittest.mock import patch
24
- import pytest
25
-
26
-
27
- try:
28
- import aiohttp
29
-
30
- AIOHTTP_NOT_INSTALLED = False
31
- except ImportError:
32
- AIOHTTP_NOT_INSTALLED = True
33
- aiohttp = mock.MagicMock()
34
-
35
- import httpx
36
-
37
- from ... import _api_client as api_client
38
- from ... import Client
39
- from ... import errors
40
- from ... import types
41
-
42
-
43
- EVENT_STREAM_DATA_WITH_ERROR = [
44
- b'{"candidates":[{"content":{"parts":[{"text":"test"}],"role":"model"}}]}',
45
- b'\n',
46
- b'{"error":{"code":500,"message":"Error","status":"INTERNAL"}}',
47
- ]
48
-
49
-
50
- class MockHTTPXResponse(httpx.Response):
51
- """Mock httpx.Response class for testing."""
52
-
53
- def __init__(self, lines: List[str]):
54
- self.aiter_lines = MagicMock()
55
- self.aiter_lines.return_value.__aiter__ = MagicMock(
56
- return_value=self._async_line_iterator(lines)
57
- )
58
- self.aclose = AsyncMock()
59
-
60
- async def _async_line_iterator(self, lines: List[str]):
61
- for line in lines:
62
- yield line
63
-
64
-
65
- class MockAIOHTTPResponse(aiohttp.ClientResponse):
66
-
67
- def __init__(self, lines: List[str]):
68
- self.content = MagicMock()
69
- self.content.readline = AsyncMock()
70
- # Simulate reading lines, each ending with newline bytes for readline behavior
71
- self._read_data = b"\n".join(line.encode("utf-8") for line in lines) + b"\n"
72
- self._read_pos = 0
73
- self.content.readline.side_effect = self._async_read_line
74
- self.release = MagicMock()
75
-
76
- async def _async_read_line(self) -> bytes:
77
- if self._read_pos >= len(self._read_data):
78
- return b"" # End of stream
79
-
80
- newline_pos = self._read_data.find(b"\n", self._read_pos)
81
- if newline_pos == -1: # Should not happen with the appended '\n'
82
- line = self._read_data[self._read_pos :]
83
- self._read_pos = len(self._read_data)
84
- return line
85
- else:
86
- line = self._read_data[self._read_pos : newline_pos + 1]
87
- self._read_pos = newline_pos + 1
88
- return line
89
-
90
-
91
- @pytest.fixture
92
- def responses() -> api_client.HttpResponse:
93
- return api_client.HttpResponse(headers={})
94
-
95
-
96
- requires_aiohttp = pytest.mark.skipif(
97
- AIOHTTP_NOT_INSTALLED, reason="aiohttp is not installed, skipping test."
98
- )
99
-
100
-
101
- @pytest.fixture(autouse=True)
102
- def reset_has_aiohttp():
103
- yield
104
- api_client.has_aiohttp = False
105
-
106
-
107
- def test_invalid_response_stream_type(responses: api_client.HttpResponse):
108
- """Tests that an invalid response stream type raises an error."""
109
- api_client.has_aiohttp = False
110
- with pytest.raises(
111
- TypeError,
112
- match=(
113
- "Expected self.response_stream to be an httpx.Response or"
114
- " aiohttp.ClientResponse object"
115
- ),
116
- ):
117
-
118
- async def run():
119
- async for _ in responses._aiter_response_stream():
120
- pass
121
-
122
- asyncio.run(run())
123
-
124
-
125
- @pytest.mark.asyncio
126
- async def test_httpx_simple_lines(responses: api_client.HttpResponse):
127
- lines = ["hello", "world", "testing"]
128
- mock_response = MockHTTPXResponse(lines)
129
- responses.response_stream = mock_response
130
-
131
- results = [line async for line in responses._aiter_response_stream()]
132
-
133
- assert results == lines
134
- mock_response.aiter_lines.assert_called_once()
135
- mock_response.aclose.assert_called_once()
136
-
137
-
138
- @pytest.mark.asyncio
139
- async def test_httpx_data_prefix(responses: api_client.HttpResponse):
140
- lines = ["data: { 'message': 'hello' }", "data: { 'status': 'ok' }"]
141
- mock_response = MockHTTPXResponse(lines)
142
- responses.response_stream = mock_response
143
-
144
- results = [line async for line in responses._aiter_response_stream()]
145
-
146
- assert results == ["{ 'message': 'hello' }", "{ 'status': 'ok' }"]
147
- mock_response.aiter_lines.assert_called_once()
148
- mock_response.aclose.assert_called_once()
149
-
150
-
151
- @pytest.mark.asyncio
152
- async def test_httpx_multiple_json_chunk(responses: api_client.HttpResponse):
153
- lines = [
154
- '{ "id": 1 }',
155
- "",
156
- 'data: { "id": 2 }',
157
- 'data: { "id": 3 }',
158
- ]
159
- mock_response = MockHTTPXResponse(lines)
160
- responses.response_stream = mock_response
161
-
162
- results = [line async for line in responses._aiter_response_stream()]
163
-
164
- assert results == ['{ "id": 1 }', '{ "id": 2 }', '{ "id": 3 }']
165
- mock_response.aiter_lines.assert_called_once()
166
- mock_response.aclose.assert_called_once()
167
-
168
-
169
- @pytest.mark.asyncio
170
- async def test_httpx_incomplete_json_at_end(responses: api_client.HttpResponse):
171
- lines = ['{ "partial": "data"'] # Missing closing brace
172
- mock_response = MockHTTPXResponse(lines)
173
- responses.response_stream = mock_response
174
-
175
- results = [line async for line in responses._aiter_response_stream()]
176
-
177
- # The remaining chunk is yielded
178
- assert results == ['{ "partial": "data"']
179
- mock_response.aiter_lines.assert_called_once()
180
- mock_response.aclose.assert_called_once()
181
-
182
-
183
- @pytest.mark.asyncio
184
- async def test_httpx_empty_stream(responses: api_client.HttpResponse):
185
- lines: List[str] = []
186
- mock_response = MockHTTPXResponse(lines)
187
- responses.response_stream = mock_response
188
-
189
- results = [line async for line in responses._aiter_response_stream()]
190
-
191
- assert results == []
192
- mock_response.aiter_lines.assert_called_once()
193
- mock_response.aclose.assert_called_once()
194
-
195
-
196
- # Async aiohttp
197
- @requires_aiohttp
198
- @pytest.mark.asyncio
199
- async def test_aiohttp_simple_lines(responses: api_client.HttpResponse):
200
- api_client.has_aiohttp = True # Force aiohttp
201
- lines = ["hello", "world", "testing"]
202
- # Use the mock class that pretends to be aiohttp.ClientResponse
203
- mock_response = MockAIOHTTPResponse(lines)
204
- responses.response_stream = mock_response
205
-
206
- results = [line async for line in responses._aiter_response_stream()]
207
-
208
- assert results == lines
209
- mock_response.content.readline.assert_any_call()
210
- mock_response.release.assert_called_once()
211
-
212
-
213
- @requires_aiohttp
214
- @pytest.mark.asyncio
215
- async def test_aiohttp_data_prefix(responses: api_client.HttpResponse):
216
- api_client.has_aiohttp = True # Force aiohttp
217
- lines = ["data: { 'message': 'hello' }", "data: { 'status': 'ok' }"]
218
- # Use the mock class that pretends to be aiohttp.ClientResponse
219
- mock_response = MockAIOHTTPResponse(lines)
220
- responses.response_stream = mock_response
221
-
222
- results = [line async for line in responses._aiter_response_stream()]
223
-
224
- assert results == ["{ 'message': 'hello' }", "{ 'status': 'ok' }"]
225
- mock_response.content.readline.assert_any_call()
226
- mock_response.release.assert_called_once()
227
-
228
-
229
- @requires_aiohttp
230
- @pytest.mark.asyncio
231
- async def test_aiohttp_multiple_json_chunks(responses: api_client.HttpResponse):
232
- api_client.has_aiohttp = True # Force aiohttp
233
- lines = [
234
- '{ "id": 1 }',
235
- "", # empty line to check robustness
236
- 'data: { "id": 2 }',
237
- 'data: { "id": 3 }',
238
- ]
239
- # Use the mock class that pretends to be aiohttp.ClientResponse
240
- mock_response = MockAIOHTTPResponse(lines)
241
- responses.response_stream = mock_response
242
-
243
- results = [line async for line in responses._aiter_response_stream()]
244
-
245
- assert results == ['{ "id": 1 }', '{ "id": 2 }', '{ "id": 3 }']
246
- mock_response.content.readline.assert_any_call()
247
- mock_response.release.assert_called_once()
248
-
249
-
250
- @requires_aiohttp
251
- @pytest.mark.asyncio
252
- async def test_aiohttp_incomplete_json_at_end(
253
- responses: api_client.HttpResponse,
254
- ):
255
- api_client.has_aiohttp = True # Force aiohttp
256
- lines = ['{ "partial": "data"'] # Missing closing brace
257
- # Use the mock class that pretends to be aiohttp.ClientResponse
258
- mock_response = MockAIOHTTPResponse(lines)
259
- responses.response_stream = mock_response
260
-
261
- results = [line async for line in responses._aiter_response_stream()]
262
-
263
- assert results == ['{ "partial": "data"']
264
- mock_response.content.readline.assert_any_call()
265
- mock_response.release.assert_called_once()
266
-
267
-
268
- def mock_response(chunks):
269
- mock_stream = MagicMock(spec=httpx.SyncByteStream)
270
- mock_stream.__iter__.return_value = chunks
271
- return httpx.Response(
272
- status_code=200,
273
- stream=mock_stream,
274
- )
275
-
276
-
277
- @patch('httpx.Client.send')
278
- def test_error_event_in_streamed_responses_bad_json(mock_send_method):
279
- with_bad_json = [
280
- b'{"candidates":[{"content":{"parts":[{"text":"test"}],"role":"model"}}]}',
281
- b'\n',
282
- b'{"error": bad_json}',
283
- ]
284
- mock_send_method.return_value = mock_response(with_bad_json)
285
-
286
- client = api_client.BaseApiClient(api_key='test_api_key')
287
- stream = client.request_streamed('POST', 'models/gemini-2.5-flash', {})
288
-
289
- chunk = next(stream)
290
- assert chunk == types.HttpResponse(
291
- headers={},
292
- body=(
293
- '{"candidates": [{"content": {"parts": [{"text": "test"}], "role":'
294
- ' "model"}}]}'
295
- ),
296
- )
297
-
298
- with pytest.raises(errors.UnknownApiResponseError):
299
- next(stream)
300
-
301
-
302
- @patch('httpx.Client.send')
303
- def test_error_event_in_streamed_responses(mock_send_method):
304
- mock_send_method.return_value = mock_response(EVENT_STREAM_DATA_WITH_ERROR)
305
-
306
- client = api_client.BaseApiClient(api_key='test_api_key')
307
- stream = client.request_streamed('POST', 'models/gemini-2.5-flash', {})
308
-
309
- chunk = next(stream)
310
- assert chunk == types.HttpResponse(
311
- body=(
312
- '{"candidates": [{"content": {"parts": [{"text": "test"}], "role":'
313
- ' "model"}}]}'
314
- ),
315
- headers={},
316
- )
317
-
318
- with pytest.raises(errors.ServerError):
319
- next(stream)
320
-
321
-
322
- @patch('httpx.Client.send')
323
- def test_error_event_in_generate_content_stream(mock_send_method):
324
- mock_send_method.return_value = mock_response(EVENT_STREAM_DATA_WITH_ERROR)
325
-
326
- client = Client(api_key='test_api_key')
327
- generated_response = client.models.generate_content_stream(
328
- model='gemini-2.5-flash',
329
- contents='Tell me a story in 300 words.',
330
- )
331
-
332
- chunk = next(generated_response)
333
- assert chunk == types.GenerateContentResponse(
334
- candidates=[
335
- types.Candidate(
336
- content=types.Content(
337
- parts=[
338
- types.Part(
339
- text='test'
340
- ),
341
- ],
342
- role='model'
343
- )
344
- ),
345
- ],
346
- sdk_http_response=types.HttpResponse(headers={})
347
- )
348
-
349
- with pytest.raises(errors.ServerError):
350
- next(generated_response)
351
-
352
-
353
- async def _async_httpx_response(_):
354
- mock_stream = MagicMock(spec=httpx.AsyncByteStream)
355
- mock_stream.__aiter__.return_value = EVENT_STREAM_DATA_WITH_ERROR
356
- mock_stream.aclose = AsyncMock()
357
- return httpx.Response(
358
- status_code=200,
359
- stream=mock_stream,
360
- )
361
-
362
-
363
- @patch('httpx.AsyncBaseTransport')
364
- @pytest.mark.asyncio
365
- async def test_error_event_in_streamed_responses_async(mock_transport):
366
- client = api_client.BaseApiClient(
367
- api_key='test_api_key',
368
- http_options=types.HttpOptions(
369
- async_client_args={'transport': mock_transport}
370
- ),
371
- )
372
- mock_transport.handle_async_request = _async_httpx_response
373
- mock_transport.aclose = AsyncMock()
374
-
375
- resp = await client.async_request_streamed(
376
- 'POST', 'models/gemini-2.5-flash', {'key': 'value'}
377
- )
378
-
379
- chunk = await anext(resp)
380
- assert chunk == types.HttpResponse(
381
- headers={},
382
- body=(
383
- '{"candidates": [{"content": {"parts": [{"text": "test"}], "role":'
384
- ' "model"}}]}'
385
- ),
386
- )
387
-
388
- with pytest.raises(errors.ServerError):
389
- await anext(resp)
390
-
391
-
392
- @patch('httpx.AsyncBaseTransport')
393
- @pytest.mark.asyncio
394
- async def test_error_event_in_generate_content_stream_async(mock_transport):
395
- client = Client(
396
- api_key='test_api_key',
397
- http_options=types.HttpOptions(
398
- async_client_args={'transport': mock_transport}
399
- ),
400
- )
401
- mock_transport.handle_async_request = _async_httpx_response
402
- mock_transport.aclose = AsyncMock()
403
-
404
- generated_response = await client.aio.models.generate_content_stream(
405
- model='gemini-2.5-flash',
406
- contents='Tell me a story in 300 words.',
407
- )
408
-
409
- chunk = await anext(generated_response)
410
- assert chunk == types.GenerateContentResponse(
411
- candidates=[
412
- types.Candidate(
413
- content=types.Content(
414
- parts=[
415
- types.Part(
416
- text='test'
417
- ),
418
- ],
419
- role='model'
420
- )
421
- ),
422
- ],
423
- sdk_http_response=types.HttpResponse(headers={})
424
- )
425
-
426
- with pytest.raises(errors.ServerError):
427
- await anext(generated_response)