mirascope 1.0.5__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 (632) hide show
  1. mirascope/__init__.py +6 -6
  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 +316 -0
  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-1.0.5.dist-info → mirascope-2.1.1.dist-info}/WHEEL +1 -1
  437. {mirascope-1.0.5.dist-info → mirascope-2.1.1.dist-info}/licenses/LICENSE +1 -1
  438. mirascope/beta/__init__.py +0 -0
  439. mirascope/beta/openai/__init__.py +0 -5
  440. mirascope/beta/openai/parse.py +0 -129
  441. mirascope/beta/rag/__init__.py +0 -24
  442. mirascope/beta/rag/base/__init__.py +0 -22
  443. mirascope/beta/rag/base/chunkers/__init__.py +0 -2
  444. mirascope/beta/rag/base/chunkers/base_chunker.py +0 -37
  445. mirascope/beta/rag/base/chunkers/text_chunker.py +0 -33
  446. mirascope/beta/rag/base/config.py +0 -8
  447. mirascope/beta/rag/base/document.py +0 -11
  448. mirascope/beta/rag/base/embedders.py +0 -35
  449. mirascope/beta/rag/base/embedding_params.py +0 -18
  450. mirascope/beta/rag/base/embedding_response.py +0 -30
  451. mirascope/beta/rag/base/query_results.py +0 -7
  452. mirascope/beta/rag/base/vectorstore_params.py +0 -18
  453. mirascope/beta/rag/base/vectorstores.py +0 -37
  454. mirascope/beta/rag/chroma/__init__.py +0 -11
  455. mirascope/beta/rag/chroma/types.py +0 -57
  456. mirascope/beta/rag/chroma/vectorstores.py +0 -97
  457. mirascope/beta/rag/cohere/__init__.py +0 -11
  458. mirascope/beta/rag/cohere/embedders.py +0 -87
  459. mirascope/beta/rag/cohere/embedding_params.py +0 -29
  460. mirascope/beta/rag/cohere/embedding_response.py +0 -29
  461. mirascope/beta/rag/cohere/py.typed +0 -0
  462. mirascope/beta/rag/openai/__init__.py +0 -11
  463. mirascope/beta/rag/openai/embedders.py +0 -144
  464. mirascope/beta/rag/openai/embedding_params.py +0 -18
  465. mirascope/beta/rag/openai/embedding_response.py +0 -14
  466. mirascope/beta/rag/openai/py.typed +0 -0
  467. mirascope/beta/rag/pinecone/__init__.py +0 -19
  468. mirascope/beta/rag/pinecone/types.py +0 -143
  469. mirascope/beta/rag/pinecone/vectorstores.py +0 -148
  470. mirascope/beta/rag/weaviate/__init__.py +0 -6
  471. mirascope/beta/rag/weaviate/types.py +0 -92
  472. mirascope/beta/rag/weaviate/vectorstores.py +0 -103
  473. mirascope/core/__init__.py +0 -55
  474. mirascope/core/anthropic/__init__.py +0 -21
  475. mirascope/core/anthropic/_call.py +0 -71
  476. mirascope/core/anthropic/_utils/__init__.py +0 -16
  477. mirascope/core/anthropic/_utils/_calculate_cost.py +0 -63
  478. mirascope/core/anthropic/_utils/_convert_message_params.py +0 -54
  479. mirascope/core/anthropic/_utils/_get_json_output.py +0 -34
  480. mirascope/core/anthropic/_utils/_handle_stream.py +0 -89
  481. mirascope/core/anthropic/_utils/_setup_call.py +0 -76
  482. mirascope/core/anthropic/call_params.py +0 -36
  483. mirascope/core/anthropic/call_response.py +0 -158
  484. mirascope/core/anthropic/call_response_chunk.py +0 -104
  485. mirascope/core/anthropic/dynamic_config.py +0 -26
  486. mirascope/core/anthropic/py.typed +0 -0
  487. mirascope/core/anthropic/stream.py +0 -140
  488. mirascope/core/anthropic/tool.py +0 -77
  489. mirascope/core/base/__init__.py +0 -40
  490. mirascope/core/base/_call_factory.py +0 -323
  491. mirascope/core/base/_create.py +0 -167
  492. mirascope/core/base/_extract.py +0 -139
  493. mirascope/core/base/_partial.py +0 -63
  494. mirascope/core/base/_utils/__init__.py +0 -64
  495. mirascope/core/base/_utils/_base_type.py +0 -17
  496. mirascope/core/base/_utils/_convert_base_model_to_base_tool.py +0 -45
  497. mirascope/core/base/_utils/_convert_base_type_to_base_tool.py +0 -24
  498. mirascope/core/base/_utils/_convert_function_to_base_tool.py +0 -126
  499. mirascope/core/base/_utils/_default_tool_docstring.py +0 -6
  500. mirascope/core/base/_utils/_extract_tool_return.py +0 -36
  501. mirascope/core/base/_utils/_format_template.py +0 -29
  502. mirascope/core/base/_utils/_get_audio_type.py +0 -18
  503. mirascope/core/base/_utils/_get_fn_args.py +0 -14
  504. mirascope/core/base/_utils/_get_image_type.py +0 -26
  505. mirascope/core/base/_utils/_get_metadata.py +0 -17
  506. mirascope/core/base/_utils/_get_possible_user_message_param.py +0 -21
  507. mirascope/core/base/_utils/_get_prompt_template.py +0 -25
  508. mirascope/core/base/_utils/_get_template_values.py +0 -52
  509. mirascope/core/base/_utils/_get_template_variables.py +0 -38
  510. mirascope/core/base/_utils/_json_mode_content.py +0 -15
  511. mirascope/core/base/_utils/_parse_content_template.py +0 -157
  512. mirascope/core/base/_utils/_parse_prompt_messages.py +0 -51
  513. mirascope/core/base/_utils/_protocols.py +0 -215
  514. mirascope/core/base/_utils/_setup_call.py +0 -64
  515. mirascope/core/base/_utils/_setup_extract_tool.py +0 -24
  516. mirascope/core/base/call_params.py +0 -6
  517. mirascope/core/base/call_response.py +0 -189
  518. mirascope/core/base/call_response_chunk.py +0 -91
  519. mirascope/core/base/dynamic_config.py +0 -55
  520. mirascope/core/base/message_param.py +0 -61
  521. mirascope/core/base/metadata.py +0 -13
  522. mirascope/core/base/prompt.py +0 -415
  523. mirascope/core/base/stream.py +0 -365
  524. mirascope/core/base/structured_stream.py +0 -251
  525. mirascope/core/base/tool.py +0 -126
  526. mirascope/core/base/toolkit.py +0 -146
  527. mirascope/core/cohere/__init__.py +0 -21
  528. mirascope/core/cohere/_call.py +0 -71
  529. mirascope/core/cohere/_utils/__init__.py +0 -16
  530. mirascope/core/cohere/_utils/_calculate_cost.py +0 -39
  531. mirascope/core/cohere/_utils/_convert_message_params.py +0 -31
  532. mirascope/core/cohere/_utils/_get_json_output.py +0 -31
  533. mirascope/core/cohere/_utils/_handle_stream.py +0 -33
  534. mirascope/core/cohere/_utils/_setup_call.py +0 -89
  535. mirascope/core/cohere/call_params.py +0 -57
  536. mirascope/core/cohere/call_response.py +0 -167
  537. mirascope/core/cohere/call_response_chunk.py +0 -101
  538. mirascope/core/cohere/dynamic_config.py +0 -24
  539. mirascope/core/cohere/py.typed +0 -0
  540. mirascope/core/cohere/stream.py +0 -113
  541. mirascope/core/cohere/tool.py +0 -92
  542. mirascope/core/gemini/__init__.py +0 -21
  543. mirascope/core/gemini/_call.py +0 -71
  544. mirascope/core/gemini/_utils/__init__.py +0 -16
  545. mirascope/core/gemini/_utils/_calculate_cost.py +0 -8
  546. mirascope/core/gemini/_utils/_convert_message_params.py +0 -74
  547. mirascope/core/gemini/_utils/_get_json_output.py +0 -33
  548. mirascope/core/gemini/_utils/_handle_stream.py +0 -33
  549. mirascope/core/gemini/_utils/_setup_call.py +0 -68
  550. mirascope/core/gemini/call_params.py +0 -28
  551. mirascope/core/gemini/call_response.py +0 -173
  552. mirascope/core/gemini/call_response_chunk.py +0 -85
  553. mirascope/core/gemini/dynamic_config.py +0 -26
  554. mirascope/core/gemini/stream.py +0 -121
  555. mirascope/core/gemini/tool.py +0 -104
  556. mirascope/core/groq/__init__.py +0 -21
  557. mirascope/core/groq/_call.py +0 -71
  558. mirascope/core/groq/_utils/__init__.py +0 -16
  559. mirascope/core/groq/_utils/_calculate_cost.py +0 -68
  560. mirascope/core/groq/_utils/_convert_message_params.py +0 -23
  561. mirascope/core/groq/_utils/_get_json_output.py +0 -27
  562. mirascope/core/groq/_utils/_handle_stream.py +0 -121
  563. mirascope/core/groq/_utils/_setup_call.py +0 -67
  564. mirascope/core/groq/call_params.py +0 -51
  565. mirascope/core/groq/call_response.py +0 -160
  566. mirascope/core/groq/call_response_chunk.py +0 -89
  567. mirascope/core/groq/dynamic_config.py +0 -26
  568. mirascope/core/groq/py.typed +0 -0
  569. mirascope/core/groq/stream.py +0 -136
  570. mirascope/core/groq/tool.py +0 -79
  571. mirascope/core/litellm/__init__.py +0 -6
  572. mirascope/core/litellm/_call.py +0 -73
  573. mirascope/core/litellm/_utils/__init__.py +0 -5
  574. mirascope/core/litellm/_utils/_setup_call.py +0 -46
  575. mirascope/core/litellm/py.typed +0 -0
  576. mirascope/core/mistral/__init__.py +0 -21
  577. mirascope/core/mistral/_call.py +0 -69
  578. mirascope/core/mistral/_utils/__init__.py +0 -16
  579. mirascope/core/mistral/_utils/_calculate_cost.py +0 -47
  580. mirascope/core/mistral/_utils/_convert_message_params.py +0 -23
  581. mirascope/core/mistral/_utils/_get_json_output.py +0 -28
  582. mirascope/core/mistral/_utils/_handle_stream.py +0 -121
  583. mirascope/core/mistral/_utils/_setup_call.py +0 -86
  584. mirascope/core/mistral/call_params.py +0 -36
  585. mirascope/core/mistral/call_response.py +0 -156
  586. mirascope/core/mistral/call_response_chunk.py +0 -84
  587. mirascope/core/mistral/dynamic_config.py +0 -24
  588. mirascope/core/mistral/py.typed +0 -0
  589. mirascope/core/mistral/stream.py +0 -117
  590. mirascope/core/mistral/tool.py +0 -77
  591. mirascope/core/openai/__init__.py +0 -21
  592. mirascope/core/openai/_call.py +0 -71
  593. mirascope/core/openai/_utils/__init__.py +0 -16
  594. mirascope/core/openai/_utils/_calculate_cost.py +0 -110
  595. mirascope/core/openai/_utils/_convert_message_params.py +0 -53
  596. mirascope/core/openai/_utils/_get_json_output.py +0 -27
  597. mirascope/core/openai/_utils/_handle_stream.py +0 -125
  598. mirascope/core/openai/_utils/_setup_call.py +0 -62
  599. mirascope/core/openai/call_params.py +0 -54
  600. mirascope/core/openai/call_response.py +0 -162
  601. mirascope/core/openai/call_response_chunk.py +0 -90
  602. mirascope/core/openai/dynamic_config.py +0 -26
  603. mirascope/core/openai/py.typed +0 -0
  604. mirascope/core/openai/stream.py +0 -148
  605. mirascope/core/openai/tool.py +0 -79
  606. mirascope/core/py.typed +0 -0
  607. mirascope/integrations/__init__.py +0 -20
  608. mirascope/integrations/_middleware_factory.py +0 -277
  609. mirascope/integrations/langfuse/__init__.py +0 -3
  610. mirascope/integrations/langfuse/_utils.py +0 -114
  611. mirascope/integrations/langfuse/_with_langfuse.py +0 -71
  612. mirascope/integrations/logfire/__init__.py +0 -3
  613. mirascope/integrations/logfire/_utils.py +0 -188
  614. mirascope/integrations/logfire/_with_logfire.py +0 -60
  615. mirascope/integrations/otel/__init__.py +0 -5
  616. mirascope/integrations/otel/_utils.py +0 -268
  617. mirascope/integrations/otel/_with_hyperdx.py +0 -61
  618. mirascope/integrations/otel/_with_otel.py +0 -60
  619. mirascope/integrations/tenacity.py +0 -50
  620. mirascope/py.typed +0 -0
  621. mirascope/v0/__init__.py +0 -43
  622. mirascope/v0/anthropic.py +0 -54
  623. mirascope/v0/base/__init__.py +0 -12
  624. mirascope/v0/base/calls.py +0 -118
  625. mirascope/v0/base/extractors.py +0 -122
  626. mirascope/v0/base/ops_utils.py +0 -207
  627. mirascope/v0/base/prompts.py +0 -48
  628. mirascope/v0/base/types.py +0 -14
  629. mirascope/v0/base/utils.py +0 -21
  630. mirascope/v0/openai.py +0 -54
  631. mirascope-1.0.5.dist-info/METADATA +0 -519
  632. mirascope-1.0.5.dist-info/RECORD +0 -198
@@ -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"]