google-genai 1.54.0__py3-none-any.whl → 1.55.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 (319) hide show
  1. google/genai/__init__.py +1 -0
  2. google/genai/_interactions/__init__.py +117 -0
  3. google/genai/_interactions/_base_client.py +2019 -0
  4. google/genai/_interactions/_client.py +511 -0
  5. google/genai/_interactions/_compat.py +234 -0
  6. google/genai/_interactions/_constants.py +29 -0
  7. google/genai/_interactions/_exceptions.py +122 -0
  8. google/genai/_interactions/_files.py +139 -0
  9. google/genai/_interactions/_models.py +873 -0
  10. google/genai/_interactions/_qs.py +165 -0
  11. google/genai/_interactions/_resource.py +58 -0
  12. google/genai/_interactions/_response.py +847 -0
  13. google/genai/_interactions/_streaming.py +354 -0
  14. google/genai/_interactions/_types.py +276 -0
  15. google/genai/_interactions/_utils/__init__.py +79 -0
  16. google/genai/_interactions/_utils/_compat.py +61 -0
  17. google/genai/_interactions/_utils/_datetime_parse.py +151 -0
  18. google/genai/_interactions/_utils/_logs.py +40 -0
  19. google/genai/_interactions/_utils/_proxy.py +80 -0
  20. google/genai/_interactions/_utils/_reflection.py +57 -0
  21. google/genai/_interactions/_utils/_resources_proxy.py +39 -0
  22. google/genai/_interactions/_utils/_streams.py +27 -0
  23. google/genai/_interactions/_utils/_sync.py +73 -0
  24. google/genai/_interactions/_utils/_transform.py +472 -0
  25. google/genai/_interactions/_utils/_typing.py +172 -0
  26. google/genai/_interactions/_utils/_utils.py +437 -0
  27. google/genai/_interactions/_version.py +18 -0
  28. google/genai/_interactions/resources/__init__.py +34 -0
  29. google/genai/_interactions/resources/interactions.py +1350 -0
  30. google/genai/_interactions/types/__init__.py +107 -0
  31. google/genai/_interactions/types/allowed_tools.py +33 -0
  32. google/genai/_interactions/types/allowed_tools_param.py +35 -0
  33. google/genai/_interactions/types/annotation.py +42 -0
  34. google/genai/_interactions/types/annotation_param.py +42 -0
  35. google/genai/_interactions/types/audio_content.py +38 -0
  36. google/genai/_interactions/types/audio_content_param.py +45 -0
  37. google/genai/_interactions/types/audio_mime_type.py +25 -0
  38. google/genai/_interactions/types/audio_mime_type_param.py +27 -0
  39. google/genai/_interactions/types/code_execution_call_arguments.py +33 -0
  40. google/genai/_interactions/types/code_execution_call_arguments_param.py +32 -0
  41. google/genai/_interactions/types/code_execution_call_content.py +37 -0
  42. google/genai/_interactions/types/code_execution_call_content_param.py +37 -0
  43. google/genai/_interactions/types/code_execution_result_content.py +42 -0
  44. google/genai/_interactions/types/code_execution_result_content_param.py +41 -0
  45. google/genai/_interactions/types/content_delta.py +358 -0
  46. google/genai/_interactions/types/content_start.py +79 -0
  47. google/genai/_interactions/types/content_stop.py +35 -0
  48. google/genai/_interactions/types/deep_research_agent_config.py +33 -0
  49. google/genai/_interactions/types/deep_research_agent_config_param.py +32 -0
  50. google/genai/_interactions/types/document_content.py +36 -0
  51. google/genai/_interactions/types/document_content_param.py +43 -0
  52. google/genai/_interactions/types/dynamic_agent_config.py +44 -0
  53. google/genai/_interactions/types/dynamic_agent_config_param.py +33 -0
  54. google/genai/_interactions/types/error_event.py +46 -0
  55. google/genai/_interactions/types/file_search_result_content.py +46 -0
  56. google/genai/_interactions/types/file_search_result_content_param.py +46 -0
  57. google/genai/_interactions/types/function.py +38 -0
  58. google/genai/_interactions/types/function_call_content.py +39 -0
  59. google/genai/_interactions/types/function_call_content_param.py +39 -0
  60. google/genai/_interactions/types/function_param.py +37 -0
  61. google/genai/_interactions/types/function_result_content.py +52 -0
  62. google/genai/_interactions/types/function_result_content_param.py +54 -0
  63. google/genai/_interactions/types/generation_config.py +57 -0
  64. google/genai/_interactions/types/generation_config_param.py +59 -0
  65. google/genai/_interactions/types/google_search_call_arguments.py +29 -0
  66. google/genai/_interactions/types/google_search_call_arguments_param.py +31 -0
  67. google/genai/_interactions/types/google_search_call_content.py +37 -0
  68. google/genai/_interactions/types/google_search_call_content_param.py +37 -0
  69. google/genai/_interactions/types/google_search_result.py +35 -0
  70. google/genai/_interactions/types/google_search_result_content.py +43 -0
  71. google/genai/_interactions/types/google_search_result_content_param.py +44 -0
  72. google/genai/_interactions/types/google_search_result_param.py +35 -0
  73. google/genai/_interactions/types/image_content.py +41 -0
  74. google/genai/_interactions/types/image_content_param.py +48 -0
  75. google/genai/_interactions/types/image_mime_type.py +23 -0
  76. google/genai/_interactions/types/image_mime_type_param.py +25 -0
  77. google/genai/_interactions/types/interaction.py +165 -0
  78. google/genai/_interactions/types/interaction_create_params.py +212 -0
  79. google/genai/_interactions/types/interaction_event.py +37 -0
  80. google/genai/_interactions/types/interaction_get_params.py +46 -0
  81. google/genai/_interactions/types/interaction_sse_event.py +32 -0
  82. google/genai/_interactions/types/interaction_status_update.py +37 -0
  83. google/genai/_interactions/types/mcp_server_tool_call_content.py +42 -0
  84. google/genai/_interactions/types/mcp_server_tool_call_content_param.py +42 -0
  85. google/genai/_interactions/types/mcp_server_tool_result_content.py +52 -0
  86. google/genai/_interactions/types/mcp_server_tool_result_content_param.py +54 -0
  87. google/genai/_interactions/types/model.py +36 -0
  88. google/genai/_interactions/types/model_param.py +38 -0
  89. google/genai/_interactions/types/speech_config.py +35 -0
  90. google/genai/_interactions/types/speech_config_param.py +35 -0
  91. google/genai/_interactions/types/text_content.py +37 -0
  92. google/genai/_interactions/types/text_content_param.py +38 -0
  93. google/genai/_interactions/types/thinking_level.py +22 -0
  94. google/genai/_interactions/types/thought_content.py +41 -0
  95. google/genai/_interactions/types/thought_content_param.py +47 -0
  96. google/genai/_interactions/types/tool.py +100 -0
  97. google/genai/_interactions/types/tool_choice.py +26 -0
  98. google/genai/_interactions/types/tool_choice_config.py +28 -0
  99. google/genai/_interactions/types/tool_choice_config_param.py +29 -0
  100. google/genai/_interactions/types/tool_choice_param.py +28 -0
  101. google/genai/_interactions/types/tool_choice_type.py +22 -0
  102. google/genai/_interactions/types/tool_param.py +97 -0
  103. google/genai/_interactions/types/turn.py +76 -0
  104. google/genai/_interactions/types/turn_param.py +73 -0
  105. google/genai/_interactions/types/url_context_call_arguments.py +29 -0
  106. google/genai/_interactions/types/url_context_call_arguments_param.py +31 -0
  107. google/genai/_interactions/types/url_context_call_content.py +37 -0
  108. google/genai/_interactions/types/url_context_call_content_param.py +37 -0
  109. google/genai/_interactions/types/url_context_result.py +33 -0
  110. google/genai/_interactions/types/url_context_result_content.py +43 -0
  111. google/genai/_interactions/types/url_context_result_content_param.py +44 -0
  112. google/genai/_interactions/types/url_context_result_param.py +32 -0
  113. google/genai/_interactions/types/usage.py +106 -0
  114. google/genai/_interactions/types/usage_param.py +106 -0
  115. google/genai/_interactions/types/video_content.py +41 -0
  116. google/genai/_interactions/types/video_content_param.py +48 -0
  117. google/genai/_interactions/types/video_mime_type.py +36 -0
  118. google/genai/_interactions/types/video_mime_type_param.py +38 -0
  119. google/genai/_live_converters.py +31 -0
  120. google/genai/_tokens_converters.py +5 -0
  121. google/genai/batches.py +7 -0
  122. google/genai/client.py +223 -0
  123. google/genai/interactions.py +17 -0
  124. google/genai/live.py +4 -3
  125. google/genai/models.py +12 -0
  126. google/genai/tests/__init__.py +21 -0
  127. google/genai/tests/afc/__init__.py +21 -0
  128. google/genai/tests/afc/test_convert_if_exist_pydantic_model.py +309 -0
  129. google/genai/tests/afc/test_convert_number_values_for_function_call_args.py +63 -0
  130. google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py +240 -0
  131. google/genai/tests/afc/test_generate_content_stream_afc.py +530 -0
  132. google/genai/tests/afc/test_generate_content_stream_afc_thoughts.py +77 -0
  133. google/genai/tests/afc/test_get_function_map.py +176 -0
  134. google/genai/tests/afc/test_get_function_response_parts.py +277 -0
  135. google/genai/tests/afc/test_get_max_remote_calls_for_afc.py +130 -0
  136. google/genai/tests/afc/test_invoke_function_from_dict_args.py +241 -0
  137. google/genai/tests/afc/test_raise_error_for_afc_incompatible_config.py +159 -0
  138. google/genai/tests/afc/test_should_append_afc_history.py +53 -0
  139. google/genai/tests/afc/test_should_disable_afc.py +214 -0
  140. google/genai/tests/batches/__init__.py +17 -0
  141. google/genai/tests/batches/test_cancel.py +77 -0
  142. google/genai/tests/batches/test_create.py +78 -0
  143. google/genai/tests/batches/test_create_with_bigquery.py +113 -0
  144. google/genai/tests/batches/test_create_with_file.py +82 -0
  145. google/genai/tests/batches/test_create_with_gcs.py +125 -0
  146. google/genai/tests/batches/test_create_with_inlined_requests.py +255 -0
  147. google/genai/tests/batches/test_delete.py +86 -0
  148. google/genai/tests/batches/test_embedding.py +157 -0
  149. google/genai/tests/batches/test_get.py +78 -0
  150. google/genai/tests/batches/test_list.py +79 -0
  151. google/genai/tests/caches/__init__.py +17 -0
  152. google/genai/tests/caches/constants.py +29 -0
  153. google/genai/tests/caches/test_create.py +210 -0
  154. google/genai/tests/caches/test_create_custom_url.py +105 -0
  155. google/genai/tests/caches/test_delete.py +54 -0
  156. google/genai/tests/caches/test_delete_custom_url.py +52 -0
  157. google/genai/tests/caches/test_get.py +94 -0
  158. google/genai/tests/caches/test_get_custom_url.py +52 -0
  159. google/genai/tests/caches/test_list.py +68 -0
  160. google/genai/tests/caches/test_update.py +70 -0
  161. google/genai/tests/caches/test_update_custom_url.py +58 -0
  162. google/genai/tests/chats/__init__.py +1 -0
  163. google/genai/tests/chats/test_get_history.py +597 -0
  164. google/genai/tests/chats/test_send_message.py +844 -0
  165. google/genai/tests/chats/test_validate_response.py +90 -0
  166. google/genai/tests/client/__init__.py +17 -0
  167. google/genai/tests/client/test_async_stream.py +427 -0
  168. google/genai/tests/client/test_client_close.py +197 -0
  169. google/genai/tests/client/test_client_initialization.py +1687 -0
  170. google/genai/tests/client/test_client_requests.py +355 -0
  171. google/genai/tests/client/test_custom_client.py +77 -0
  172. google/genai/tests/client/test_http_options.py +178 -0
  173. google/genai/tests/client/test_replay_client_equality.py +168 -0
  174. google/genai/tests/client/test_retries.py +846 -0
  175. google/genai/tests/client/test_upload_errors.py +136 -0
  176. google/genai/tests/common/__init__.py +17 -0
  177. google/genai/tests/common/test_common.py +954 -0
  178. google/genai/tests/conftest.py +162 -0
  179. google/genai/tests/documents/__init__.py +17 -0
  180. google/genai/tests/documents/test_delete.py +51 -0
  181. google/genai/tests/documents/test_get.py +85 -0
  182. google/genai/tests/documents/test_list.py +72 -0
  183. google/genai/tests/errors/__init__.py +1 -0
  184. google/genai/tests/errors/test_api_error.py +417 -0
  185. google/genai/tests/file_search_stores/__init__.py +17 -0
  186. google/genai/tests/file_search_stores/test_create.py +66 -0
  187. google/genai/tests/file_search_stores/test_delete.py +64 -0
  188. google/genai/tests/file_search_stores/test_get.py +94 -0
  189. google/genai/tests/file_search_stores/test_import_file.py +112 -0
  190. google/genai/tests/file_search_stores/test_list.py +57 -0
  191. google/genai/tests/file_search_stores/test_upload_to_file_search_store.py +141 -0
  192. google/genai/tests/files/__init__.py +17 -0
  193. google/genai/tests/files/test_delete.py +46 -0
  194. google/genai/tests/files/test_download.py +85 -0
  195. google/genai/tests/files/test_get.py +46 -0
  196. google/genai/tests/files/test_list.py +72 -0
  197. google/genai/tests/files/test_upload.py +255 -0
  198. google/genai/tests/imports/test_no_optional_imports.py +28 -0
  199. google/genai/tests/interactions/__init__.py +0 -0
  200. google/genai/tests/interactions/test_integration.py +80 -0
  201. google/genai/tests/live/__init__.py +16 -0
  202. google/genai/tests/live/test_live.py +2177 -0
  203. google/genai/tests/live/test_live_music.py +362 -0
  204. google/genai/tests/live/test_live_response.py +163 -0
  205. google/genai/tests/live/test_send_client_content.py +147 -0
  206. google/genai/tests/live/test_send_realtime_input.py +268 -0
  207. google/genai/tests/live/test_send_tool_response.py +222 -0
  208. google/genai/tests/local_tokenizer/__init__.py +17 -0
  209. google/genai/tests/local_tokenizer/test_local_tokenizer.py +343 -0
  210. google/genai/tests/local_tokenizer/test_local_tokenizer_loader.py +235 -0
  211. google/genai/tests/mcp/__init__.py +17 -0
  212. google/genai/tests/mcp/test_has_mcp_tool_usage.py +89 -0
  213. google/genai/tests/mcp/test_mcp_to_gemini_tools.py +191 -0
  214. google/genai/tests/mcp/test_parse_config_for_mcp_sessions.py +201 -0
  215. google/genai/tests/mcp/test_parse_config_for_mcp_usage.py +130 -0
  216. google/genai/tests/mcp/test_set_mcp_usage_header.py +72 -0
  217. google/genai/tests/models/__init__.py +17 -0
  218. google/genai/tests/models/constants.py +8 -0
  219. google/genai/tests/models/test_compute_tokens.py +120 -0
  220. google/genai/tests/models/test_count_tokens.py +159 -0
  221. google/genai/tests/models/test_delete.py +107 -0
  222. google/genai/tests/models/test_edit_image.py +264 -0
  223. google/genai/tests/models/test_embed_content.py +94 -0
  224. google/genai/tests/models/test_function_call_streaming.py +442 -0
  225. google/genai/tests/models/test_generate_content.py +2502 -0
  226. google/genai/tests/models/test_generate_content_cached_content.py +132 -0
  227. google/genai/tests/models/test_generate_content_config_zero_value.py +103 -0
  228. google/genai/tests/models/test_generate_content_from_apikey.py +44 -0
  229. google/genai/tests/models/test_generate_content_http_options.py +40 -0
  230. google/genai/tests/models/test_generate_content_image_generation.py +143 -0
  231. google/genai/tests/models/test_generate_content_mcp.py +343 -0
  232. google/genai/tests/models/test_generate_content_media_resolution.py +97 -0
  233. google/genai/tests/models/test_generate_content_model.py +139 -0
  234. google/genai/tests/models/test_generate_content_part.py +821 -0
  235. google/genai/tests/models/test_generate_content_thought.py +76 -0
  236. google/genai/tests/models/test_generate_content_tools.py +1761 -0
  237. google/genai/tests/models/test_generate_images.py +191 -0
  238. google/genai/tests/models/test_generate_videos.py +759 -0
  239. google/genai/tests/models/test_get.py +104 -0
  240. google/genai/tests/models/test_list.py +233 -0
  241. google/genai/tests/models/test_recontext_image.py +189 -0
  242. google/genai/tests/models/test_segment_image.py +148 -0
  243. google/genai/tests/models/test_update.py +95 -0
  244. google/genai/tests/models/test_upscale_image.py +157 -0
  245. google/genai/tests/operations/__init__.py +17 -0
  246. google/genai/tests/operations/test_get.py +38 -0
  247. google/genai/tests/public_samples/__init__.py +17 -0
  248. google/genai/tests/public_samples/test_gemini_text_only.py +34 -0
  249. google/genai/tests/pytest_helper.py +229 -0
  250. google/genai/tests/shared/__init__.py +16 -0
  251. google/genai/tests/shared/batches/__init__.py +14 -0
  252. google/genai/tests/shared/batches/test_create_delete.py +57 -0
  253. google/genai/tests/shared/batches/test_create_get_cancel.py +56 -0
  254. google/genai/tests/shared/batches/test_list.py +40 -0
  255. google/genai/tests/shared/caches/__init__.py +14 -0
  256. google/genai/tests/shared/caches/test_create_get_delete.py +67 -0
  257. google/genai/tests/shared/caches/test_create_update_get.py +71 -0
  258. google/genai/tests/shared/caches/test_list.py +40 -0
  259. google/genai/tests/shared/chats/__init__.py +14 -0
  260. google/genai/tests/shared/chats/test_send_message.py +48 -0
  261. google/genai/tests/shared/chats/test_send_message_stream.py +50 -0
  262. google/genai/tests/shared/files/__init__.py +14 -0
  263. google/genai/tests/shared/files/test_list.py +41 -0
  264. google/genai/tests/shared/files/test_upload_get_delete.py +54 -0
  265. google/genai/tests/shared/models/__init__.py +14 -0
  266. google/genai/tests/shared/models/test_compute_tokens.py +41 -0
  267. google/genai/tests/shared/models/test_count_tokens.py +40 -0
  268. google/genai/tests/shared/models/test_edit_image.py +67 -0
  269. google/genai/tests/shared/models/test_embed.py +40 -0
  270. google/genai/tests/shared/models/test_generate_content.py +39 -0
  271. google/genai/tests/shared/models/test_generate_content_stream.py +54 -0
  272. google/genai/tests/shared/models/test_generate_images.py +40 -0
  273. google/genai/tests/shared/models/test_generate_videos.py +38 -0
  274. google/genai/tests/shared/models/test_list.py +37 -0
  275. google/genai/tests/shared/models/test_recontext_image.py +55 -0
  276. google/genai/tests/shared/models/test_segment_image.py +52 -0
  277. google/genai/tests/shared/models/test_upscale_image.py +52 -0
  278. google/genai/tests/shared/tunings/__init__.py +16 -0
  279. google/genai/tests/shared/tunings/test_create.py +46 -0
  280. google/genai/tests/shared/tunings/test_create_get_cancel.py +56 -0
  281. google/genai/tests/shared/tunings/test_list.py +39 -0
  282. google/genai/tests/tokens/__init__.py +16 -0
  283. google/genai/tests/tokens/test_create.py +154 -0
  284. google/genai/tests/transformers/__init__.py +17 -0
  285. google/genai/tests/transformers/test_blobs.py +71 -0
  286. google/genai/tests/transformers/test_bytes.py +15 -0
  287. google/genai/tests/transformers/test_duck_type.py +96 -0
  288. google/genai/tests/transformers/test_function_responses.py +72 -0
  289. google/genai/tests/transformers/test_schema.py +653 -0
  290. google/genai/tests/transformers/test_t_batch.py +286 -0
  291. google/genai/tests/transformers/test_t_content.py +160 -0
  292. google/genai/tests/transformers/test_t_contents.py +398 -0
  293. google/genai/tests/transformers/test_t_part.py +85 -0
  294. google/genai/tests/transformers/test_t_parts.py +87 -0
  295. google/genai/tests/transformers/test_t_tool.py +157 -0
  296. google/genai/tests/transformers/test_t_tools.py +195 -0
  297. google/genai/tests/tunings/__init__.py +16 -0
  298. google/genai/tests/tunings/test_cancel.py +39 -0
  299. google/genai/tests/tunings/test_end_to_end.py +106 -0
  300. google/genai/tests/tunings/test_get.py +67 -0
  301. google/genai/tests/tunings/test_list.py +75 -0
  302. google/genai/tests/tunings/test_tune.py +268 -0
  303. google/genai/tests/types/__init__.py +16 -0
  304. google/genai/tests/types/test_bytes_internal.py +271 -0
  305. google/genai/tests/types/test_bytes_type.py +152 -0
  306. google/genai/tests/types/test_future.py +101 -0
  307. google/genai/tests/types/test_optional_types.py +36 -0
  308. google/genai/tests/types/test_part_type.py +616 -0
  309. google/genai/tests/types/test_schema_from_json_schema.py +417 -0
  310. google/genai/tests/types/test_schema_json_schema.py +468 -0
  311. google/genai/tests/types/test_types.py +2903 -0
  312. google/genai/types.py +72 -0
  313. google/genai/version.py +1 -1
  314. {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/METADATA +3 -1
  315. google_genai-1.55.0.dist-info/RECORD +345 -0
  316. google_genai-1.54.0.dist-info/RECORD +0 -41
  317. {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/WHEEL +0 -0
  318. {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/licenses/LICENSE +0 -0
  319. {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,90 @@
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)
@@ -0,0 +1,17 @@
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."""
@@ -0,0 +1,427 @@
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)