mirascope 1.22.4__py3-none-any.whl → 2.1.1__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 (812) hide show
  1. mirascope/__init__.py +5 -50
  2. mirascope/_stubs.py +384 -0
  3. mirascope/_utils.py +34 -0
  4. mirascope/api/__init__.py +14 -0
  5. mirascope/api/_generated/README.md +207 -0
  6. mirascope/api/_generated/__init__.py +444 -0
  7. mirascope/api/_generated/annotations/__init__.py +33 -0
  8. mirascope/api/_generated/annotations/client.py +506 -0
  9. mirascope/api/_generated/annotations/raw_client.py +1414 -0
  10. mirascope/api/_generated/annotations/types/__init__.py +31 -0
  11. mirascope/api/_generated/annotations/types/annotations_create_request_label.py +5 -0
  12. mirascope/api/_generated/annotations/types/annotations_create_response.py +48 -0
  13. mirascope/api/_generated/annotations/types/annotations_create_response_label.py +5 -0
  14. mirascope/api/_generated/annotations/types/annotations_get_response.py +48 -0
  15. mirascope/api/_generated/annotations/types/annotations_get_response_label.py +5 -0
  16. mirascope/api/_generated/annotations/types/annotations_list_request_label.py +5 -0
  17. mirascope/api/_generated/annotations/types/annotations_list_response.py +21 -0
  18. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +50 -0
  19. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item_label.py +5 -0
  20. mirascope/api/_generated/annotations/types/annotations_update_request_label.py +5 -0
  21. mirascope/api/_generated/annotations/types/annotations_update_response.py +48 -0
  22. mirascope/api/_generated/annotations/types/annotations_update_response_label.py +5 -0
  23. mirascope/api/_generated/api_keys/__init__.py +17 -0
  24. mirascope/api/_generated/api_keys/client.py +530 -0
  25. mirascope/api/_generated/api_keys/raw_client.py +1236 -0
  26. mirascope/api/_generated/api_keys/types/__init__.py +15 -0
  27. mirascope/api/_generated/api_keys/types/api_keys_create_response.py +28 -0
  28. mirascope/api/_generated/api_keys/types/api_keys_get_response.py +27 -0
  29. mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
  30. mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +27 -0
  31. mirascope/api/_generated/client.py +211 -0
  32. mirascope/api/_generated/core/__init__.py +52 -0
  33. mirascope/api/_generated/core/api_error.py +23 -0
  34. mirascope/api/_generated/core/client_wrapper.py +46 -0
  35. mirascope/api/_generated/core/datetime_utils.py +28 -0
  36. mirascope/api/_generated/core/file.py +67 -0
  37. mirascope/api/_generated/core/force_multipart.py +16 -0
  38. mirascope/api/_generated/core/http_client.py +543 -0
  39. mirascope/api/_generated/core/http_response.py +55 -0
  40. mirascope/api/_generated/core/jsonable_encoder.py +100 -0
  41. mirascope/api/_generated/core/pydantic_utilities.py +255 -0
  42. mirascope/api/_generated/core/query_encoder.py +58 -0
  43. mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
  44. mirascope/api/_generated/core/request_options.py +35 -0
  45. mirascope/api/_generated/core/serialization.py +276 -0
  46. mirascope/api/_generated/docs/__init__.py +4 -0
  47. mirascope/api/_generated/docs/client.py +91 -0
  48. mirascope/api/_generated/docs/raw_client.py +178 -0
  49. mirascope/api/_generated/environment.py +9 -0
  50. mirascope/api/_generated/environments/__init__.py +23 -0
  51. mirascope/api/_generated/environments/client.py +649 -0
  52. mirascope/api/_generated/environments/raw_client.py +1567 -0
  53. mirascope/api/_generated/environments/types/__init__.py +25 -0
  54. mirascope/api/_generated/environments/types/environments_create_response.py +24 -0
  55. mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
  56. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
  57. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_models_item.py +22 -0
  58. mirascope/api/_generated/environments/types/environments_get_response.py +24 -0
  59. mirascope/api/_generated/environments/types/environments_list_response_item.py +24 -0
  60. mirascope/api/_generated/environments/types/environments_update_response.py +24 -0
  61. mirascope/api/_generated/errors/__init__.py +25 -0
  62. mirascope/api/_generated/errors/bad_request_error.py +14 -0
  63. mirascope/api/_generated/errors/conflict_error.py +14 -0
  64. mirascope/api/_generated/errors/forbidden_error.py +11 -0
  65. mirascope/api/_generated/errors/internal_server_error.py +10 -0
  66. mirascope/api/_generated/errors/not_found_error.py +11 -0
  67. mirascope/api/_generated/errors/payment_required_error.py +15 -0
  68. mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
  69. mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
  70. mirascope/api/_generated/errors/unauthorized_error.py +11 -0
  71. mirascope/api/_generated/functions/__init__.py +39 -0
  72. mirascope/api/_generated/functions/client.py +647 -0
  73. mirascope/api/_generated/functions/raw_client.py +1890 -0
  74. mirascope/api/_generated/functions/types/__init__.py +53 -0
  75. mirascope/api/_generated/functions/types/functions_create_request_dependencies_value.py +20 -0
  76. mirascope/api/_generated/functions/types/functions_create_response.py +37 -0
  77. mirascope/api/_generated/functions/types/functions_create_response_dependencies_value.py +20 -0
  78. mirascope/api/_generated/functions/types/functions_find_by_hash_response.py +39 -0
  79. mirascope/api/_generated/functions/types/functions_find_by_hash_response_dependencies_value.py +20 -0
  80. mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
  81. mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
  82. mirascope/api/_generated/functions/types/functions_get_response.py +37 -0
  83. mirascope/api/_generated/functions/types/functions_get_response_dependencies_value.py +20 -0
  84. mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
  85. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
  86. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
  87. mirascope/api/_generated/functions/types/functions_list_response.py +21 -0
  88. mirascope/api/_generated/functions/types/functions_list_response_functions_item.py +41 -0
  89. mirascope/api/_generated/functions/types/functions_list_response_functions_item_dependencies_value.py +20 -0
  90. mirascope/api/_generated/health/__init__.py +7 -0
  91. mirascope/api/_generated/health/client.py +92 -0
  92. mirascope/api/_generated/health/raw_client.py +175 -0
  93. mirascope/api/_generated/health/types/__init__.py +8 -0
  94. mirascope/api/_generated/health/types/health_check_response.py +22 -0
  95. mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
  96. mirascope/api/_generated/organization_invitations/__init__.py +33 -0
  97. mirascope/api/_generated/organization_invitations/client.py +546 -0
  98. mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
  99. mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
  100. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
  101. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
  102. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
  103. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
  104. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
  105. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
  106. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
  107. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
  108. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
  109. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
  110. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
  111. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
  112. mirascope/api/_generated/organization_memberships/__init__.py +19 -0
  113. mirascope/api/_generated/organization_memberships/client.py +302 -0
  114. mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
  115. mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
  116. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
  117. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
  118. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
  119. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
  120. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
  121. mirascope/api/_generated/organizations/__init__.py +51 -0
  122. mirascope/api/_generated/organizations/client.py +869 -0
  123. mirascope/api/_generated/organizations/raw_client.py +2593 -0
  124. mirascope/api/_generated/organizations/types/__init__.py +71 -0
  125. mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
  126. mirascope/api/_generated/organizations/types/organizations_create_response.py +26 -0
  127. mirascope/api/_generated/organizations/types/organizations_create_response_role.py +5 -0
  128. mirascope/api/_generated/organizations/types/organizations_get_response.py +26 -0
  129. mirascope/api/_generated/organizations/types/organizations_get_response_role.py +5 -0
  130. mirascope/api/_generated/organizations/types/organizations_list_response_item.py +26 -0
  131. mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +5 -0
  132. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
  133. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
  134. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
  135. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
  136. mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
  137. mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
  138. mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
  139. mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
  140. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
  141. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
  142. mirascope/api/_generated/organizations/types/organizations_update_response.py +26 -0
  143. mirascope/api/_generated/organizations/types/organizations_update_response_role.py +5 -0
  144. mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
  145. mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
  146. mirascope/api/_generated/project_memberships/__init__.py +29 -0
  147. mirascope/api/_generated/project_memberships/client.py +528 -0
  148. mirascope/api/_generated/project_memberships/raw_client.py +1278 -0
  149. mirascope/api/_generated/project_memberships/types/__init__.py +33 -0
  150. mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
  151. mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
  152. mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
  153. mirascope/api/_generated/project_memberships/types/project_memberships_get_response.py +33 -0
  154. mirascope/api/_generated/project_memberships/types/project_memberships_get_response_role.py +7 -0
  155. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
  156. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
  157. mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
  158. mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
  159. mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
  160. mirascope/api/_generated/projects/__init__.py +7 -0
  161. mirascope/api/_generated/projects/client.py +428 -0
  162. mirascope/api/_generated/projects/raw_client.py +1302 -0
  163. mirascope/api/_generated/projects/types/__init__.py +10 -0
  164. mirascope/api/_generated/projects/types/projects_create_response.py +25 -0
  165. mirascope/api/_generated/projects/types/projects_get_response.py +25 -0
  166. mirascope/api/_generated/projects/types/projects_list_response_item.py +25 -0
  167. mirascope/api/_generated/projects/types/projects_update_response.py +25 -0
  168. mirascope/api/_generated/reference.md +4987 -0
  169. mirascope/api/_generated/tags/__init__.py +19 -0
  170. mirascope/api/_generated/tags/client.py +504 -0
  171. mirascope/api/_generated/tags/raw_client.py +1288 -0
  172. mirascope/api/_generated/tags/types/__init__.py +17 -0
  173. mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
  174. mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
  175. mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
  176. mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
  177. mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
  178. mirascope/api/_generated/token_cost/__init__.py +7 -0
  179. mirascope/api/_generated/token_cost/client.py +160 -0
  180. mirascope/api/_generated/token_cost/raw_client.py +264 -0
  181. mirascope/api/_generated/token_cost/types/__init__.py +8 -0
  182. mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
  183. mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
  184. mirascope/api/_generated/traces/__init__.py +97 -0
  185. mirascope/api/_generated/traces/client.py +1103 -0
  186. mirascope/api/_generated/traces/raw_client.py +2322 -0
  187. mirascope/api/_generated/traces/types/__init__.py +155 -0
  188. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +29 -0
  189. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +27 -0
  190. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +23 -0
  191. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +38 -0
  192. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +19 -0
  193. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +22 -0
  194. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +20 -0
  195. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +29 -0
  196. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +31 -0
  197. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +23 -0
  198. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +38 -0
  199. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +19 -0
  200. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +22 -0
  201. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +22 -0
  202. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +48 -0
  203. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +23 -0
  204. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +38 -0
  205. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +19 -0
  206. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +24 -0
  207. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +22 -0
  208. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +20 -0
  209. mirascope/api/_generated/traces/types/traces_create_response.py +24 -0
  210. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +22 -0
  211. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +60 -0
  212. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_functions_item.py +24 -0
  213. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_models_item.py +22 -0
  214. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
  215. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
  216. mirascope/api/_generated/traces/types/traces_get_trace_detail_response.py +33 -0
  217. mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +88 -0
  218. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
  219. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
  220. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
  221. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
  222. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
  223. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
  224. mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
  225. mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
  226. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item.py +26 -0
  227. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item_operator.py +7 -0
  228. mirascope/api/_generated/traces/types/traces_search_request_sort_by.py +7 -0
  229. mirascope/api/_generated/traces/types/traces_search_request_sort_order.py +5 -0
  230. mirascope/api/_generated/traces/types/traces_search_response.py +26 -0
  231. mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +50 -0
  232. mirascope/api/_generated/types/__init__.py +85 -0
  233. mirascope/api/_generated/types/already_exists_error.py +22 -0
  234. mirascope/api/_generated/types/already_exists_error_tag.py +5 -0
  235. mirascope/api/_generated/types/bad_request_error_body.py +50 -0
  236. mirascope/api/_generated/types/click_house_error.py +22 -0
  237. mirascope/api/_generated/types/database_error.py +22 -0
  238. mirascope/api/_generated/types/database_error_tag.py +5 -0
  239. mirascope/api/_generated/types/date.py +3 -0
  240. mirascope/api/_generated/types/http_api_decode_error.py +27 -0
  241. mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
  242. mirascope/api/_generated/types/immutable_resource_error.py +22 -0
  243. mirascope/api/_generated/types/internal_server_error_body.py +49 -0
  244. mirascope/api/_generated/types/issue.py +38 -0
  245. mirascope/api/_generated/types/issue_tag.py +10 -0
  246. mirascope/api/_generated/types/not_found_error_body.py +22 -0
  247. mirascope/api/_generated/types/not_found_error_tag.py +5 -0
  248. mirascope/api/_generated/types/number_from_string.py +3 -0
  249. mirascope/api/_generated/types/permission_denied_error.py +22 -0
  250. mirascope/api/_generated/types/permission_denied_error_tag.py +5 -0
  251. mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
  252. mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
  253. mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
  254. mirascope/api/_generated/types/property_key.py +7 -0
  255. mirascope/api/_generated/types/property_key_key.py +25 -0
  256. mirascope/api/_generated/types/property_key_key_tag.py +5 -0
  257. mirascope/api/_generated/types/rate_limit_error.py +31 -0
  258. mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
  259. mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
  260. mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
  261. mirascope/api/_generated/types/stripe_error.py +20 -0
  262. mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
  263. mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
  264. mirascope/api/_generated/types/unauthorized_error_body.py +21 -0
  265. mirascope/api/_generated/types/unauthorized_error_tag.py +5 -0
  266. mirascope/api/client.py +255 -0
  267. mirascope/api/settings.py +99 -0
  268. mirascope/llm/__init__.py +309 -13
  269. mirascope/llm/calls/__init__.py +17 -0
  270. mirascope/llm/calls/calls.py +348 -0
  271. mirascope/llm/calls/decorator.py +268 -0
  272. mirascope/llm/content/__init__.py +71 -0
  273. mirascope/llm/content/audio.py +173 -0
  274. mirascope/llm/content/document.py +94 -0
  275. mirascope/llm/content/image.py +206 -0
  276. mirascope/llm/content/text.py +47 -0
  277. mirascope/llm/content/thought.py +58 -0
  278. mirascope/llm/content/tool_call.py +69 -0
  279. mirascope/llm/content/tool_output.py +43 -0
  280. mirascope/llm/context/__init__.py +6 -0
  281. mirascope/llm/context/_utils.py +41 -0
  282. mirascope/llm/context/context.py +24 -0
  283. mirascope/llm/exceptions.py +360 -0
  284. mirascope/llm/formatting/__init__.py +39 -0
  285. mirascope/llm/formatting/format.py +291 -0
  286. mirascope/llm/formatting/from_call_args.py +30 -0
  287. mirascope/llm/formatting/output_parser.py +178 -0
  288. mirascope/llm/formatting/partial.py +131 -0
  289. mirascope/llm/formatting/primitives.py +192 -0
  290. mirascope/llm/formatting/types.py +83 -0
  291. mirascope/llm/mcp/__init__.py +5 -0
  292. mirascope/llm/mcp/mcp_client.py +130 -0
  293. mirascope/llm/messages/__init__.py +35 -0
  294. mirascope/llm/messages/_utils.py +34 -0
  295. mirascope/llm/messages/message.py +190 -0
  296. mirascope/llm/models/__init__.py +21 -0
  297. mirascope/llm/models/models.py +1339 -0
  298. mirascope/llm/models/params.py +72 -0
  299. mirascope/llm/models/thinking_config.py +61 -0
  300. mirascope/llm/prompts/__init__.py +34 -0
  301. mirascope/llm/prompts/_utils.py +31 -0
  302. mirascope/llm/prompts/decorator.py +215 -0
  303. mirascope/llm/prompts/prompts.py +484 -0
  304. mirascope/llm/prompts/protocols.py +65 -0
  305. mirascope/llm/providers/__init__.py +65 -0
  306. mirascope/llm/providers/anthropic/__init__.py +11 -0
  307. mirascope/llm/providers/anthropic/_utils/__init__.py +27 -0
  308. mirascope/llm/providers/anthropic/_utils/beta_decode.py +297 -0
  309. mirascope/llm/providers/anthropic/_utils/beta_encode.py +272 -0
  310. mirascope/llm/providers/anthropic/_utils/decode.py +326 -0
  311. mirascope/llm/providers/anthropic/_utils/encode.py +431 -0
  312. mirascope/llm/providers/anthropic/_utils/errors.py +46 -0
  313. mirascope/llm/providers/anthropic/beta_provider.py +338 -0
  314. mirascope/llm/providers/anthropic/model_id.py +23 -0
  315. mirascope/llm/providers/anthropic/model_info.py +87 -0
  316. mirascope/llm/providers/anthropic/provider.py +440 -0
  317. mirascope/llm/providers/base/__init__.py +14 -0
  318. mirascope/llm/providers/base/_utils.py +248 -0
  319. mirascope/llm/providers/base/base_provider.py +1463 -0
  320. mirascope/llm/providers/base/kwargs.py +12 -0
  321. mirascope/llm/providers/google/__init__.py +6 -0
  322. mirascope/llm/providers/google/_utils/__init__.py +17 -0
  323. mirascope/llm/providers/google/_utils/decode.py +357 -0
  324. mirascope/llm/providers/google/_utils/encode.py +418 -0
  325. mirascope/llm/providers/google/_utils/errors.py +50 -0
  326. mirascope/llm/providers/google/message.py +7 -0
  327. mirascope/llm/providers/google/model_id.py +22 -0
  328. mirascope/llm/providers/google/model_info.py +63 -0
  329. mirascope/llm/providers/google/provider.py +456 -0
  330. mirascope/llm/providers/mirascope/__init__.py +5 -0
  331. mirascope/llm/providers/mirascope/_utils.py +73 -0
  332. mirascope/llm/providers/mirascope/provider.py +313 -0
  333. mirascope/llm/providers/mlx/__init__.py +9 -0
  334. mirascope/llm/providers/mlx/_utils.py +141 -0
  335. mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
  336. mirascope/llm/providers/mlx/encoding/base.py +69 -0
  337. mirascope/llm/providers/mlx/encoding/transformers.py +146 -0
  338. mirascope/llm/providers/mlx/mlx.py +242 -0
  339. mirascope/llm/providers/mlx/model_id.py +17 -0
  340. mirascope/llm/providers/mlx/provider.py +416 -0
  341. mirascope/llm/providers/model_id.py +16 -0
  342. mirascope/llm/providers/ollama/__init__.py +7 -0
  343. mirascope/llm/providers/ollama/provider.py +71 -0
  344. mirascope/llm/providers/openai/__init__.py +15 -0
  345. mirascope/llm/providers/openai/_utils/__init__.py +5 -0
  346. mirascope/llm/providers/openai/_utils/errors.py +46 -0
  347. mirascope/llm/providers/openai/completions/__init__.py +7 -0
  348. mirascope/llm/providers/openai/completions/_utils/__init__.py +18 -0
  349. mirascope/llm/providers/openai/completions/_utils/decode.py +252 -0
  350. mirascope/llm/providers/openai/completions/_utils/encode.py +390 -0
  351. mirascope/llm/providers/openai/completions/_utils/feature_info.py +50 -0
  352. mirascope/llm/providers/openai/completions/base_provider.py +522 -0
  353. mirascope/llm/providers/openai/completions/provider.py +28 -0
  354. mirascope/llm/providers/openai/model_id.py +31 -0
  355. mirascope/llm/providers/openai/model_info.py +303 -0
  356. mirascope/llm/providers/openai/provider.py +405 -0
  357. mirascope/llm/providers/openai/responses/__init__.py +5 -0
  358. mirascope/llm/providers/openai/responses/_utils/__init__.py +15 -0
  359. mirascope/llm/providers/openai/responses/_utils/decode.py +289 -0
  360. mirascope/llm/providers/openai/responses/_utils/encode.py +399 -0
  361. mirascope/llm/providers/openai/responses/provider.py +472 -0
  362. mirascope/llm/providers/openrouter/__init__.py +5 -0
  363. mirascope/llm/providers/openrouter/provider.py +67 -0
  364. mirascope/llm/providers/provider_id.py +26 -0
  365. mirascope/llm/providers/provider_registry.py +305 -0
  366. mirascope/llm/providers/together/__init__.py +7 -0
  367. mirascope/llm/providers/together/provider.py +40 -0
  368. mirascope/llm/responses/__init__.py +66 -0
  369. mirascope/llm/responses/_utils.py +146 -0
  370. mirascope/llm/responses/base_response.py +103 -0
  371. mirascope/llm/responses/base_stream_response.py +824 -0
  372. mirascope/llm/responses/finish_reason.py +28 -0
  373. mirascope/llm/responses/response.py +362 -0
  374. mirascope/llm/responses/root_response.py +248 -0
  375. mirascope/llm/responses/stream_response.py +577 -0
  376. mirascope/llm/responses/streams.py +363 -0
  377. mirascope/llm/responses/usage.py +139 -0
  378. mirascope/llm/tools/__init__.py +71 -0
  379. mirascope/llm/tools/_utils.py +34 -0
  380. mirascope/llm/tools/decorator.py +184 -0
  381. mirascope/llm/tools/protocols.py +96 -0
  382. mirascope/llm/tools/provider_tools.py +18 -0
  383. mirascope/llm/tools/tool_schema.py +321 -0
  384. mirascope/llm/tools/toolkit.py +178 -0
  385. mirascope/llm/tools/tools.py +263 -0
  386. mirascope/llm/tools/types.py +112 -0
  387. mirascope/llm/tools/web_search_tool.py +32 -0
  388. mirascope/llm/types/__init__.py +22 -0
  389. mirascope/llm/types/dataclass.py +9 -0
  390. mirascope/llm/types/jsonable.py +44 -0
  391. mirascope/llm/types/type_vars.py +19 -0
  392. mirascope/ops/__init__.py +129 -0
  393. mirascope/ops/_internal/__init__.py +5 -0
  394. mirascope/ops/_internal/closure.py +1172 -0
  395. mirascope/ops/_internal/configuration.py +177 -0
  396. mirascope/ops/_internal/context.py +76 -0
  397. mirascope/ops/_internal/exporters/__init__.py +26 -0
  398. mirascope/ops/_internal/exporters/exporters.py +362 -0
  399. mirascope/ops/_internal/exporters/processors.py +104 -0
  400. mirascope/ops/_internal/exporters/types.py +165 -0
  401. mirascope/ops/_internal/exporters/utils.py +66 -0
  402. mirascope/ops/_internal/instrumentation/__init__.py +28 -0
  403. mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
  404. mirascope/ops/_internal/instrumentation/llm/common.py +500 -0
  405. mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
  406. mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
  407. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
  408. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
  409. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
  410. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
  411. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
  412. mirascope/ops/_internal/instrumentation/llm/llm.py +161 -0
  413. mirascope/ops/_internal/instrumentation/llm/model.py +1777 -0
  414. mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
  415. mirascope/ops/_internal/instrumentation/llm/serialize.py +324 -0
  416. mirascope/ops/_internal/instrumentation/providers/__init__.py +29 -0
  417. mirascope/ops/_internal/instrumentation/providers/anthropic.py +78 -0
  418. mirascope/ops/_internal/instrumentation/providers/base.py +179 -0
  419. mirascope/ops/_internal/instrumentation/providers/google_genai.py +85 -0
  420. mirascope/ops/_internal/instrumentation/providers/openai.py +82 -0
  421. mirascope/ops/_internal/propagation.py +198 -0
  422. mirascope/ops/_internal/protocols.py +133 -0
  423. mirascope/ops/_internal/session.py +139 -0
  424. mirascope/ops/_internal/spans.py +232 -0
  425. mirascope/ops/_internal/traced_calls.py +389 -0
  426. mirascope/ops/_internal/traced_functions.py +528 -0
  427. mirascope/ops/_internal/tracing.py +353 -0
  428. mirascope/ops/_internal/types.py +13 -0
  429. mirascope/ops/_internal/utils.py +131 -0
  430. mirascope/ops/_internal/versioned_calls.py +512 -0
  431. mirascope/ops/_internal/versioned_functions.py +357 -0
  432. mirascope/ops/_internal/versioning.py +303 -0
  433. mirascope/ops/exceptions.py +21 -0
  434. mirascope-2.1.1.dist-info/METADATA +231 -0
  435. mirascope-2.1.1.dist-info/RECORD +437 -0
  436. mirascope-2.1.1.dist-info/WHEEL +4 -0
  437. mirascope-2.1.1.dist-info/licenses/LICENSE +21 -0
  438. mirascope/beta/__init__.py +0 -3
  439. mirascope/beta/openai/__init__.py +0 -17
  440. mirascope/beta/openai/realtime/__init__.py +0 -13
  441. mirascope/beta/openai/realtime/_utils/__init__.py +0 -3
  442. mirascope/beta/openai/realtime/_utils/_audio.py +0 -74
  443. mirascope/beta/openai/realtime/_utils/_protocols.py +0 -50
  444. mirascope/beta/openai/realtime/realtime.py +0 -500
  445. mirascope/beta/openai/realtime/recording.py +0 -98
  446. mirascope/beta/openai/realtime/tool.py +0 -113
  447. mirascope/beta/rag/__init__.py +0 -24
  448. mirascope/beta/rag/base/__init__.py +0 -22
  449. mirascope/beta/rag/base/chunkers/__init__.py +0 -2
  450. mirascope/beta/rag/base/chunkers/base_chunker.py +0 -37
  451. mirascope/beta/rag/base/chunkers/text_chunker.py +0 -33
  452. mirascope/beta/rag/base/config.py +0 -8
  453. mirascope/beta/rag/base/document.py +0 -11
  454. mirascope/beta/rag/base/embedders.py +0 -35
  455. mirascope/beta/rag/base/embedding_params.py +0 -18
  456. mirascope/beta/rag/base/embedding_response.py +0 -30
  457. mirascope/beta/rag/base/query_results.py +0 -7
  458. mirascope/beta/rag/base/vectorstore_params.py +0 -18
  459. mirascope/beta/rag/base/vectorstores.py +0 -37
  460. mirascope/beta/rag/chroma/__init__.py +0 -11
  461. mirascope/beta/rag/chroma/types.py +0 -57
  462. mirascope/beta/rag/chroma/vectorstores.py +0 -97
  463. mirascope/beta/rag/cohere/__init__.py +0 -11
  464. mirascope/beta/rag/cohere/embedders.py +0 -87
  465. mirascope/beta/rag/cohere/embedding_params.py +0 -29
  466. mirascope/beta/rag/cohere/embedding_response.py +0 -29
  467. mirascope/beta/rag/cohere/py.typed +0 -0
  468. mirascope/beta/rag/openai/__init__.py +0 -11
  469. mirascope/beta/rag/openai/embedders.py +0 -144
  470. mirascope/beta/rag/openai/embedding_params.py +0 -18
  471. mirascope/beta/rag/openai/embedding_response.py +0 -14
  472. mirascope/beta/rag/openai/py.typed +0 -0
  473. mirascope/beta/rag/pinecone/__init__.py +0 -19
  474. mirascope/beta/rag/pinecone/types.py +0 -143
  475. mirascope/beta/rag/pinecone/vectorstores.py +0 -148
  476. mirascope/beta/rag/weaviate/__init__.py +0 -6
  477. mirascope/beta/rag/weaviate/types.py +0 -92
  478. mirascope/beta/rag/weaviate/vectorstores.py +0 -103
  479. mirascope/core/__init__.py +0 -107
  480. mirascope/core/anthropic/__init__.py +0 -31
  481. mirascope/core/anthropic/_call.py +0 -67
  482. mirascope/core/anthropic/_call_kwargs.py +0 -13
  483. mirascope/core/anthropic/_utils/__init__.py +0 -16
  484. mirascope/core/anthropic/_utils/_convert_common_call_params.py +0 -25
  485. mirascope/core/anthropic/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -21
  486. mirascope/core/anthropic/_utils/_convert_message_params.py +0 -102
  487. mirascope/core/anthropic/_utils/_get_json_output.py +0 -31
  488. mirascope/core/anthropic/_utils/_handle_stream.py +0 -113
  489. mirascope/core/anthropic/_utils/_message_param_converter.py +0 -142
  490. mirascope/core/anthropic/_utils/_setup_call.py +0 -134
  491. mirascope/core/anthropic/call_params.py +0 -41
  492. mirascope/core/anthropic/call_response.py +0 -206
  493. mirascope/core/anthropic/call_response_chunk.py +0 -132
  494. mirascope/core/anthropic/dynamic_config.py +0 -40
  495. mirascope/core/anthropic/py.typed +0 -0
  496. mirascope/core/anthropic/stream.py +0 -147
  497. mirascope/core/anthropic/tool.py +0 -101
  498. mirascope/core/azure/__init__.py +0 -31
  499. mirascope/core/azure/_call.py +0 -67
  500. mirascope/core/azure/_call_kwargs.py +0 -13
  501. mirascope/core/azure/_utils/__init__.py +0 -14
  502. mirascope/core/azure/_utils/_convert_common_call_params.py +0 -26
  503. mirascope/core/azure/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -21
  504. mirascope/core/azure/_utils/_convert_message_params.py +0 -121
  505. mirascope/core/azure/_utils/_get_credential.py +0 -33
  506. mirascope/core/azure/_utils/_get_json_output.py +0 -27
  507. mirascope/core/azure/_utils/_handle_stream.py +0 -130
  508. mirascope/core/azure/_utils/_message_param_converter.py +0 -117
  509. mirascope/core/azure/_utils/_setup_call.py +0 -183
  510. mirascope/core/azure/call_params.py +0 -59
  511. mirascope/core/azure/call_response.py +0 -215
  512. mirascope/core/azure/call_response_chunk.py +0 -105
  513. mirascope/core/azure/dynamic_config.py +0 -30
  514. mirascope/core/azure/py.typed +0 -0
  515. mirascope/core/azure/stream.py +0 -147
  516. mirascope/core/azure/tool.py +0 -93
  517. mirascope/core/base/__init__.py +0 -84
  518. mirascope/core/base/_call_factory.py +0 -256
  519. mirascope/core/base/_create.py +0 -253
  520. mirascope/core/base/_extract.py +0 -175
  521. mirascope/core/base/_extract_with_tools.py +0 -189
  522. mirascope/core/base/_partial.py +0 -95
  523. mirascope/core/base/_utils/__init__.py +0 -92
  524. mirascope/core/base/_utils/_base_message_param_converter.py +0 -22
  525. mirascope/core/base/_utils/_base_type.py +0 -26
  526. mirascope/core/base/_utils/_convert_base_model_to_base_tool.py +0 -48
  527. mirascope/core/base/_utils/_convert_base_type_to_base_tool.py +0 -24
  528. mirascope/core/base/_utils/_convert_function_to_base_tool.py +0 -139
  529. mirascope/core/base/_utils/_convert_messages_to_message_params.py +0 -171
  530. mirascope/core/base/_utils/_convert_provider_finish_reason_to_finish_reason.py +0 -20
  531. mirascope/core/base/_utils/_default_tool_docstring.py +0 -6
  532. mirascope/core/base/_utils/_extract_tool_return.py +0 -42
  533. mirascope/core/base/_utils/_fn_is_async.py +0 -15
  534. mirascope/core/base/_utils/_format_template.py +0 -32
  535. mirascope/core/base/_utils/_get_audio_type.py +0 -18
  536. mirascope/core/base/_utils/_get_common_usage.py +0 -20
  537. mirascope/core/base/_utils/_get_create_fn_or_async_create_fn.py +0 -137
  538. mirascope/core/base/_utils/_get_document_type.py +0 -7
  539. mirascope/core/base/_utils/_get_dynamic_configuration.py +0 -69
  540. mirascope/core/base/_utils/_get_fields_from_call_args.py +0 -34
  541. mirascope/core/base/_utils/_get_fn_args.py +0 -23
  542. mirascope/core/base/_utils/_get_image_dimensions.py +0 -39
  543. mirascope/core/base/_utils/_get_image_type.py +0 -26
  544. mirascope/core/base/_utils/_get_metadata.py +0 -17
  545. mirascope/core/base/_utils/_get_possible_user_message_param.py +0 -21
  546. mirascope/core/base/_utils/_get_prompt_template.py +0 -28
  547. mirascope/core/base/_utils/_get_template_values.py +0 -51
  548. mirascope/core/base/_utils/_get_template_variables.py +0 -38
  549. mirascope/core/base/_utils/_get_unsupported_tool_config_keys.py +0 -10
  550. mirascope/core/base/_utils/_is_prompt_template.py +0 -24
  551. mirascope/core/base/_utils/_json_mode_content.py +0 -17
  552. mirascope/core/base/_utils/_messages_decorator.py +0 -121
  553. mirascope/core/base/_utils/_parse_content_template.py +0 -321
  554. mirascope/core/base/_utils/_parse_prompt_messages.py +0 -63
  555. mirascope/core/base/_utils/_pil_image_to_bytes.py +0 -13
  556. mirascope/core/base/_utils/_protocols.py +0 -901
  557. mirascope/core/base/_utils/_setup_call.py +0 -79
  558. mirascope/core/base/_utils/_setup_extract_tool.py +0 -30
  559. mirascope/core/base/call_kwargs.py +0 -13
  560. mirascope/core/base/call_params.py +0 -36
  561. mirascope/core/base/call_response.py +0 -340
  562. mirascope/core/base/call_response_chunk.py +0 -130
  563. mirascope/core/base/dynamic_config.py +0 -82
  564. mirascope/core/base/from_call_args.py +0 -30
  565. mirascope/core/base/merge_decorators.py +0 -59
  566. mirascope/core/base/message_param.py +0 -162
  567. mirascope/core/base/messages.py +0 -111
  568. mirascope/core/base/metadata.py +0 -13
  569. mirascope/core/base/prompt.py +0 -497
  570. mirascope/core/base/response_model_config_dict.py +0 -9
  571. mirascope/core/base/stream.py +0 -479
  572. mirascope/core/base/stream_config.py +0 -11
  573. mirascope/core/base/structured_stream.py +0 -296
  574. mirascope/core/base/tool.py +0 -205
  575. mirascope/core/base/toolkit.py +0 -176
  576. mirascope/core/base/types.py +0 -344
  577. mirascope/core/bedrock/__init__.py +0 -34
  578. mirascope/core/bedrock/_call.py +0 -68
  579. mirascope/core/bedrock/_call_kwargs.py +0 -12
  580. mirascope/core/bedrock/_types.py +0 -104
  581. mirascope/core/bedrock/_utils/__init__.py +0 -14
  582. mirascope/core/bedrock/_utils/_convert_common_call_params.py +0 -39
  583. mirascope/core/bedrock/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -23
  584. mirascope/core/bedrock/_utils/_convert_message_params.py +0 -111
  585. mirascope/core/bedrock/_utils/_get_json_output.py +0 -30
  586. mirascope/core/bedrock/_utils/_handle_stream.py +0 -104
  587. mirascope/core/bedrock/_utils/_message_param_converter.py +0 -171
  588. mirascope/core/bedrock/_utils/_setup_call.py +0 -258
  589. mirascope/core/bedrock/call_params.py +0 -38
  590. mirascope/core/bedrock/call_response.py +0 -249
  591. mirascope/core/bedrock/call_response_chunk.py +0 -111
  592. mirascope/core/bedrock/dynamic_config.py +0 -37
  593. mirascope/core/bedrock/py.typed +0 -0
  594. mirascope/core/bedrock/stream.py +0 -154
  595. mirascope/core/bedrock/tool.py +0 -96
  596. mirascope/core/cohere/__init__.py +0 -30
  597. mirascope/core/cohere/_call.py +0 -67
  598. mirascope/core/cohere/_call_kwargs.py +0 -11
  599. mirascope/core/cohere/_types.py +0 -20
  600. mirascope/core/cohere/_utils/__init__.py +0 -14
  601. mirascope/core/cohere/_utils/_convert_common_call_params.py +0 -26
  602. mirascope/core/cohere/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -24
  603. mirascope/core/cohere/_utils/_convert_message_params.py +0 -32
  604. mirascope/core/cohere/_utils/_get_json_output.py +0 -30
  605. mirascope/core/cohere/_utils/_handle_stream.py +0 -35
  606. mirascope/core/cohere/_utils/_message_param_converter.py +0 -54
  607. mirascope/core/cohere/_utils/_setup_call.py +0 -150
  608. mirascope/core/cohere/call_params.py +0 -62
  609. mirascope/core/cohere/call_response.py +0 -205
  610. mirascope/core/cohere/call_response_chunk.py +0 -125
  611. mirascope/core/cohere/dynamic_config.py +0 -32
  612. mirascope/core/cohere/py.typed +0 -0
  613. mirascope/core/cohere/stream.py +0 -113
  614. mirascope/core/cohere/tool.py +0 -93
  615. mirascope/core/costs/__init__.py +0 -5
  616. mirascope/core/costs/_anthropic_calculate_cost.py +0 -219
  617. mirascope/core/costs/_azure_calculate_cost.py +0 -11
  618. mirascope/core/costs/_bedrock_calculate_cost.py +0 -15
  619. mirascope/core/costs/_cohere_calculate_cost.py +0 -44
  620. mirascope/core/costs/_gemini_calculate_cost.py +0 -67
  621. mirascope/core/costs/_google_calculate_cost.py +0 -427
  622. mirascope/core/costs/_groq_calculate_cost.py +0 -156
  623. mirascope/core/costs/_litellm_calculate_cost.py +0 -11
  624. mirascope/core/costs/_mistral_calculate_cost.py +0 -64
  625. mirascope/core/costs/_openai_calculate_cost.py +0 -416
  626. mirascope/core/costs/_vertex_calculate_cost.py +0 -67
  627. mirascope/core/costs/_xai_calculate_cost.py +0 -104
  628. mirascope/core/costs/calculate_cost.py +0 -86
  629. mirascope/core/gemini/__init__.py +0 -40
  630. mirascope/core/gemini/_call.py +0 -67
  631. mirascope/core/gemini/_call_kwargs.py +0 -12
  632. mirascope/core/gemini/_utils/__init__.py +0 -14
  633. mirascope/core/gemini/_utils/_convert_common_call_params.py +0 -39
  634. mirascope/core/gemini/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -23
  635. mirascope/core/gemini/_utils/_convert_message_params.py +0 -156
  636. mirascope/core/gemini/_utils/_get_json_output.py +0 -35
  637. mirascope/core/gemini/_utils/_handle_stream.py +0 -33
  638. mirascope/core/gemini/_utils/_message_param_converter.py +0 -209
  639. mirascope/core/gemini/_utils/_setup_call.py +0 -149
  640. mirascope/core/gemini/call_params.py +0 -52
  641. mirascope/core/gemini/call_response.py +0 -216
  642. mirascope/core/gemini/call_response_chunk.py +0 -100
  643. mirascope/core/gemini/dynamic_config.py +0 -26
  644. mirascope/core/gemini/stream.py +0 -120
  645. mirascope/core/gemini/tool.py +0 -104
  646. mirascope/core/google/__init__.py +0 -29
  647. mirascope/core/google/_call.py +0 -67
  648. mirascope/core/google/_call_kwargs.py +0 -13
  649. mirascope/core/google/_utils/__init__.py +0 -14
  650. mirascope/core/google/_utils/_convert_common_call_params.py +0 -38
  651. mirascope/core/google/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -27
  652. mirascope/core/google/_utils/_convert_message_params.py +0 -206
  653. mirascope/core/google/_utils/_get_json_output.py +0 -37
  654. mirascope/core/google/_utils/_handle_stream.py +0 -35
  655. mirascope/core/google/_utils/_message_param_converter.py +0 -162
  656. mirascope/core/google/_utils/_setup_call.py +0 -201
  657. mirascope/core/google/_utils/_validate_media_type.py +0 -34
  658. mirascope/core/google/call_params.py +0 -22
  659. mirascope/core/google/call_response.py +0 -232
  660. mirascope/core/google/call_response_chunk.py +0 -110
  661. mirascope/core/google/dynamic_config.py +0 -26
  662. mirascope/core/google/stream.py +0 -143
  663. mirascope/core/google/tool.py +0 -146
  664. mirascope/core/groq/__init__.py +0 -30
  665. mirascope/core/groq/_call.py +0 -67
  666. mirascope/core/groq/_call_kwargs.py +0 -13
  667. mirascope/core/groq/_utils/__init__.py +0 -14
  668. mirascope/core/groq/_utils/_convert_common_call_params.py +0 -26
  669. mirascope/core/groq/_utils/_convert_message_params.py +0 -112
  670. mirascope/core/groq/_utils/_get_json_output.py +0 -27
  671. mirascope/core/groq/_utils/_handle_stream.py +0 -123
  672. mirascope/core/groq/_utils/_message_param_converter.py +0 -89
  673. mirascope/core/groq/_utils/_setup_call.py +0 -132
  674. mirascope/core/groq/call_params.py +0 -52
  675. mirascope/core/groq/call_response.py +0 -213
  676. mirascope/core/groq/call_response_chunk.py +0 -104
  677. mirascope/core/groq/dynamic_config.py +0 -29
  678. mirascope/core/groq/py.typed +0 -0
  679. mirascope/core/groq/stream.py +0 -135
  680. mirascope/core/groq/tool.py +0 -80
  681. mirascope/core/litellm/__init__.py +0 -28
  682. mirascope/core/litellm/_call.py +0 -67
  683. mirascope/core/litellm/_utils/__init__.py +0 -5
  684. mirascope/core/litellm/_utils/_setup_call.py +0 -109
  685. mirascope/core/litellm/call_params.py +0 -10
  686. mirascope/core/litellm/call_response.py +0 -24
  687. mirascope/core/litellm/call_response_chunk.py +0 -14
  688. mirascope/core/litellm/dynamic_config.py +0 -8
  689. mirascope/core/litellm/py.typed +0 -0
  690. mirascope/core/litellm/stream.py +0 -86
  691. mirascope/core/litellm/tool.py +0 -13
  692. mirascope/core/mistral/__init__.py +0 -36
  693. mirascope/core/mistral/_call.py +0 -65
  694. mirascope/core/mistral/_call_kwargs.py +0 -19
  695. mirascope/core/mistral/_utils/__init__.py +0 -14
  696. mirascope/core/mistral/_utils/_convert_common_call_params.py +0 -24
  697. mirascope/core/mistral/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -22
  698. mirascope/core/mistral/_utils/_convert_message_params.py +0 -122
  699. mirascope/core/mistral/_utils/_get_json_output.py +0 -34
  700. mirascope/core/mistral/_utils/_handle_stream.py +0 -139
  701. mirascope/core/mistral/_utils/_message_param_converter.py +0 -176
  702. mirascope/core/mistral/_utils/_setup_call.py +0 -154
  703. mirascope/core/mistral/call_params.py +0 -36
  704. mirascope/core/mistral/call_response.py +0 -205
  705. mirascope/core/mistral/call_response_chunk.py +0 -105
  706. mirascope/core/mistral/dynamic_config.py +0 -33
  707. mirascope/core/mistral/py.typed +0 -0
  708. mirascope/core/mistral/stream.py +0 -120
  709. mirascope/core/mistral/tool.py +0 -80
  710. mirascope/core/openai/__init__.py +0 -31
  711. mirascope/core/openai/_call.py +0 -67
  712. mirascope/core/openai/_call_kwargs.py +0 -13
  713. mirascope/core/openai/_utils/__init__.py +0 -14
  714. mirascope/core/openai/_utils/_convert_common_call_params.py +0 -26
  715. mirascope/core/openai/_utils/_convert_message_params.py +0 -146
  716. mirascope/core/openai/_utils/_get_json_output.py +0 -31
  717. mirascope/core/openai/_utils/_handle_stream.py +0 -138
  718. mirascope/core/openai/_utils/_message_param_converter.py +0 -105
  719. mirascope/core/openai/_utils/_setup_call.py +0 -155
  720. mirascope/core/openai/call_params.py +0 -91
  721. mirascope/core/openai/call_response.py +0 -273
  722. mirascope/core/openai/call_response_chunk.py +0 -139
  723. mirascope/core/openai/dynamic_config.py +0 -34
  724. mirascope/core/openai/py.typed +0 -0
  725. mirascope/core/openai/stream.py +0 -185
  726. mirascope/core/openai/tool.py +0 -101
  727. mirascope/core/py.typed +0 -0
  728. mirascope/core/vertex/__init__.py +0 -45
  729. mirascope/core/vertex/_call.py +0 -62
  730. mirascope/core/vertex/_call_kwargs.py +0 -12
  731. mirascope/core/vertex/_utils/__init__.py +0 -14
  732. mirascope/core/vertex/_utils/_convert_common_call_params.py +0 -37
  733. mirascope/core/vertex/_utils/_convert_finish_reason_to_common_finish_reasons.py +0 -23
  734. mirascope/core/vertex/_utils/_convert_message_params.py +0 -171
  735. mirascope/core/vertex/_utils/_get_json_output.py +0 -36
  736. mirascope/core/vertex/_utils/_handle_stream.py +0 -33
  737. mirascope/core/vertex/_utils/_message_param_converter.py +0 -133
  738. mirascope/core/vertex/_utils/_setup_call.py +0 -160
  739. mirascope/core/vertex/call_params.py +0 -24
  740. mirascope/core/vertex/call_response.py +0 -206
  741. mirascope/core/vertex/call_response_chunk.py +0 -99
  742. mirascope/core/vertex/dynamic_config.py +0 -28
  743. mirascope/core/vertex/stream.py +0 -119
  744. mirascope/core/vertex/tool.py +0 -101
  745. mirascope/core/xai/__init__.py +0 -28
  746. mirascope/core/xai/_call.py +0 -67
  747. mirascope/core/xai/_utils/__init__.py +0 -5
  748. mirascope/core/xai/_utils/_setup_call.py +0 -113
  749. mirascope/core/xai/call_params.py +0 -10
  750. mirascope/core/xai/call_response.py +0 -16
  751. mirascope/core/xai/call_response_chunk.py +0 -14
  752. mirascope/core/xai/dynamic_config.py +0 -8
  753. mirascope/core/xai/py.typed +0 -0
  754. mirascope/core/xai/stream.py +0 -57
  755. mirascope/core/xai/tool.py +0 -13
  756. mirascope/experimental/graphs/__init__.py +0 -5
  757. mirascope/experimental/graphs/finite_state_machine.py +0 -714
  758. mirascope/integrations/__init__.py +0 -16
  759. mirascope/integrations/_middleware_factory.py +0 -403
  760. mirascope/integrations/langfuse/__init__.py +0 -3
  761. mirascope/integrations/langfuse/_utils.py +0 -114
  762. mirascope/integrations/langfuse/_with_langfuse.py +0 -70
  763. mirascope/integrations/logfire/__init__.py +0 -3
  764. mirascope/integrations/logfire/_utils.py +0 -225
  765. mirascope/integrations/logfire/_with_logfire.py +0 -63
  766. mirascope/integrations/otel/__init__.py +0 -10
  767. mirascope/integrations/otel/_utils.py +0 -270
  768. mirascope/integrations/otel/_with_hyperdx.py +0 -60
  769. mirascope/integrations/otel/_with_otel.py +0 -59
  770. mirascope/integrations/tenacity.py +0 -14
  771. mirascope/llm/_call.py +0 -401
  772. mirascope/llm/_context.py +0 -384
  773. mirascope/llm/_override.py +0 -3639
  774. mirascope/llm/_protocols.py +0 -500
  775. mirascope/llm/_response_metaclass.py +0 -31
  776. mirascope/llm/call_response.py +0 -167
  777. mirascope/llm/call_response_chunk.py +0 -66
  778. mirascope/llm/stream.py +0 -162
  779. mirascope/llm/tool.py +0 -64
  780. mirascope/mcp/__init__.py +0 -7
  781. mirascope/mcp/_utils.py +0 -277
  782. mirascope/mcp/client.py +0 -167
  783. mirascope/mcp/server.py +0 -356
  784. mirascope/mcp/tools.py +0 -110
  785. mirascope/py.typed +0 -0
  786. mirascope/retries/__init__.py +0 -11
  787. mirascope/retries/fallback.py +0 -128
  788. mirascope/retries/tenacity.py +0 -50
  789. mirascope/tools/__init__.py +0 -37
  790. mirascope/tools/base.py +0 -98
  791. mirascope/tools/system/__init__.py +0 -0
  792. mirascope/tools/system/_docker_operation.py +0 -166
  793. mirascope/tools/system/_file_system.py +0 -267
  794. mirascope/tools/web/__init__.py +0 -0
  795. mirascope/tools/web/_duckduckgo.py +0 -111
  796. mirascope/tools/web/_httpx.py +0 -125
  797. mirascope/tools/web/_parse_url_content.py +0 -94
  798. mirascope/tools/web/_requests.py +0 -54
  799. mirascope/v0/__init__.py +0 -43
  800. mirascope/v0/anthropic.py +0 -54
  801. mirascope/v0/base/__init__.py +0 -12
  802. mirascope/v0/base/calls.py +0 -118
  803. mirascope/v0/base/extractors.py +0 -122
  804. mirascope/v0/base/ops_utils.py +0 -207
  805. mirascope/v0/base/prompts.py +0 -48
  806. mirascope/v0/base/types.py +0 -14
  807. mirascope/v0/base/utils.py +0 -21
  808. mirascope/v0/openai.py +0 -54
  809. mirascope-1.22.4.dist-info/METADATA +0 -169
  810. mirascope-1.22.4.dist-info/RECORD +0 -377
  811. mirascope-1.22.4.dist-info/WHEEL +0 -4
  812. mirascope-1.22.4.dist-info/licenses/LICENSE +0 -21
@@ -0,0 +1,1172 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import gc
5
+ import hashlib
6
+ import importlib.metadata
7
+ import importlib.util
8
+ import inspect
9
+ import logging
10
+ import os
11
+ import site
12
+ import subprocess
13
+ import sys
14
+ import tempfile
15
+ from collections.abc import Callable
16
+ from dataclasses import dataclass
17
+ from functools import cached_property, lru_cache
18
+ from pathlib import Path
19
+ from textwrap import dedent
20
+ from types import ModuleType
21
+ from typing import Annotated, Any, TypedDict, TypeVar, cast, get_args, get_origin
22
+
23
+ import libcst as cst
24
+ import libcst.matchers as m
25
+ from libcst import MaybeSentinel
26
+ from packaging.markers import default_environment
27
+ from packaging.requirements import Requirement
28
+
29
+ from ..exceptions import ClosureComputationError
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ _BaseCompoundStatementT = TypeVar(
34
+ "_BaseCompoundStatementT", bound=cst.BaseCompoundStatement
35
+ )
36
+
37
+
38
+ def _is_third_party(module: ModuleType, site_packages: set[str]) -> bool:
39
+ """Returns True if the module is a third-party or standard library module."""
40
+ module_file = getattr(module, "__file__", None)
41
+ return (
42
+ module.__name__ == "mirascope"
43
+ or module.__name__.startswith("mirascope.")
44
+ or module.__name__ in sys.stdlib_module_names
45
+ or module_file is None
46
+ or any(
47
+ str(Path(module_file).resolve()).startswith(site_pkg)
48
+ for site_pkg in site_packages
49
+ )
50
+ )
51
+
52
+
53
+ class _RemoveVersionDecoratorTransformer(cst.CSTTransformer):
54
+ """CST transformer to remove @ops.version and @version decorators."""
55
+
56
+ @classmethod
57
+ def _is_version_decorator(cls, decorator: cst.Decorator) -> bool:
58
+ """Returns True if the decorator is @version or @ops.version."""
59
+ decorator_node = decorator.decorator
60
+
61
+ if isinstance(decorator_node, cst.Name) and decorator_node.value == "version":
62
+ return True
63
+ if (
64
+ isinstance(decorator_node, cst.Call)
65
+ and isinstance(decorator_node.func, cst.Name)
66
+ and decorator_node.func.value == "version"
67
+ ):
68
+ return True
69
+
70
+ if (
71
+ isinstance(decorator_node, cst.Attribute)
72
+ and isinstance(decorator_node.value, cst.Name)
73
+ and decorator_node.value.value == "ops"
74
+ and decorator_node.attr.value == "version"
75
+ ):
76
+ return True
77
+ if isinstance(decorator_node, cst.Call) and isinstance(
78
+ decorator_node.func, cst.Attribute
79
+ ):
80
+ func_attribute = decorator_node.func
81
+ if (
82
+ isinstance(func_attribute.value, cst.Name)
83
+ and func_attribute.value.value == "ops"
84
+ and func_attribute.attr.value == "version"
85
+ ):
86
+ return True
87
+
88
+ return False
89
+
90
+ def leave_FunctionDef(
91
+ self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
92
+ ) -> cst.FunctionDef:
93
+ """Returns function definition with @version/@ops.version decorators removed."""
94
+ new_decorators = [
95
+ decorator
96
+ for decorator in updated_node.decorators
97
+ if not self._is_version_decorator(decorator)
98
+ ]
99
+ return updated_node.with_changes(decorators=new_decorators)
100
+
101
+
102
+ class _RemoveDocstringTransformer(cst.CSTTransformer):
103
+ """CST transformer to remove docstrings from functions and classes."""
104
+
105
+ def __init__(self, exclude_fn_body: bool) -> None:
106
+ super().__init__()
107
+ self.exclude_fn_body = exclude_fn_body
108
+
109
+ @staticmethod
110
+ def _remove_first_docstring(
111
+ node: _BaseCompoundStatementT,
112
+ ) -> _BaseCompoundStatementT:
113
+ """Returns the node with the first docstring removed from its body."""
114
+ body = node.body
115
+ stmts = list(body.body)
116
+ if stmts:
117
+ first_stmt = stmts[0]
118
+ if m.matches(
119
+ first_stmt, m.SimpleStatementLine(body=[m.Expr(value=m.SimpleString())])
120
+ ):
121
+ stmts.pop(0)
122
+
123
+ if not stmts:
124
+ stmts = [
125
+ cst.Expr(
126
+ value=cst.Ellipsis(
127
+ lpar=[],
128
+ rpar=[],
129
+ ),
130
+ semicolon=MaybeSentinel.DEFAULT,
131
+ )
132
+ ]
133
+ if m.matches(node.body, m.IndentedBlock()):
134
+ return node.with_changes(body=stmts[0])
135
+ new_body = body.with_changes(body=stmts)
136
+ return node.with_changes(body=new_body)
137
+
138
+ def leave_FunctionDef(
139
+ self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
140
+ ) -> cst.FunctionDef:
141
+ """Returns the function definition with docstring removed or body replaced with ellipsis."""
142
+ if self.exclude_fn_body:
143
+ stmts = cst.Expr(
144
+ value=cst.Ellipsis(
145
+ lpar=[],
146
+ rpar=[],
147
+ ),
148
+ semicolon=MaybeSentinel.DEFAULT,
149
+ )
150
+ return updated_node.with_changes(body=stmts)
151
+
152
+ return self._remove_first_docstring(updated_node)
153
+
154
+ def leave_ClassDef(
155
+ self, original_node: cst.ClassDef, updated_node: cst.ClassDef
156
+ ) -> cst.ClassDef:
157
+ """Returns the class definition with docstring removed or body replaced with pass."""
158
+ if self.exclude_fn_body:
159
+ pass_stmt = cst.SimpleStatementLine([cst.Pass()])
160
+ new_body = updated_node.body.with_changes(body=[pass_stmt])
161
+ return updated_node.with_changes(body=new_body)
162
+
163
+ return self._remove_first_docstring(updated_node)
164
+
165
+
166
+ def _clean_source_code(
167
+ fn: Callable[..., Any] | type,
168
+ *,
169
+ exclude_fn_body: bool = False,
170
+ ) -> str:
171
+ """Returns the source code of a function or class with docstrings optionally removed."""
172
+ source = dedent(inspect.getsource(fn))
173
+ docstr_flag = os.getenv("MIRASCOPE_VERSIONING_INCLUDE_DOCSTRINGS", "false").lower()
174
+ if docstr_flag in ("1", "true", "yes"):
175
+ return source.rstrip()
176
+ module = cst.parse_module(source)
177
+ module = module.visit(_RemoveVersionDecoratorTransformer())
178
+ module = module.visit(_RemoveDocstringTransformer(exclude_fn_body=exclude_fn_body))
179
+ return module.code.rstrip()
180
+
181
+
182
+ @dataclass(frozen=True)
183
+ class _AttributePath:
184
+ """Represents a parsed attribute access path like 'module.class.method'."""
185
+
186
+ components: list[str]
187
+ """Ordered list from base to final attribute (e.g., ['module', 'class', 'method'])."""
188
+
189
+ @property
190
+ def base_name(self) -> str:
191
+ """Returns the base module or object name."""
192
+ return self.components[0] if self.components else ""
193
+
194
+ @property
195
+ def last_attribute(self) -> str:
196
+ """Returns the last attribute in the chain."""
197
+ return self.components[-1] if self.components else ""
198
+
199
+ @property
200
+ def full_path(self) -> str:
201
+ """Returns the complete dotted path."""
202
+ return ".".join(self.components)
203
+
204
+ @classmethod
205
+ def from_ast_node(cls, node: ast.AST) -> _AttributePath | None:
206
+ """Creates an `_AttributePath` from an AST node.
207
+
208
+ Args:
209
+ node: An AST node (typically ast.Name, ast.Attribute, or ast.Call).
210
+
211
+ Returns:
212
+ `_AttributePath` with parsed components, or None if parsing fails.
213
+ """
214
+ components = []
215
+ current = node
216
+
217
+ while True:
218
+ if isinstance(current, ast.Attribute):
219
+ components.append(current.attr)
220
+ current = current.value
221
+ elif isinstance(current, ast.Call):
222
+ current = current.func
223
+ elif isinstance(current, ast.Name):
224
+ components.append(current.id)
225
+ break
226
+ else:
227
+ break
228
+
229
+ components.reverse()
230
+ return cls(components=components)
231
+
232
+ def __bool__(self) -> bool:
233
+ """Returns True if components exist."""
234
+ return bool(self.components)
235
+
236
+
237
+ class _NameCollector(ast.NodeVisitor):
238
+ """AST visitor that collects all names used in a piece of code."""
239
+
240
+ def __init__(self) -> None:
241
+ self.used_names: list[str] = []
242
+
243
+ def visit_Name(self, node: ast.Name) -> None:
244
+ """Collects name nodes."""
245
+ self.used_names.append(node.id)
246
+
247
+ def visit_Call(self, node: ast.Call) -> None:
248
+ """Collects function names from call nodes."""
249
+ if isinstance(node.func, ast.Name):
250
+ self.used_names.append(node.func.id)
251
+ self.generic_visit(node)
252
+
253
+ def visit_Attribute(self, node: ast.Attribute) -> None:
254
+ """Collects attribute access chains."""
255
+ attr_path = _AttributePath.from_ast_node(node)
256
+ if attr_path:
257
+ self.used_names.append(attr_path.full_path)
258
+ self.used_names.append(attr_path.base_name)
259
+
260
+
261
+ class _ImportCollector(ast.NodeVisitor):
262
+ """AST visitor that collects import statements based on used names."""
263
+
264
+ def __init__(self, used_names: list[str], site_packages: set[str]) -> None:
265
+ self.imports: set[str] = set()
266
+ self.user_defined_imports: set[str] = set()
267
+ self.used_names = used_names
268
+ self.site_packages = site_packages
269
+ self.alias_map: dict[str, str] = {}
270
+
271
+ def _is_used_import(self, import_name: str) -> bool:
272
+ """Returns whether an import with the given name is used in the code."""
273
+ return import_name in self.used_names or any(
274
+ u.startswith(f"{import_name}.") for u in self.used_names
275
+ )
276
+
277
+ def _add_import(
278
+ self, import_statement: str, is_third_party: bool, alias: str | None = None
279
+ ) -> None:
280
+ """Adds import statement to appropriate collection."""
281
+ if alias:
282
+ self.alias_map[alias] = import_statement
283
+
284
+ if is_third_party:
285
+ self.imports.add(import_statement)
286
+ else:
287
+ self.user_defined_imports.add(import_statement)
288
+
289
+ def visit_Import(self, node: ast.Import) -> None:
290
+ """Collects import statements."""
291
+ for name in node.names:
292
+ full_module_name = name.name
293
+ base_module_name = name.name.split(".")[0]
294
+ try:
295
+ module = __import__(base_module_name)
296
+ except ImportError:
297
+ module = None
298
+ import_name = name.asname or base_module_name
299
+
300
+ if not self._is_used_import(import_name):
301
+ continue
302
+
303
+ is_third_party = (
304
+ _is_third_party(module, self.site_packages) if module else False
305
+ )
306
+
307
+ if alias := name.asname:
308
+ import_statement = f"import {full_module_name} as {alias}"
309
+ else:
310
+ import_statement = f"import {full_module_name}"
311
+
312
+ self._add_import(import_statement, is_third_party, name.asname)
313
+
314
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
315
+ """Collects from-import statements."""
316
+ if not (module := node.module):
317
+ return
318
+
319
+ try:
320
+ is_third_party = _is_third_party(
321
+ __import__(module.split(".")[0]), self.site_packages
322
+ )
323
+ except ImportError:
324
+ module = "." * node.level + module
325
+ is_third_party = False
326
+
327
+ for name in node.names:
328
+ import_name = name.asname or name.name
329
+
330
+ if not self._is_used_import(import_name):
331
+ continue
332
+
333
+ if alias := name.asname:
334
+ import_statement = f"from {module} import {name.name} as {alias}"
335
+ else:
336
+ import_statement = f"from {module} import {name.name}"
337
+
338
+ self._add_import(import_statement, is_third_party, name.asname)
339
+
340
+
341
+ class _LocalAssignmentCollector(ast.NodeVisitor):
342
+ """AST visitor that collects local variable assignments."""
343
+
344
+ def __init__(self) -> None:
345
+ self.assignments: set[str] = set()
346
+
347
+ def visit_Assign(self, node: ast.Assign) -> None:
348
+ """Collects variable names from assignment statements."""
349
+ if isinstance(node.targets[0], ast.Name):
350
+ self.assignments.add(node.targets[0].id)
351
+ self.generic_visit(node)
352
+
353
+ def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
354
+ """Collects variable names from annotated assignment statements."""
355
+ if isinstance(node.target, ast.Name):
356
+ self.assignments.add(node.target.id)
357
+ self.generic_visit(node)
358
+
359
+
360
+ class _GlobalAssignmentCollector(ast.NodeVisitor):
361
+ """AST visitor that collects global assignments used in function."""
362
+
363
+ def __init__(self, used_names: list[str], source: str) -> None:
364
+ self.used_names = used_names
365
+ self.source = source
366
+ self.assignments: list[str] = []
367
+ self.current_function = None
368
+ self.current_class = None
369
+
370
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
371
+ """Tracks function scope while visiting."""
372
+ old_function = self.current_function
373
+ self.current_function = node
374
+ self.generic_visit(node)
375
+ self.current_function = old_function
376
+
377
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
378
+ """Tracks class scope while visiting."""
379
+ old_class = self.current_class
380
+ self.current_class = node
381
+ self.generic_visit(node)
382
+ self.current_class = old_class
383
+
384
+ def visit_Assign(self, node: ast.Assign) -> None:
385
+ """Collects global assignment statements."""
386
+ if self.current_function is not None or self.current_class is not None:
387
+ return
388
+ for target in node.targets:
389
+ if isinstance(target, ast.Name) and target.id in self.used_names:
390
+ code = ast.get_source_segment(self.source, node)
391
+ if code is not None:
392
+ self.assignments.append(code)
393
+
394
+ def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
395
+ """Collects global annotated assignment statements."""
396
+ if self.current_function is not None or self.current_class is not None:
397
+ return
398
+ if isinstance(node.target, ast.Name) and node.target.id in self.used_names:
399
+ code = ast.get_source_segment(self.source, node)
400
+ if code is not None:
401
+ self.assignments.append(code)
402
+
403
+
404
+ def _collect_parameter_names(tree: ast.Module) -> set[str]:
405
+ """Returns set of all parameter names from functions in the AST."""
406
+ params = set()
407
+ for node in ast.walk(tree):
408
+ if isinstance(node, ast.FunctionDef):
409
+ for arg in node.args.args:
410
+ params.add(arg.arg)
411
+ for arg in node.args.kwonlyargs:
412
+ params.add(arg.arg)
413
+ if node.args.vararg:
414
+ params.add(node.args.vararg.arg)
415
+ if node.args.kwarg:
416
+ params.add(node.args.kwarg.arg)
417
+ return params
418
+
419
+
420
+ def _extract_types(annotation: Any) -> set[type]: # noqa: ANN401
421
+ """Returns set of types found in a type annotation."""
422
+ types_found: set[type] = set()
423
+ origin = get_origin(annotation)
424
+
425
+ if origin is not None:
426
+ if origin is Annotated:
427
+ base_annotation, *_ = get_args(annotation)
428
+ types_found |= _extract_types(base_annotation)
429
+ else:
430
+ for arg in get_args(annotation):
431
+ types_found |= _extract_types(arg)
432
+ elif isinstance(annotation, type) and not _is_stdlib_or_builtin(annotation):
433
+ types_found.add(annotation)
434
+ return types_found
435
+
436
+
437
+ def _is_stdlib_or_builtin(obj: Any) -> bool: # noqa: ANN401
438
+ """Returns True if object is from standard library or builtins."""
439
+ if not hasattr(obj, "__module__"):
440
+ return False
441
+
442
+ module_name = obj.__module__
443
+ if not module_name:
444
+ return False
445
+
446
+ return (
447
+ module_name in sys.stdlib_module_names
448
+ or module_name.startswith("collections.")
449
+ or module_name.startswith("typing.")
450
+ or module_name in {"abc", "typing", "builtins", "_collections_abc"}
451
+ )
452
+
453
+
454
+ class _DefinitionCollector(ast.NodeVisitor):
455
+ """AST visitor that collects function and class definitions referenced in code."""
456
+
457
+ def __init__(
458
+ self, module: ModuleType, used_names: list[str], site_packages: set[str]
459
+ ) -> None:
460
+ self.module = module
461
+ self.used_names = used_names
462
+ self.site_packages = site_packages
463
+ self.definitions_to_include: list[Callable[..., Any] | type] = []
464
+ self.definitions_to_analyze: list[Callable[..., Any] | type] = []
465
+ self.imports: set[str] = set()
466
+
467
+ def visit_Name(self, node: ast.Name) -> None:
468
+ """Collects named references to callable definitions."""
469
+ if node.id in self.used_names:
470
+ candidate = getattr(self.module, node.id, None)
471
+ if callable(candidate) and not _is_stdlib_or_builtin(candidate):
472
+ self.definitions_to_include.append(candidate)
473
+ self.generic_visit(node)
474
+
475
+ def _process_decorator(self, decorator_node: ast.AST) -> None:
476
+ """Processes a decorator node to extract its definition."""
477
+ if isinstance(decorator_node, ast.Name):
478
+ if decorator_func := getattr(self.module, decorator_node.id, None):
479
+ self.definitions_to_include.append(decorator_func)
480
+ elif isinstance(decorator_node, ast.Attribute):
481
+ attr_path = _AttributePath.from_ast_node(decorator_node)
482
+ if attr_path:
483
+ base_module = getattr(self.module, attr_path.base_name, None)
484
+ if (
485
+ attr_path.full_path in self.used_names
486
+ and base_module
487
+ and (
488
+ definition := getattr(
489
+ base_module, attr_path.last_attribute, None
490
+ )
491
+ )
492
+ ):
493
+ self.definitions_to_include.append(definition)
494
+
495
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
496
+ """Collects function definitions and their decorators."""
497
+ for decorator_node in node.decorator_list:
498
+ self._process_decorator(decorator_node)
499
+
500
+ nested_func = getattr(self.module, node.name, None)
501
+ if nested_func:
502
+ self.definitions_to_analyze.append(nested_func)
503
+
504
+ self.generic_visit(node)
505
+
506
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
507
+ """Collects class definitions and their type annotations."""
508
+ if class_def := getattr(self.module, node.name, None):
509
+ self.definitions_to_analyze.append(class_def)
510
+ if hasattr(class_def, "__annotations__"):
511
+ for ann in class_def.__annotations__.values():
512
+ for candidate in _extract_types(ann):
513
+ if (
514
+ isinstance(candidate, type)
515
+ and candidate.__module__ == class_def.__module__
516
+ and candidate.__module__ != "builtins"
517
+ ) and candidate not in self.definitions_to_include:
518
+ self.definitions_to_include.append(candidate)
519
+ for item in node.body:
520
+ if isinstance(item, ast.FunctionDef) and (
521
+ definition := getattr(class_def, item.name, None)
522
+ ):
523
+ self.definitions_to_analyze.append(definition)
524
+ self.generic_visit(node)
525
+
526
+ def _process_name_or_attribute(self, node: ast.AST) -> None:
527
+ """Processes name or attribute nodes to find definitions."""
528
+ if isinstance(node, ast.Name):
529
+ if (
530
+ (obj := getattr(self.module, node.id, None))
531
+ and hasattr(obj, "__name__")
532
+ and not _is_stdlib_or_builtin(obj)
533
+ ):
534
+ self.definitions_to_include.append(obj)
535
+ elif isinstance(node, ast.Attribute):
536
+ attr_path = _AttributePath.from_ast_node(node)
537
+ if not attr_path or attr_path.full_path not in self.used_names:
538
+ return
539
+
540
+ base_module = getattr(self.module, attr_path.base_name, None)
541
+ if (
542
+ base_module
543
+ and isinstance(base_module, ModuleType)
544
+ and _is_third_party(base_module, self.site_packages)
545
+ ):
546
+ return
547
+
548
+ obj = self.module
549
+ for component in attr_path.components:
550
+ obj = getattr(obj, component, None)
551
+ if obj is None:
552
+ break
553
+
554
+ if (
555
+ obj
556
+ and hasattr(obj, "__name__")
557
+ and not _is_stdlib_or_builtin(obj)
558
+ and not isinstance(obj, ModuleType)
559
+ ):
560
+ self.definitions_to_include.append(obj)
561
+
562
+ def visit_Call(self, node: ast.Call) -> None:
563
+ """Collects definitions referenced in function calls."""
564
+ self._process_name_or_attribute(node.func)
565
+ for arg in node.args:
566
+ self._process_name_or_attribute(arg)
567
+ for keyword in node.keywords:
568
+ self._process_name_or_attribute(keyword.value)
569
+ self.generic_visit(node)
570
+
571
+
572
+ class _QualifiedNameRewriter(cst.CSTTransformer):
573
+ """CST transformer that rewrites qualified names to simple names for local definitions."""
574
+
575
+ def __init__(self, local_names: set[str], user_defined_imports: set[str]) -> None:
576
+ super().__init__()
577
+ self.local_names: set[str] = local_names
578
+ self.alias_mapping = {}
579
+ for import_stmt in user_defined_imports:
580
+ if not import_stmt.startswith("from "):
581
+ continue
582
+ parts = import_stmt.split(" ")
583
+ if len(parts) >= 4 and "as" in parts:
584
+ original_name = parts[parts.index("import") + 1]
585
+ alias = parts[parts.index("as") + 1]
586
+ self.alias_mapping[alias] = original_name
587
+
588
+ def _gather_attribute_chain(self, node: cst.Attribute | cst.Name) -> list[str]:
589
+ """Returns the chain of attribute names from an attribute node."""
590
+ names = []
591
+ current = node
592
+
593
+ while isinstance(current, cst.Attribute):
594
+ names.append(current.attr.value)
595
+ current = current.value
596
+
597
+ if isinstance(current, cst.Name):
598
+ names.append(current.value)
599
+
600
+ return list(reversed(names))
601
+
602
+ def leave_Attribute(
603
+ self, original_node: cst.Attribute, updated_node: cst.Attribute
604
+ ) -> cst.Name | cst.Attribute:
605
+ """Returns simplified name if attribute refers to local definition."""
606
+ names = self._gather_attribute_chain(updated_node)
607
+ if names and names[-1] in self.local_names:
608
+ return cst.Name(value=names[-1])
609
+
610
+ return updated_node
611
+
612
+ def leave_Name(self, original_node: cst.Name, updated_node: cst.Name) -> cst.Name:
613
+ """Returns de-aliased name if it was imported with an alias."""
614
+ if updated_node.value in self.alias_mapping:
615
+ return cst.Name(
616
+ value=self.alias_mapping[updated_node.value],
617
+ lpar=updated_node.lpar,
618
+ rpar=updated_node.rpar,
619
+ )
620
+ return updated_node
621
+
622
+
623
+ def _get_class_from_unbound_method(method: Callable[..., Any]) -> type | None:
624
+ """Returns the class that contains the given unbound method."""
625
+ qualname = method.__qualname__
626
+ parts = qualname.split(".")
627
+ class_qualname = ".".join(parts[:-1])
628
+
629
+ for obj in gc.get_objects():
630
+ try:
631
+ object_is_type = isinstance(obj, type)
632
+ except Exception:
633
+ continue
634
+ if object_is_type and getattr(obj, "__qualname__", None) == class_qualname:
635
+ return obj
636
+ return None
637
+
638
+
639
+ def _clean_source_from_string(source: str, exclude_fn_body: bool = False) -> str:
640
+ """Returns cleaned source code string with optional docstring removal."""
641
+ source = dedent(source)
642
+ module = cst.parse_module(source)
643
+ module = module.visit(_RemoveVersionDecoratorTransformer())
644
+ module = module.visit(_RemoveDocstringTransformer(exclude_fn_body=exclude_fn_body))
645
+ return module.code.rstrip()
646
+
647
+
648
+ def _get_class_source_from_method(method: Callable[..., Any]) -> str:
649
+ """Get the source code of the class containing the given method.
650
+
651
+ Args:
652
+ method: The method to get the containing class source from.
653
+
654
+ Returns:
655
+ The cleaned source code of the containing class.
656
+
657
+ Raises:
658
+ ValueError: If the class cannot be determined from the method.
659
+ """
660
+ cls = _get_class_from_unbound_method(method)
661
+ if cls is None:
662
+ raise ValueError("Cannot determine class from method via gc")
663
+ source = inspect.getsource(cls)
664
+ return _clean_source_from_string(source)
665
+
666
+
667
+ class _DependencyCollector:
668
+ """Collects dependencies, imports, and source code for function closure."""
669
+
670
+ def __init__(self) -> None:
671
+ self.imports: set[str] = set()
672
+ self.fn_internal_imports: set[str] = set()
673
+ self.user_defined_imports: set[str] = set()
674
+ self.assignments: list[str] = []
675
+ self.source_code: list[str] = []
676
+ self.visited_functions: set[str] = set()
677
+ self.site_packages: set[str] = {
678
+ str(Path(p).resolve()) for p in site.getsitepackages()
679
+ }
680
+ self._last_import_collector: _ImportCollector | None = None
681
+
682
+ def _collect_assignments_and_imports(
683
+ self,
684
+ fn_tree: ast.Module,
685
+ module_tree: ast.Module,
686
+ used_names: list[str],
687
+ module_source: str,
688
+ ) -> None:
689
+ """Collects global assignments and their required imports."""
690
+ local_assignment_collector = _LocalAssignmentCollector()
691
+ local_assignment_collector.visit(fn_tree)
692
+ local_assignments = local_assignment_collector.assignments
693
+
694
+ parameter_names = _collect_parameter_names(fn_tree)
695
+
696
+ global_assignment_collector = _GlobalAssignmentCollector(
697
+ used_names, module_source
698
+ )
699
+ global_assignment_collector.visit(module_tree)
700
+
701
+ for global_assignment in global_assignment_collector.assignments:
702
+ tree = ast.parse(global_assignment)
703
+ stmt = cast(ast.Assign | ast.AnnAssign, tree.body[0])
704
+ if isinstance(stmt, ast.Assign):
705
+ var_name = cast(ast.Name, stmt.targets[0]).id
706
+ else:
707
+ var_name = cast(ast.Name, stmt.target).id
708
+
709
+ if var_name in parameter_names:
710
+ continue
711
+
712
+ if var_name not in used_names or var_name in local_assignments:
713
+ continue
714
+
715
+ self.assignments.append(global_assignment)
716
+
717
+ name_collector = _NameCollector()
718
+ name_collector.visit(tree)
719
+ import_collector = _ImportCollector(
720
+ name_collector.used_names, self.site_packages
721
+ )
722
+ import_collector.visit(module_tree)
723
+ self.imports.update(import_collector.imports)
724
+ self.user_defined_imports.update(import_collector.user_defined_imports)
725
+
726
+ @staticmethod
727
+ def _extract_definition(
728
+ definition: Callable[..., Any] | type | property,
729
+ ) -> Callable[..., Any] | type | None:
730
+ """Returns the actual definition from decorators and properties."""
731
+ if isinstance(definition, property):
732
+ return definition.fget
733
+
734
+ if isinstance(definition, cached_property) or (
735
+ hasattr(definition, "func")
736
+ and getattr(definition, "__name__", None) is None
737
+ ):
738
+ # For Python 3.13+
739
+ return definition.func # pyright: ignore[reportFunctionMemberAccess] # pragma: no cover
740
+
741
+ # Handle objects with .fn but no __qualname__ (e.g., old-style wrappers).
742
+ # With copy_function_metadata() now copying __qualname__ to ToolSchema, Prompt,
743
+ # Call, etc., this branch is no longer reached in normal usage.
744
+ if (
745
+ (wrapped_function := getattr(definition, "fn", None)) is not None
746
+ and not hasattr(definition, "__qualname__")
747
+ and callable(wrapped_function)
748
+ ):
749
+ return wrapped_function # pragma: no cover
750
+
751
+ return definition
752
+
753
+ def _get_source_code(self, definition: Callable[..., Any] | type) -> str | None:
754
+ """Returns the source code for a definition."""
755
+ if definition.__qualname__ in self.visited_functions:
756
+ return None
757
+ self.visited_functions.add(definition.__qualname__)
758
+
759
+ if "." in definition.__qualname__ and inspect.getmodule(definition) is not None:
760
+ try:
761
+ return _get_class_source_from_method(definition)
762
+ except ValueError:
763
+ return _clean_source_code(definition)
764
+
765
+ return _clean_source_code(definition)
766
+
767
+ def _process_imports(
768
+ self,
769
+ module_tree: ast.Module,
770
+ used_names: list[str],
771
+ source: str,
772
+ ) -> None:
773
+ """Process and categorize imports."""
774
+ import_collector = _ImportCollector(used_names, self.site_packages)
775
+ import_collector.visit(module_tree)
776
+
777
+ new_imports = {
778
+ import_stmt
779
+ for import_stmt in import_collector.imports
780
+ if import_stmt not in source
781
+ }
782
+
783
+ self.imports.update(new_imports)
784
+ self.fn_internal_imports.update(import_collector.imports - new_imports)
785
+ self.user_defined_imports.update(import_collector.user_defined_imports)
786
+
787
+ def _process_definitions(
788
+ self, fn_tree: ast.Module, module: ModuleType, used_names: list[str]
789
+ ) -> None:
790
+ """Process nested definitions and dependencies."""
791
+ definition_collector = _DefinitionCollector(
792
+ module, used_names, self.site_packages
793
+ )
794
+ definition_collector.visit(fn_tree)
795
+
796
+ for definition in definition_collector.definitions_to_include:
797
+ self._collect_imports_and_source_code(definition, True)
798
+
799
+ for definition in definition_collector.definitions_to_analyze:
800
+ self._collect_imports_and_source_code(definition, False)
801
+
802
+ def _collect_imports_and_source_code(
803
+ self,
804
+ definition: Callable[..., Any] | type | property,
805
+ include_source: bool,
806
+ ) -> None:
807
+ """Collects imports and optionally source code for a definition."""
808
+ try:
809
+ if _is_stdlib_or_builtin(definition) or isinstance(definition, ModuleType):
810
+ return
811
+
812
+ # property(fget=None) is not reachable via current code paths but kept as guard
813
+ if (
814
+ isinstance(definition, property) and definition.fget is None
815
+ ): # pragma: no cover
816
+ return
817
+
818
+ extracted_definition = _DependencyCollector._extract_definition(definition)
819
+ # Same guard as above; kept for defensive coding
820
+ if extracted_definition is None: # pragma: no cover
821
+ return
822
+
823
+ source = self._get_source_code(extracted_definition)
824
+ if source is None:
825
+ return
826
+
827
+ module = inspect.getmodule(extracted_definition)
828
+ if not module or _is_third_party(module, self.site_packages):
829
+ return
830
+
831
+ module_source = inspect.getsource(module)
832
+ module_tree = ast.parse(module_source)
833
+ fn_tree = ast.parse(source)
834
+
835
+ name_collector = _NameCollector()
836
+ name_collector.visit(fn_tree)
837
+ used_names = list(dict.fromkeys(name_collector.used_names))
838
+
839
+ self._process_imports(module_tree, used_names, source)
840
+
841
+ if include_source:
842
+ for import_stmt in self.user_defined_imports:
843
+ source = source.replace(import_stmt, "")
844
+ self.source_code.insert(0, source)
845
+
846
+ self._collect_assignments_and_imports(
847
+ fn_tree, module_tree, used_names, module_source
848
+ )
849
+
850
+ self._process_definitions(fn_tree, module, used_names)
851
+
852
+ except (OSError, TypeError) as e:
853
+ logger.debug(f"Failed to collect imports for {definition}: {e}")
854
+
855
+ @staticmethod
856
+ def _collect_required_dependencies(imports: set[str]) -> dict[str, dict[str, Any]]:
857
+ """Returns package dependencies required by the import statements."""
858
+ stdlib_modules = set(sys.stdlib_module_names)
859
+ installed_packages = {
860
+ dist.name: dist for dist in importlib.metadata.distributions()
861
+ }
862
+ import_to_dist = importlib.metadata.packages_distributions()
863
+
864
+ dependencies = {}
865
+ imported_dists = {}
866
+ imported_roots = set()
867
+
868
+ for import_stmt in imports:
869
+ parts = import_stmt.strip().split()
870
+ root_module = parts[1].split(".")[0]
871
+ if root_module in stdlib_modules:
872
+ continue
873
+
874
+ imported_roots.add(root_module)
875
+
876
+ dist_names = import_to_dist.get(root_module, [root_module])
877
+ for dist_name in dist_names:
878
+ if dist_name not in installed_packages:
879
+ continue
880
+
881
+ dist = installed_packages[dist_name]
882
+ imported_dists.setdefault(dist_name, dist)
883
+ if dist_name not in dependencies:
884
+ dependencies[dist_name] = {
885
+ "version": dist.version,
886
+ "extras": None,
887
+ }
888
+ break
889
+
890
+ if not imported_dists:
891
+ return {}
892
+
893
+ dist_to_modules = {}
894
+ for module_name, dist_names in import_to_dist.items():
895
+ for dist_name in dist_names:
896
+ dist_to_modules.setdefault(dist_name, set()).add(module_name)
897
+
898
+ base_env = cast(dict[str, str], default_environment().copy())
899
+ base_env["extra"] = ""
900
+ extra_env_cache = {}
901
+
902
+ def _env_for_extra(extra: str) -> dict[str, str]:
903
+ if extra not in extra_env_cache:
904
+ env = cast(dict[str, str], default_environment().copy())
905
+ env["extra"] = extra
906
+ extra_env_cache[extra] = env
907
+ return extra_env_cache[extra]
908
+
909
+ base_requirements = {}
910
+ extra_requirements = {}
911
+
912
+ for dist_name, dist in imported_dists.items():
913
+ base_reqs = set()
914
+ extras_map = {
915
+ extra: set() for extra in dist.metadata.get_all("Provides-Extra", [])
916
+ }
917
+ requirements = dist.requires or []
918
+ for requirement_str in requirements:
919
+ req = Requirement(requirement_str)
920
+ marker = req.marker
921
+ if marker is None or marker.evaluate(base_env):
922
+ base_reqs.add(req.name)
923
+ continue
924
+
925
+ for extra in extras_map:
926
+ if marker.evaluate(_env_for_extra(extra)):
927
+ extras_map[extra].add(req.name)
928
+
929
+ base_requirements[dist_name] = base_reqs
930
+ extra_requirements[dist_name] = extras_map
931
+
932
+ provided_requirements = set()
933
+ for reqs in base_requirements.values():
934
+ provided_requirements.update(reqs)
935
+ provided_requirements.update(imported_dists.keys())
936
+
937
+ for dist_name in sorted(imported_dists):
938
+ extras_to_keep = []
939
+ apply_usage_gate = not dist_name.startswith("mirascope")
940
+ for extra, deps in extra_requirements[dist_name].items():
941
+ if not deps:
942
+ continue
943
+
944
+ if apply_usage_gate and not any(
945
+ dist_to_modules.get(dep, set()) & imported_roots for dep in deps
946
+ ):
947
+ continue
948
+
949
+ missing = [dep for dep in deps if dep not in provided_requirements]
950
+ if missing:
951
+ extras_to_keep.append(extra)
952
+ provided_requirements.update(deps)
953
+
954
+ dependencies[dist_name]["extras"] = extras_to_keep or None
955
+
956
+ return dependencies
957
+
958
+ @classmethod
959
+ def _map_child_to_parent(
960
+ cls,
961
+ child_to_parent: dict[ast.AST, ast.AST | None],
962
+ node: ast.AST,
963
+ parent: ast.AST | None = None,
964
+ ) -> None:
965
+ """Maps each AST node to its parent node."""
966
+ child_to_parent[node] = parent
967
+ for _, value in ast.iter_fields(node):
968
+ if isinstance(value, list):
969
+ for child in value:
970
+ if isinstance(child, ast.AST):
971
+ cls._map_child_to_parent(child_to_parent, child, node)
972
+ elif isinstance(value, ast.AST):
973
+ cls._map_child_to_parent(child_to_parent, value, node)
974
+
975
+ def _extract_local_names(self, code_blocks: list[str]) -> set[str]:
976
+ """Extracts names of locally defined functions and classes."""
977
+ local_names = set()
978
+
979
+ for code in code_blocks:
980
+ tree = ast.parse(code)
981
+ child_to_parent = {}
982
+ self._map_child_to_parent(child_to_parent, tree)
983
+
984
+ for node in ast.walk(tree):
985
+ if isinstance(node, ast.FunctionDef | ast.ClassDef):
986
+ parent = child_to_parent.get(node)
987
+ if isinstance(parent, ast.Module):
988
+ local_names.add(node.name)
989
+
990
+ return local_names
991
+
992
+ @staticmethod
993
+ def _rewrite_code_blocks(
994
+ code_blocks: list[str], rewriter: _QualifiedNameRewriter
995
+ ) -> list[str]:
996
+ """Rewrites code blocks with simplified names."""
997
+ rewritten = []
998
+ for code in code_blocks:
999
+ tree = cst.parse_module(code)
1000
+ new_tree = tree.visit(rewriter)
1001
+ rewritten.append(new_tree.code)
1002
+ return rewritten
1003
+
1004
+ def collect(
1005
+ self, fn: Callable[..., Any]
1006
+ ) -> tuple[list[str], list[str], list[str], dict[str, dict[str, Any]]]:
1007
+ """Collects all components needed for function closure.
1008
+
1009
+ Args:
1010
+ fn: The function to collect closure information for.
1011
+
1012
+ Returns:
1013
+ A tuple containing:
1014
+ - List of import statements
1015
+ - List of assignment statements
1016
+ - List of source code blocks
1017
+ - Dictionary of required dependencies
1018
+ """
1019
+ self._collect_imports_and_source_code(fn, True)
1020
+
1021
+ local_names = self._extract_local_names(self.source_code + self.assignments)
1022
+ rewriter = _QualifiedNameRewriter(local_names, self.user_defined_imports)
1023
+
1024
+ assignments = self._rewrite_code_blocks(self.assignments, rewriter)
1025
+ source_code = self._rewrite_code_blocks(self.source_code, rewriter)
1026
+
1027
+ required_dependencies = _DependencyCollector._collect_required_dependencies(
1028
+ self.imports | self.fn_internal_imports
1029
+ )
1030
+
1031
+ return (
1032
+ list(self.imports),
1033
+ list(dict.fromkeys(assignments)),
1034
+ source_code,
1035
+ required_dependencies,
1036
+ )
1037
+
1038
+
1039
+ def _run_ruff(code: str) -> str:
1040
+ """Returns formatted code using ruff formatter."""
1041
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as tmp_file:
1042
+ tmp_file.write(code)
1043
+ tmp_path = Path(tmp_file.name)
1044
+
1045
+ try:
1046
+ proc = subprocess.run(
1047
+ ["ruff", "check", "--isolated", "--select=I001", "--fix", str(tmp_path)],
1048
+ capture_output=True,
1049
+ text=True,
1050
+ )
1051
+
1052
+ if proc.returncode not in (0, 1):
1053
+ raise subprocess.CalledProcessError(
1054
+ proc.returncode, proc.args, output=proc.stdout, stderr=proc.stderr
1055
+ )
1056
+
1057
+ subprocess.run(
1058
+ ["ruff", "format", "--isolated", "--line-length=88", str(tmp_path)],
1059
+ check=True,
1060
+ capture_output=True,
1061
+ text=True,
1062
+ )
1063
+ processed_code = tmp_path.read_text()
1064
+ return processed_code
1065
+ finally:
1066
+ tmp_path.unlink()
1067
+
1068
+
1069
+ def get_qualified_name(fn: Callable[..., Any]) -> str:
1070
+ """Return the simplified qualified name of a function.
1071
+
1072
+ If the function is defined locally, return the name after '<locals>.';
1073
+ otherwise, return the last non-empty part after splitting by '.'.
1074
+
1075
+ Args:
1076
+ fn: The function to get the qualified name from.
1077
+
1078
+ Returns:
1079
+ The simplified qualified name of the function.
1080
+ """
1081
+ qualified_name = fn.__qualname__
1082
+ if "<locals>." in qualified_name:
1083
+ return qualified_name.split("<locals>.")[-1]
1084
+ else:
1085
+ parts = [part for part in qualified_name.split(".") if part]
1086
+ return parts[-1] if parts else qualified_name
1087
+
1088
+
1089
+ class DependencyInfo(TypedDict):
1090
+ """Represents the dependency information for a closure."""
1091
+
1092
+ version: str
1093
+ """The version of the dependency."""
1094
+
1095
+ extras: list[str] | None
1096
+ """The extras required for the dependency."""
1097
+
1098
+
1099
+ @dataclass(frozen=True, kw_only=True)
1100
+ class Closure:
1101
+ """Represents the closure of a function."""
1102
+
1103
+ name: str
1104
+ """The name of the function."""
1105
+
1106
+ signature: str
1107
+ """The signature of the function."""
1108
+
1109
+ docstring: str | None
1110
+ """The docstring of the function."""
1111
+
1112
+ code: str
1113
+ """The code of the function."""
1114
+
1115
+ hash: str
1116
+ """The hash of the closure."""
1117
+
1118
+ signature_hash: str
1119
+ """The hash of the function signature (determines major version X)."""
1120
+
1121
+ dependencies: dict[str, DependencyInfo]
1122
+ """The dependencies of the closure."""
1123
+
1124
+ @classmethod
1125
+ @lru_cache(maxsize=128)
1126
+ def from_fn(cls, fn: Callable[..., Any]) -> Closure:
1127
+ """Create a closure from a function.
1128
+
1129
+ Args:
1130
+ fn: The function to analyze
1131
+
1132
+ Returns:
1133
+ Closure: The closure of the function.
1134
+
1135
+ Raises:
1136
+ ClosureComputationError: if the closure cannot be computed properly.
1137
+ """
1138
+ collector = _DependencyCollector()
1139
+ imports, assignments, source_code, dependencies = collector.collect(fn)
1140
+ code = "{imports}\n\n{assignments}\n\n{source_code}".format(
1141
+ imports="\n".join(imports),
1142
+ assignments="\n".join(assignments),
1143
+ source_code="\n\n".join(source_code),
1144
+ )
1145
+ qualified_name = get_qualified_name(fn)
1146
+ try:
1147
+ formatted_code = _run_ruff(code)
1148
+ except (subprocess.CalledProcessError, FileNotFoundError, OSError):
1149
+ raise ClosureComputationError(qualified_name=qualified_name)
1150
+ hash_value = hashlib.sha256(formatted_code.encode("utf-8")).hexdigest()
1151
+
1152
+ signature = _run_ruff(_clean_source_code(fn, exclude_fn_body=True)).strip()
1153
+ signature_hash = hashlib.sha256(signature.encode("utf-8")).hexdigest()
1154
+
1155
+ return cls(
1156
+ name=qualified_name,
1157
+ docstring=inspect.getdoc(fn),
1158
+ signature=signature,
1159
+ code=formatted_code,
1160
+ hash=hash_value,
1161
+ signature_hash=signature_hash,
1162
+ dependencies={
1163
+ name: DependencyInfo(
1164
+ version=dep_info["version"],
1165
+ extras=dep_info.get("extras"),
1166
+ )
1167
+ for name, dep_info in dependencies.items()
1168
+ },
1169
+ )
1170
+
1171
+
1172
+ __all__ = ["Closure", "DependencyInfo"]