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,847 @@
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
+ from __future__ import annotations
17
+
18
+ import os
19
+ import inspect
20
+ import logging
21
+ import datetime
22
+ import functools
23
+ from types import TracebackType
24
+ from typing import (
25
+ TYPE_CHECKING,
26
+ Any,
27
+ Union,
28
+ Generic,
29
+ TypeVar,
30
+ Callable,
31
+ Iterator,
32
+ AsyncIterator,
33
+ cast,
34
+ overload,
35
+ )
36
+ from typing_extensions import Awaitable, ParamSpec, override, get_origin
37
+
38
+ import anyio
39
+ import httpx
40
+ import pydantic
41
+
42
+ from ._types import NoneType
43
+ from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
44
+ from ._models import BaseModel, is_basemodel
45
+ from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
46
+ from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
47
+ from ._exceptions import APIResponseValidationError, GeminiNextGenAPIClientError
48
+
49
+ if TYPE_CHECKING:
50
+ from ._models import FinalRequestOptions
51
+ from ._base_client import BaseClient
52
+
53
+
54
+ P = ParamSpec("P")
55
+ R = TypeVar("R")
56
+ _T = TypeVar("_T")
57
+ _APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]")
58
+ _AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]")
59
+
60
+ log: logging.Logger = logging.getLogger(__name__)
61
+
62
+
63
+ class BaseAPIResponse(Generic[R]):
64
+ _cast_to: type[R]
65
+ _client: BaseClient[Any, Any]
66
+ _parsed_by_type: dict[type[Any], Any]
67
+ _is_sse_stream: bool
68
+ _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
69
+ _options: FinalRequestOptions
70
+
71
+ http_response: httpx.Response
72
+
73
+ retries_taken: int
74
+ """The number of retries made. If no retries happened this will be `0`"""
75
+
76
+ def __init__(
77
+ self,
78
+ *,
79
+ raw: httpx.Response,
80
+ cast_to: type[R],
81
+ client: BaseClient[Any, Any],
82
+ stream: bool,
83
+ stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
84
+ options: FinalRequestOptions,
85
+ retries_taken: int = 0,
86
+ ) -> None:
87
+ self._cast_to = cast_to
88
+ self._client = client
89
+ self._parsed_by_type = {}
90
+ self._is_sse_stream = stream
91
+ self._stream_cls = stream_cls
92
+ self._options = options
93
+ self.http_response = raw
94
+ self.retries_taken = retries_taken
95
+
96
+ @property
97
+ def headers(self) -> httpx.Headers:
98
+ return self.http_response.headers
99
+
100
+ @property
101
+ def http_request(self) -> httpx.Request:
102
+ """Returns the httpx Request instance associated with the current response."""
103
+ return self.http_response.request
104
+
105
+ @property
106
+ def status_code(self) -> int:
107
+ return self.http_response.status_code
108
+
109
+ @property
110
+ def url(self) -> httpx.URL:
111
+ """Returns the URL for which the request was made."""
112
+ return self.http_response.url
113
+
114
+ @property
115
+ def method(self) -> str:
116
+ return self.http_request.method
117
+
118
+ @property
119
+ def http_version(self) -> str:
120
+ return self.http_response.http_version
121
+
122
+ @property
123
+ def elapsed(self) -> datetime.timedelta:
124
+ """The time taken for the complete request/response cycle to complete."""
125
+ return self.http_response.elapsed
126
+
127
+ @property
128
+ def is_closed(self) -> bool:
129
+ """Whether or not the response body has been closed.
130
+
131
+ If this is False then there is response data that has not been read yet.
132
+ You must either fully consume the response body or call `.close()`
133
+ before discarding the response to prevent resource leaks.
134
+ """
135
+ return self.http_response.is_closed
136
+
137
+ @override
138
+ def __repr__(self) -> str:
139
+ return (
140
+ f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"
141
+ )
142
+
143
+ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
144
+ cast_to = to if to is not None else self._cast_to
145
+
146
+ # unwrap `TypeAlias('Name', T)` -> `T`
147
+ if is_type_alias_type(cast_to):
148
+ cast_to = cast_to.__value__ # type: ignore[unreachable]
149
+
150
+ # unwrap `Annotated[T, ...]` -> `T`
151
+ if cast_to and is_annotated_type(cast_to):
152
+ cast_to = extract_type_arg(cast_to, 0)
153
+
154
+ origin = get_origin(cast_to) or cast_to
155
+
156
+ if self._is_sse_stream:
157
+ if to:
158
+ if not is_stream_class_type(to):
159
+ raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")
160
+
161
+ return cast(
162
+ _T,
163
+ to(
164
+ cast_to=extract_stream_chunk_type(
165
+ to,
166
+ failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
167
+ ),
168
+ response=self.http_response,
169
+ client=cast(Any, self._client),
170
+ ),
171
+ )
172
+
173
+ if self._stream_cls:
174
+ return cast(
175
+ R,
176
+ self._stream_cls(
177
+ cast_to=extract_stream_chunk_type(self._stream_cls),
178
+ response=self.http_response,
179
+ client=cast(Any, self._client),
180
+ ),
181
+ )
182
+
183
+ stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
184
+ if stream_cls is None:
185
+ raise MissingStreamClassError()
186
+
187
+ return cast(
188
+ R,
189
+ stream_cls(
190
+ cast_to=cast_to,
191
+ response=self.http_response,
192
+ client=cast(Any, self._client),
193
+ ),
194
+ )
195
+
196
+ if cast_to is NoneType:
197
+ return cast(R, None)
198
+
199
+ response = self.http_response
200
+ if cast_to == str:
201
+ return cast(R, response.text)
202
+
203
+ if cast_to == bytes:
204
+ return cast(R, response.content)
205
+
206
+ if cast_to == int:
207
+ return cast(R, int(response.text))
208
+
209
+ if cast_to == float:
210
+ return cast(R, float(response.text))
211
+
212
+ if cast_to == bool:
213
+ return cast(R, response.text.lower() == "true")
214
+
215
+ if origin == APIResponse:
216
+ raise RuntimeError("Unexpected state - cast_to is `APIResponse`")
217
+
218
+ if inspect.isclass(origin) and issubclass(origin, httpx.Response):
219
+ # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
220
+ # and pass that class to our request functions. We cannot change the variance to be either
221
+ # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
222
+ # the response class ourselves but that is something that should be supported directly in httpx
223
+ # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
224
+ if cast_to != httpx.Response:
225
+ raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
226
+ return cast(R, response)
227
+
228
+ if (
229
+ inspect.isclass(
230
+ origin # pyright: ignore[reportUnknownArgumentType]
231
+ )
232
+ and not issubclass(origin, BaseModel)
233
+ and issubclass(origin, pydantic.BaseModel)
234
+ ):
235
+ raise TypeError(
236
+ "Pydantic models must subclass our base model type, e.g. `from google.genai._interactions import BaseModel`"
237
+ )
238
+
239
+ if (
240
+ cast_to is not object
241
+ and not origin is list
242
+ and not origin is dict
243
+ and not origin is Union
244
+ and not issubclass(origin, BaseModel)
245
+ ):
246
+ raise RuntimeError(
247
+ f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
248
+ )
249
+
250
+ # split is required to handle cases where additional information is included
251
+ # in the response, e.g. application/json; charset=utf-8
252
+ content_type, *_ = response.headers.get("content-type", "*").split(";")
253
+ if not content_type.endswith("json"):
254
+ if is_basemodel(cast_to):
255
+ try:
256
+ data = response.json()
257
+ except Exception as exc:
258
+ log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
259
+ else:
260
+ return self._client._process_response_data(
261
+ data=data,
262
+ cast_to=cast_to, # type: ignore
263
+ response=response,
264
+ )
265
+
266
+ if self._client._strict_response_validation:
267
+ raise APIResponseValidationError(
268
+ response=response,
269
+ message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
270
+ body=response.text,
271
+ )
272
+
273
+ # If the API responds with content that isn't JSON then we just return
274
+ # the (decoded) text without performing any parsing so that you can still
275
+ # handle the response however you need to.
276
+ return response.text # type: ignore
277
+
278
+ data = response.json()
279
+
280
+ return self._client._process_response_data(
281
+ data=data,
282
+ cast_to=cast_to, # type: ignore
283
+ response=response,
284
+ )
285
+
286
+
287
+ class APIResponse(BaseAPIResponse[R]):
288
+ @overload
289
+ def parse(self, *, to: type[_T]) -> _T: ...
290
+
291
+ @overload
292
+ def parse(self) -> R: ...
293
+
294
+ def parse(self, *, to: type[_T] | None = None) -> R | _T:
295
+ """Returns the rich python representation of this response's data.
296
+
297
+ For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
298
+
299
+ You can customise the type that the response is parsed into through
300
+ the `to` argument, e.g.
301
+
302
+ ```py
303
+ from google.genai._interactions import BaseModel
304
+
305
+
306
+ class MyModel(BaseModel):
307
+ foo: str
308
+
309
+
310
+ obj = response.parse(to=MyModel)
311
+ print(obj.foo)
312
+ ```
313
+
314
+ We support parsing:
315
+ - `BaseModel`
316
+ - `dict`
317
+ - `list`
318
+ - `Union`
319
+ - `str`
320
+ - `int`
321
+ - `float`
322
+ - `httpx.Response`
323
+ """
324
+ cache_key = to if to is not None else self._cast_to
325
+ cached = self._parsed_by_type.get(cache_key)
326
+ if cached is not None:
327
+ return cached # type: ignore[no-any-return]
328
+
329
+ if not self._is_sse_stream:
330
+ self.read()
331
+
332
+ parsed = self._parse(to=to)
333
+ if is_given(self._options.post_parser):
334
+ parsed = self._options.post_parser(parsed)
335
+
336
+ self._parsed_by_type[cache_key] = parsed
337
+ return parsed
338
+
339
+ def read(self) -> bytes:
340
+ """Read and return the binary response content."""
341
+ try:
342
+ return self.http_response.read()
343
+ except httpx.StreamConsumed as exc:
344
+ # The default error raised by httpx isn't very
345
+ # helpful in our case so we re-raise it with
346
+ # a different error message.
347
+ raise StreamAlreadyConsumed() from exc
348
+
349
+ def text(self) -> str:
350
+ """Read and decode the response content into a string."""
351
+ self.read()
352
+ return self.http_response.text
353
+
354
+ def json(self) -> object:
355
+ """Read and decode the JSON response content."""
356
+ self.read()
357
+ return self.http_response.json()
358
+
359
+ def close(self) -> None:
360
+ """Close the response and release the connection.
361
+
362
+ Automatically called if the response body is read to completion.
363
+ """
364
+ self.http_response.close()
365
+
366
+ def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
367
+ """
368
+ A byte-iterator over the decoded response content.
369
+
370
+ This automatically handles gzip, deflate and brotli encoded responses.
371
+ """
372
+ for chunk in self.http_response.iter_bytes(chunk_size):
373
+ yield chunk
374
+
375
+ def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
376
+ """A str-iterator over the decoded response content
377
+ that handles both gzip, deflate, etc but also detects the content's
378
+ string encoding.
379
+ """
380
+ for chunk in self.http_response.iter_text(chunk_size):
381
+ yield chunk
382
+
383
+ def iter_lines(self) -> Iterator[str]:
384
+ """Like `iter_text()` but will only yield chunks for each line"""
385
+ for chunk in self.http_response.iter_lines():
386
+ yield chunk
387
+
388
+
389
+ class AsyncAPIResponse(BaseAPIResponse[R]):
390
+ @overload
391
+ async def parse(self, *, to: type[_T]) -> _T: ...
392
+
393
+ @overload
394
+ async def parse(self) -> R: ...
395
+
396
+ async def parse(self, *, to: type[_T] | None = None) -> R | _T:
397
+ """Returns the rich python representation of this response's data.
398
+
399
+ For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
400
+
401
+ You can customise the type that the response is parsed into through
402
+ the `to` argument, e.g.
403
+
404
+ ```py
405
+ from google.genai._interactions import BaseModel
406
+
407
+
408
+ class MyModel(BaseModel):
409
+ foo: str
410
+
411
+
412
+ obj = response.parse(to=MyModel)
413
+ print(obj.foo)
414
+ ```
415
+
416
+ We support parsing:
417
+ - `BaseModel`
418
+ - `dict`
419
+ - `list`
420
+ - `Union`
421
+ - `str`
422
+ - `httpx.Response`
423
+ """
424
+ cache_key = to if to is not None else self._cast_to
425
+ cached = self._parsed_by_type.get(cache_key)
426
+ if cached is not None:
427
+ return cached # type: ignore[no-any-return]
428
+
429
+ if not self._is_sse_stream:
430
+ await self.read()
431
+
432
+ parsed = self._parse(to=to)
433
+ if is_given(self._options.post_parser):
434
+ parsed = self._options.post_parser(parsed)
435
+
436
+ self._parsed_by_type[cache_key] = parsed
437
+ return parsed
438
+
439
+ async def read(self) -> bytes:
440
+ """Read and return the binary response content."""
441
+ try:
442
+ return await self.http_response.aread()
443
+ except httpx.StreamConsumed as exc:
444
+ # the default error raised by httpx isn't very
445
+ # helpful in our case so we re-raise it with
446
+ # a different error message
447
+ raise StreamAlreadyConsumed() from exc
448
+
449
+ async def text(self) -> str:
450
+ """Read and decode the response content into a string."""
451
+ await self.read()
452
+ return self.http_response.text
453
+
454
+ async def json(self) -> object:
455
+ """Read and decode the JSON response content."""
456
+ await self.read()
457
+ return self.http_response.json()
458
+
459
+ async def close(self) -> None:
460
+ """Close the response and release the connection.
461
+
462
+ Automatically called if the response body is read to completion.
463
+ """
464
+ await self.http_response.aclose()
465
+
466
+ async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
467
+ """
468
+ A byte-iterator over the decoded response content.
469
+
470
+ This automatically handles gzip, deflate and brotli encoded responses.
471
+ """
472
+ async for chunk in self.http_response.aiter_bytes(chunk_size):
473
+ yield chunk
474
+
475
+ async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
476
+ """A str-iterator over the decoded response content
477
+ that handles both gzip, deflate, etc but also detects the content's
478
+ string encoding.
479
+ """
480
+ async for chunk in self.http_response.aiter_text(chunk_size):
481
+ yield chunk
482
+
483
+ async def iter_lines(self) -> AsyncIterator[str]:
484
+ """Like `iter_text()` but will only yield chunks for each line"""
485
+ async for chunk in self.http_response.aiter_lines():
486
+ yield chunk
487
+
488
+
489
+ class BinaryAPIResponse(APIResponse[bytes]):
490
+ """Subclass of APIResponse providing helpers for dealing with binary data.
491
+
492
+ Note: If you want to stream the response data instead of eagerly reading it
493
+ all at once then you should use `.with_streaming_response` when making
494
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
495
+ """
496
+
497
+ def write_to_file(
498
+ self,
499
+ file: str | os.PathLike[str],
500
+ ) -> None:
501
+ """Write the output to the given file.
502
+
503
+ Accepts a filename or any path-like object, e.g. pathlib.Path
504
+
505
+ Note: if you want to stream the data to the file instead of writing
506
+ all at once then you should use `.with_streaming_response` when making
507
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
508
+ """
509
+ with open(file, mode="wb") as f:
510
+ for data in self.iter_bytes():
511
+ f.write(data)
512
+
513
+
514
+ class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]):
515
+ """Subclass of APIResponse providing helpers for dealing with binary data.
516
+
517
+ Note: If you want to stream the response data instead of eagerly reading it
518
+ all at once then you should use `.with_streaming_response` when making
519
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
520
+ """
521
+
522
+ async def write_to_file(
523
+ self,
524
+ file: str | os.PathLike[str],
525
+ ) -> None:
526
+ """Write the output to the given file.
527
+
528
+ Accepts a filename or any path-like object, e.g. pathlib.Path
529
+
530
+ Note: if you want to stream the data to the file instead of writing
531
+ all at once then you should use `.with_streaming_response` when making
532
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
533
+ """
534
+ path = anyio.Path(file)
535
+ async with await path.open(mode="wb") as f:
536
+ async for data in self.iter_bytes():
537
+ await f.write(data)
538
+
539
+
540
+ class StreamedBinaryAPIResponse(APIResponse[bytes]):
541
+ def stream_to_file(
542
+ self,
543
+ file: str | os.PathLike[str],
544
+ *,
545
+ chunk_size: int | None = None,
546
+ ) -> None:
547
+ """Streams the output to the given file.
548
+
549
+ Accepts a filename or any path-like object, e.g. pathlib.Path
550
+ """
551
+ with open(file, mode="wb") as f:
552
+ for data in self.iter_bytes(chunk_size):
553
+ f.write(data)
554
+
555
+
556
+ class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]):
557
+ async def stream_to_file(
558
+ self,
559
+ file: str | os.PathLike[str],
560
+ *,
561
+ chunk_size: int | None = None,
562
+ ) -> None:
563
+ """Streams the output to the given file.
564
+
565
+ Accepts a filename or any path-like object, e.g. pathlib.Path
566
+ """
567
+ path = anyio.Path(file)
568
+ async with await path.open(mode="wb") as f:
569
+ async for data in self.iter_bytes(chunk_size):
570
+ await f.write(data)
571
+
572
+
573
+ class MissingStreamClassError(TypeError):
574
+ def __init__(self) -> None:
575
+ super().__init__(
576
+ "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `google.genai._interactions._streaming` for reference",
577
+ )
578
+
579
+
580
+ class StreamAlreadyConsumed(GeminiNextGenAPIClientError):
581
+ """
582
+ Attempted to read or stream content, but the content has already
583
+ been streamed.
584
+
585
+ This can happen if you use a method like `.iter_lines()` and then attempt
586
+ to read th entire response body afterwards, e.g.
587
+
588
+ ```py
589
+ response = await client.post(...)
590
+ async for line in response.iter_lines():
591
+ ... # do something with `line`
592
+
593
+ content = await response.read()
594
+ # ^ error
595
+ ```
596
+
597
+ If you want this behaviour you'll need to either manually accumulate the response
598
+ content or call `await response.read()` before iterating over the stream.
599
+ """
600
+
601
+ def __init__(self) -> None:
602
+ message = (
603
+ "Attempted to read or stream some content, but the content has "
604
+ "already been streamed. "
605
+ "This could be due to attempting to stream the response "
606
+ "content more than once."
607
+ "\n\n"
608
+ "You can fix this by manually accumulating the response content while streaming "
609
+ "or by calling `.read()` before starting to stream."
610
+ )
611
+ super().__init__(message)
612
+
613
+
614
+ class ResponseContextManager(Generic[_APIResponseT]):
615
+ """Context manager for ensuring that a request is not made
616
+ until it is entered and that the response will always be closed
617
+ when the context manager exits
618
+ """
619
+
620
+ def __init__(self, request_func: Callable[[], _APIResponseT]) -> None:
621
+ self._request_func = request_func
622
+ self.__response: _APIResponseT | None = None
623
+
624
+ def __enter__(self) -> _APIResponseT:
625
+ self.__response = self._request_func()
626
+ return self.__response
627
+
628
+ def __exit__(
629
+ self,
630
+ exc_type: type[BaseException] | None,
631
+ exc: BaseException | None,
632
+ exc_tb: TracebackType | None,
633
+ ) -> None:
634
+ if self.__response is not None:
635
+ self.__response.close()
636
+
637
+
638
+ class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]):
639
+ """Context manager for ensuring that a request is not made
640
+ until it is entered and that the response will always be closed
641
+ when the context manager exits
642
+ """
643
+
644
+ def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None:
645
+ self._api_request = api_request
646
+ self.__response: _AsyncAPIResponseT | None = None
647
+
648
+ async def __aenter__(self) -> _AsyncAPIResponseT:
649
+ self.__response = await self._api_request
650
+ return self.__response
651
+
652
+ async def __aexit__(
653
+ self,
654
+ exc_type: type[BaseException] | None,
655
+ exc: BaseException | None,
656
+ exc_tb: TracebackType | None,
657
+ ) -> None:
658
+ if self.__response is not None:
659
+ await self.__response.close()
660
+
661
+
662
+ def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]:
663
+ """Higher order function that takes one of our bound API methods and wraps it
664
+ to support streaming and returning the raw `APIResponse` object directly.
665
+ """
666
+
667
+ @functools.wraps(func)
668
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]:
669
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
670
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
671
+
672
+ kwargs["extra_headers"] = extra_headers
673
+
674
+ make_request = functools.partial(func, *args, **kwargs)
675
+
676
+ return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request))
677
+
678
+ return wrapped
679
+
680
+
681
+ def async_to_streamed_response_wrapper(
682
+ func: Callable[P, Awaitable[R]],
683
+ ) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]:
684
+ """Higher order function that takes one of our bound API methods and wraps it
685
+ to support streaming and returning the raw `APIResponse` object directly.
686
+ """
687
+
688
+ @functools.wraps(func)
689
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]:
690
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
691
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
692
+
693
+ kwargs["extra_headers"] = extra_headers
694
+
695
+ make_request = func(*args, **kwargs)
696
+
697
+ return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request))
698
+
699
+ return wrapped
700
+
701
+
702
+ def to_custom_streamed_response_wrapper(
703
+ func: Callable[P, object],
704
+ response_cls: type[_APIResponseT],
705
+ ) -> Callable[P, ResponseContextManager[_APIResponseT]]:
706
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
707
+ and wraps the method to support streaming and returning the given response class directly.
708
+
709
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
710
+ """
711
+
712
+ @functools.wraps(func)
713
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]:
714
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
715
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
716
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
717
+
718
+ kwargs["extra_headers"] = extra_headers
719
+
720
+ make_request = functools.partial(func, *args, **kwargs)
721
+
722
+ return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request))
723
+
724
+ return wrapped
725
+
726
+
727
+ def async_to_custom_streamed_response_wrapper(
728
+ func: Callable[P, Awaitable[object]],
729
+ response_cls: type[_AsyncAPIResponseT],
730
+ ) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]:
731
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
732
+ and wraps the method to support streaming and returning the given response class directly.
733
+
734
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
735
+ """
736
+
737
+ @functools.wraps(func)
738
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]:
739
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
740
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
741
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
742
+
743
+ kwargs["extra_headers"] = extra_headers
744
+
745
+ make_request = func(*args, **kwargs)
746
+
747
+ return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request))
748
+
749
+ return wrapped
750
+
751
+
752
+ def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
753
+ """Higher order function that takes one of our bound API methods and wraps it
754
+ to support returning the raw `APIResponse` object directly.
755
+ """
756
+
757
+ @functools.wraps(func)
758
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]:
759
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
760
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
761
+
762
+ kwargs["extra_headers"] = extra_headers
763
+
764
+ return cast(APIResponse[R], func(*args, **kwargs))
765
+
766
+ return wrapped
767
+
768
+
769
+ def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]:
770
+ """Higher order function that takes one of our bound API methods and wraps it
771
+ to support returning the raw `APIResponse` object directly.
772
+ """
773
+
774
+ @functools.wraps(func)
775
+ async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]:
776
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
777
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
778
+
779
+ kwargs["extra_headers"] = extra_headers
780
+
781
+ return cast(AsyncAPIResponse[R], await func(*args, **kwargs))
782
+
783
+ return wrapped
784
+
785
+
786
+ def to_custom_raw_response_wrapper(
787
+ func: Callable[P, object],
788
+ response_cls: type[_APIResponseT],
789
+ ) -> Callable[P, _APIResponseT]:
790
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
791
+ and wraps the method to support returning the given response class directly.
792
+
793
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
794
+ """
795
+
796
+ @functools.wraps(func)
797
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT:
798
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
799
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
800
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
801
+
802
+ kwargs["extra_headers"] = extra_headers
803
+
804
+ return cast(_APIResponseT, func(*args, **kwargs))
805
+
806
+ return wrapped
807
+
808
+
809
+ def async_to_custom_raw_response_wrapper(
810
+ func: Callable[P, Awaitable[object]],
811
+ response_cls: type[_AsyncAPIResponseT],
812
+ ) -> Callable[P, Awaitable[_AsyncAPIResponseT]]:
813
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
814
+ and wraps the method to support returning the given response class directly.
815
+
816
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
817
+ """
818
+
819
+ @functools.wraps(func)
820
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]:
821
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
822
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
823
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
824
+
825
+ kwargs["extra_headers"] = extra_headers
826
+
827
+ return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs))
828
+
829
+ return wrapped
830
+
831
+
832
+ def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type:
833
+ """Given a type like `APIResponse[T]`, returns the generic type variable `T`.
834
+
835
+ This also handles the case where a concrete subclass is given, e.g.
836
+ ```py
837
+ class MyResponse(APIResponse[bytes]):
838
+ ...
839
+
840
+ extract_response_type(MyResponse) -> bytes
841
+ ```
842
+ """
843
+ return extract_type_var_from_base(
844
+ typ,
845
+ generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)),
846
+ index=0,
847
+ )