abstra 3.23.3__py3-none-any.whl → 3.23.5__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 (239) hide show
  1. {abstra-3.23.3.dist-info → abstra-3.23.5.dist-info}/METADATA +1 -1
  2. {abstra-3.23.3.dist-info → abstra-3.23.5.dist-info}/RECORD +191 -192
  3. abstra_internals/contracts_generated.py +660 -652
  4. abstra_internals/controllers/execution/consumer.py +27 -39
  5. abstra_internals/controllers/execution/execution.py +7 -4
  6. abstra_internals/controllers/execution/execution_client.py +2 -11
  7. abstra_internals/controllers/execution/execution_client_form.py +10 -11
  8. abstra_internals/controllers/execution/execution_client_hook.py +3 -9
  9. abstra_internals/controllers/execution/execution_stdio.py +3 -14
  10. abstra_internals/controllers/execution/execution_test.py +5 -9
  11. abstra_internals/controllers/execution/worker_process.py +8 -33
  12. abstra_internals/controllers/main.py +18 -26
  13. abstra_internals/controllers/workflows.py +33 -0
  14. abstra_internals/entities/execution_context.py +1 -0
  15. abstra_internals/entities/execution_test.py +6 -3
  16. abstra_internals/entities/forms/widgets/file_upload.py +1 -1
  17. abstra_internals/interface/cli/editor.py +6 -3
  18. abstra_internals/interface/sdk/forms/deprecated/page_test.py +6 -8
  19. abstra_internals/interface/sdk/tables/api_test.py +1 -7
  20. abstra_internals/logs_watcher.py +13 -6
  21. abstra_internals/repositories/consumer.py +1 -3
  22. abstra_internals/repositories/factory.py +7 -6
  23. abstra_internals/repositories/producer.py +7 -41
  24. abstra_internals/repositories/project/project_test.py +1 -6
  25. abstra_internals/repositories/tasks.py +8 -4
  26. abstra_internals/server/blueprints/player.py +39 -24
  27. abstra_internals/server/guards/role_guard_test.py +1 -6
  28. abstra_internals/server/routes/forms.py +10 -3
  29. abstra_internals/server/routes/mcp.py +2 -1
  30. abstra_internals/server/routes/stdio.py +3 -3
  31. abstra_internals/stdio_patcher.py +4 -4
  32. abstra_internals/tasks_watcher.py +59 -0
  33. abstra_internals/utils/ai.py +4 -9
  34. abstra_statics/dist/assets/AbstraButton.vue_vue_type_script_setup_true_lang.ad9e23f0.js +2 -0
  35. abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.ee29465c.js +2 -0
  36. abstra_statics/dist/assets/ApiKeys.958a2342.js +2 -0
  37. abstra_statics/dist/assets/App.6c9e1d73.js +2 -0
  38. abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.46bbdba5.js +2 -0
  39. abstra_statics/dist/assets/{BaseLayout.670b8441.js → BaseLayout.d4459d3a.js} +2 -2
  40. abstra_statics/dist/assets/{Billing.80a50079.js → Billing.920e2b19.js} +2 -2
  41. abstra_statics/dist/assets/{Breadcrumb.1d42c80c.js → Breadcrumb.2d9d636f.js} +2 -2
  42. abstra_statics/dist/assets/{Builds.4641dcf9.js → Builds.11e86dae.js} +2 -2
  43. abstra_statics/dist/assets/{Card.774e5d67.js → Card.0b4293e1.js} +2 -2
  44. abstra_statics/dist/assets/{CircularLoading.02fd7b7b.js → CircularLoading.e2f7dd80.js} +2 -2
  45. abstra_statics/dist/assets/CloseCircleOutlined.196a309a.js +2 -0
  46. abstra_statics/dist/assets/{ConnectorsView.746c7417.js → ConnectorsView.cda5c60f.js} +2 -2
  47. abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.4d7bb007.js +2 -0
  48. abstra_statics/dist/assets/{ContentLayout.4fc85c66.js → ContentLayout.69b8ab09.js} +2 -2
  49. abstra_statics/dist/assets/{CrudView.7f2cbde4.js → CrudView.56ebce1a.js} +2 -2
  50. abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.4eb7d335.js +2 -0
  51. abstra_statics/dist/assets/{EditorLogin.45764526.js → EditorLogin.a10e3a50.js} +2 -2
  52. abstra_statics/dist/assets/{EditorsView.e9452d34.js → EditorsView.1925d1b5.js} +2 -2
  53. abstra_statics/dist/assets/EnvVars.67eed38a.js +2 -0
  54. abstra_statics/dist/assets/Error.66a8e630.js +2 -0
  55. abstra_statics/dist/assets/ExclamationCircleOutlined.3ef65f87.js +2 -0
  56. abstra_statics/dist/assets/{Files.8bcd4d1f.js → Files.e978e721.js} +2 -2
  57. abstra_statics/dist/assets/{Form.288a7191.js → Form.ba1aeab0.js} +2 -2
  58. abstra_statics/dist/assets/{FormRunner.f721431d.js → FormRunner.1a6e12cb.js} +2 -2
  59. abstra_statics/dist/assets/Home.2871cae9.js +2 -0
  60. abstra_statics/dist/assets/Home.de257b98.js +2 -0
  61. abstra_statics/dist/assets/{Live.2bbd1666.js → Live.cf58dd01.js} +2 -2
  62. abstra_statics/dist/assets/LoadingContainer.e6a98e2f.js +2 -0
  63. abstra_statics/dist/assets/LoadingOutlined.398021fd.js +2 -0
  64. abstra_statics/dist/assets/Login.604d7bbd.js +2 -0
  65. abstra_statics/dist/assets/{Login.186f24a9.js → Login.605b23e8.js} +2 -2
  66. abstra_statics/dist/assets/{Login.vue_vue_type_script_setup_true_lang.fab46c0c.js → Login.vue_vue_type_script_setup_true_lang.a8986f39.js} +2 -2
  67. abstra_statics/dist/assets/Logo.0fee0801.js +2 -0
  68. abstra_statics/dist/assets/Logs.e15923a0.js +2 -0
  69. abstra_statics/dist/assets/LogsController.35cad159.js +2 -0
  70. abstra_statics/dist/assets/Main.17024018.js +2 -0
  71. abstra_statics/dist/assets/{MockForm.9b6a244c.js → MockForm.603b1cbb.js} +2 -2
  72. abstra_statics/dist/assets/Navbar.d4751f52.js +2 -0
  73. abstra_statics/dist/assets/{NewEditor.1d7ddf86.js → NewEditor.bd4c258c.js} +5 -5
  74. abstra_statics/dist/assets/OidcLoginCallback.14f293b0.js +2 -0
  75. abstra_statics/dist/assets/OidcLogoutCallback.cb05eca4.js +2 -0
  76. abstra_statics/dist/assets/{OmniChat.5e68be1c.js → OmniChat.6d6fed9c.js} +2 -2
  77. abstra_statics/dist/assets/{OnboardingView.8733d184.js → OnboardingView.546afa14.js} +2 -2
  78. abstra_statics/dist/assets/{Organization.bcb76b94.js → Organization.a7309bfd.js} +2 -2
  79. abstra_statics/dist/assets/Organizations.91c269e6.js +2 -0
  80. abstra_statics/dist/assets/{PhArrowCounterClockwise.vue.d273955f.js → PhArrowCounterClockwise.vue.2706c012.js} +2 -2
  81. abstra_statics/dist/assets/{PhArrowSquareOut.vue.0d20b8e9.js → PhArrowSquareOut.vue.06e1e087.js} +2 -2
  82. abstra_statics/dist/assets/{PhBookBookmark.vue.8611e54f.js → PhBookBookmark.vue.b9569168.js} +2 -2
  83. abstra_statics/dist/assets/{PhChats.vue.3dd1c2d7.js → PhChats.vue.bb8ced1d.js} +2 -2
  84. abstra_statics/dist/assets/{PhClockCounterClockwise.vue.853bd5ec.js → PhClockCounterClockwise.vue.4252e6be.js} +2 -2
  85. abstra_statics/dist/assets/{PhCopy.vue.b1c2f21f.js → PhCopy.vue.59c3a353.js} +2 -2
  86. abstra_statics/dist/assets/{PhCopySimple.vue.ffc7ec94.js → PhCopySimple.vue.1c5a0760.js} +2 -2
  87. abstra_statics/dist/assets/{PhCube.vue.b5e79047.js → PhCube.vue.7728ef5a.js} +2 -2
  88. abstra_statics/dist/assets/{PhDotsThreeVertical.vue.89b627bd.js → PhDotsThreeVertical.vue.275748da.js} +2 -2
  89. abstra_statics/dist/assets/{PhDownloadSimple.vue.2c2eeb2c.js → PhDownloadSimple.vue.ed1fb44c.js} +2 -2
  90. abstra_statics/dist/assets/{PhFolderPlus.vue.e199ba17.js → PhFolderPlus.vue.aa7bc872.js} +2 -2
  91. abstra_statics/dist/assets/{PhGear.vue.fbac4098.js → PhGear.vue.ab14d56a.js} +2 -2
  92. abstra_statics/dist/assets/{PhKey.vue.1850e466.js → PhKey.vue.c0768b68.js} +2 -2
  93. abstra_statics/dist/assets/{PhPencil.vue.c3c70f7d.js → PhPencil.vue.aaf907d1.js} +2 -2
  94. abstra_statics/dist/assets/{PhPencilSimple.vue.ee9d03fc.js → PhPencilSimple.vue.2580bcf9.js} +2 -2
  95. abstra_statics/dist/assets/{PhPencilSimpleLine.vue.6eedd1e6.js → PhPencilSimpleLine.vue.2c2058ce.js} +2 -2
  96. abstra_statics/dist/assets/{PhRocket.vue.56a9b767.js → PhRocket.vue.67935a85.js} +2 -2
  97. abstra_statics/dist/assets/{PhSignOut.vue.ee548b4c.js → PhSignOut.vue.87084f39.js} +2 -2
  98. abstra_statics/dist/assets/{PhSparkle.vue.dc906cc7.js → PhSparkle.vue.707b3721.js} +2 -2
  99. abstra_statics/dist/assets/{PhUserList.vue.6d0e7bc8.js → PhUserList.vue.907c1a2b.js} +2 -2
  100. abstra_statics/dist/assets/{PhUsersThree.vue.528ec19f.js → PhUsersThree.vue.2c3539c7.js} +2 -2
  101. abstra_statics/dist/assets/{PhWebhooksLogo.vue.49b66d69.js → PhWebhooksLogo.vue.1134426a.js} +2 -2
  102. abstra_statics/dist/assets/{PlayerConfigProvider.ead35c7b.js → PlayerConfigProvider.bee6a260.js} +2 -2
  103. abstra_statics/dist/assets/{PlayerNavbar.523eb145.js → PlayerNavbar.b15bb1f3.js} +2 -2
  104. abstra_statics/dist/assets/{Project.d667be48.js → Project.c5ac4ebe.js} +2 -2
  105. abstra_statics/dist/assets/{ProjectLogin.ed25cb39.js → ProjectLogin.2905a0d6.js} +2 -2
  106. abstra_statics/dist/assets/{ProjectSettings.78ebb6ae.js → ProjectSettings.e40cc19c.js} +2 -2
  107. abstra_statics/dist/assets/{ProjectsView.0a9b802f.js → ProjectsView.3df99f87.js} +2 -2
  108. abstra_statics/dist/assets/{SaveButton.7c95c711.js → SaveButton.5a81e6f5.js} +2 -2
  109. abstra_statics/dist/assets/{Sidebar.88ff0297.js → Sidebar.f23acdbf.js} +2 -2
  110. abstra_statics/dist/assets/{Sql.cbcb65af.js → Sql.ea1ee70d.js} +5 -5
  111. abstra_statics/dist/assets/Steps.ad9ac163.js +2 -0
  112. abstra_statics/dist/assets/{TableEditor.8166dbd3.js → TableEditor.3150beb1.js} +2 -2
  113. abstra_statics/dist/assets/Tables.ad8c6f7a.js +2 -0
  114. abstra_statics/dist/assets/{TablesDiagram.67a07e98.js → TablesDiagram.5d3625d1.js} +2 -2
  115. abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.58bcff9a.js +2 -0
  116. abstra_statics/dist/assets/{Tasks.d7e74b8f.js → Tasks.d5e7bdf2.js} +2 -2
  117. abstra_statics/dist/assets/{UploadOutlined.6515c426.js → UploadOutlined.5f3b92b7.js} +2 -2
  118. abstra_statics/dist/assets/{View.2cf966b9.js → View.63e7c09c.js} +2 -2
  119. abstra_statics/dist/assets/{View.vue_vue_type_script_setup_true_lang.17304c45.js → View.vue_vue_type_script_setup_true_lang.a36086e8.js} +2 -2
  120. abstra_statics/dist/assets/{Watermark.4b6da85c.js → Watermark.a7a7af90.js} +2 -2
  121. abstra_statics/dist/assets/{WebEditor.969d0862.js → WebEditor.37194274.js} +2 -2
  122. abstra_statics/dist/assets/{WidgetPreview.6cc6a025.js → WidgetPreview.5d662c77.js} +2 -2
  123. abstra_statics/dist/assets/ant-design.9bb31d85.js +2 -0
  124. abstra_statics/dist/assets/apiKey.4f2a122c.js +2 -0
  125. abstra_statics/dist/assets/asyncComputed.06839806.js +2 -0
  126. abstra_statics/dist/assets/{build.c819a477.js → build.995e6997.js} +2 -2
  127. abstra_statics/dist/assets/colorHelpers.eb706cff.js +2 -0
  128. abstra_statics/dist/assets/{console.072f10aa.js → console.a07258d6.js} +2 -2
  129. abstra_statics/dist/assets/constants.d30e64e7.js +2 -0
  130. abstra_statics/dist/assets/{cssMode.b24669f0.js → cssMode.5c9f19b3.js} +2 -2
  131. abstra_statics/dist/assets/datetime.49213c3c.js +2 -0
  132. abstra_statics/dist/assets/dayjs.9d20dd08.js +2 -0
  133. abstra_statics/dist/assets/editor.a151d549.js +2 -0
  134. abstra_statics/dist/assets/editor.main.f809eb34.js +2 -0
  135. abstra_statics/dist/assets/fetch.ce34efab.js +2 -0
  136. abstra_statics/dist/assets/{files.ff551888.js → files.5dcc78be.js} +2 -2
  137. abstra_statics/dist/assets/{folder.92a70293.js → folder.777c3209.js} +2 -2
  138. abstra_statics/dist/assets/{freemarker2.c3563cfb.js → freemarker2.385a198f.js} +2 -2
  139. abstra_statics/dist/assets/{handlebars.bedd8360.js → handlebars.806a8b22.js} +3 -3
  140. abstra_statics/dist/assets/{html.3cfc37f8.js → html.8df330b0.js} +2 -2
  141. abstra_statics/dist/assets/{htmlMode.f9c29f9d.js → htmlMode.08713341.js} +2 -2
  142. abstra_statics/dist/assets/{index.5429e60c.js → index.11470ce1.js} +5 -5
  143. abstra_statics/dist/assets/index.15909af6.js +2 -0
  144. abstra_statics/dist/assets/{index.dfc9f500.js → index.227be58a.js} +2 -2
  145. abstra_statics/dist/assets/{index.9028af84.js → index.440bd76c.js} +2 -2
  146. abstra_statics/dist/assets/{index.138ad220.js → index.49f6d313.js} +2 -2
  147. abstra_statics/dist/assets/index.6f004291.js +2 -0
  148. abstra_statics/dist/assets/{index.a9e83a57.js → index.ad15e486.js} +2 -2
  149. abstra_statics/dist/assets/{index.26a34a52.js → index.cf5ef754.js} +2 -2
  150. abstra_statics/dist/assets/{index.b69b3146.js → index.f219d050.js} +2 -2
  151. abstra_statics/dist/assets/{index.e6b397aa.js → index.fdbbf978.js} +2 -2
  152. abstra_statics/dist/assets/{javascript.a4a24e63.js → javascript.7e683eb3.js} +2 -2
  153. abstra_statics/dist/assets/{jsonMode.4df0cb48.js → jsonMode.c71ca5e7.js} +2 -2
  154. abstra_statics/dist/assets/{jwt-decode.esm.b204fb7d.js → jwt-decode.esm.bcf2db26.js} +7 -7
  155. abstra_statics/dist/assets/linters.1f3c2bdc.js +2 -0
  156. abstra_statics/dist/assets/{liquid.6539d262.js → liquid.c89ab4d8.js} +3 -3
  157. abstra_statics/dist/assets/{member.c5283d66.js → member.39a2b810.js} +2 -2
  158. abstra_statics/dist/assets/{metadata.e6e36ac2.js → metadata.77edb2ef.js} +2 -2
  159. abstra_statics/dist/assets/{omniChatStore.0470e5b1.js → omniChatStore.13045a23.js} +2 -2
  160. abstra_statics/dist/assets/{organization.04cff158.js → organization.017d0932.js} +2 -2
  161. abstra_statics/dist/assets/player.490b5a36.js +2 -0
  162. abstra_statics/dist/assets/{plotly.min.e185d882.js → plotly.min.ebdb6341.js} +2 -2
  163. abstra_statics/dist/assets/polling.b53cbe3d.js +2 -0
  164. abstra_statics/dist/assets/{project.c198d794.js → project.6106c0ae.js} +2 -2
  165. abstra_statics/dist/assets/{python.5c45007e.js → python.fe5f6c61.js} +3 -3
  166. abstra_statics/dist/assets/{razor.dbd79c37.js → razor.63a60978.js} +2 -2
  167. abstra_statics/dist/assets/{record.316e025a.js → record.6b6d4da9.js} +2 -2
  168. abstra_statics/dist/assets/{redirect.d1904049.js → redirect.b8ba406b.js} +2 -2
  169. abstra_statics/dist/assets/{repository.ae6e826f.js → repository.54d4a909.js} +2 -2
  170. abstra_statics/dist/assets/{repository.babdc912.js → repository.f2f5d880.js} +2 -2
  171. abstra_statics/dist/assets/{router.29d47df0.js → router.e5052323.js} +3 -3
  172. abstra_statics/dist/assets/{string.4a80abd5.js → string.2f1abff3.js} +2 -2
  173. abstra_statics/dist/assets/{tables.e80a0328.js → tables.ecf1f359.js} +2 -2
  174. abstra_statics/dist/assets/{tasksController.307be8dc.js → tasksController.64f43cce.js} +3 -3
  175. abstra_statics/dist/assets/{toggleHighContrast.4184537f.js → toggleHighContrast.a33c9afc.js} +7 -7
  176. abstra_statics/dist/assets/{tsMode.12c94eed.js → tsMode.bbb62deb.js} +2 -2
  177. abstra_statics/dist/assets/{typescript.e087b68e.js → typescript.f3be1a16.js} +3 -3
  178. abstra_statics/dist/assets/url.79c72bde.js +2 -0
  179. abstra_statics/dist/assets/userStore.5dca7ff0.js +2 -0
  180. abstra_statics/dist/assets/uuid.3f5b8acd.js +2 -0
  181. abstra_statics/dist/assets/{vue-flow-background.402b0a32.js → vue-flow-background.939eca5f.js} +2 -2
  182. abstra_statics/dist/assets/{vue-quill.esm-bundler.10d49c97.js → vue-quill.esm-bundler.c86df351.js} +2 -2
  183. abstra_statics/dist/assets/workspaceStore.7283625b.js +2 -0
  184. abstra_statics/dist/assets/{xml.c4ef7130.js → xml.96392483.js} +3 -3
  185. abstra_statics/dist/assets/{yaml.5eb27541.js → yaml.96b91a1d.js} +3 -3
  186. abstra_statics/dist/console.html +15 -15
  187. abstra_statics/dist/editor.html +11 -11
  188. abstra_statics/dist/player.html +9 -9
  189. abstra_internals/utils/threading.py +0 -24
  190. abstra_internals/utils/websockets.py +0 -79
  191. abstra_statics/dist/assets/AbstraButton.vue_vue_type_script_setup_true_lang.c714d609.js +0 -2
  192. abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.37e7ad5b.js +0 -2
  193. abstra_statics/dist/assets/ApiKeys.94113392.js +0 -2
  194. abstra_statics/dist/assets/App.6c102d26.js +0 -2
  195. abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.26121baf.js +0 -2
  196. abstra_statics/dist/assets/CloseCircleOutlined.7ac8e1e8.js +0 -2
  197. abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.b85f86d8.js +0 -2
  198. abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.0b26fb89.js +0 -2
  199. abstra_statics/dist/assets/EnvVars.ccc24231.js +0 -2
  200. abstra_statics/dist/assets/Error.1bf571a6.js +0 -2
  201. abstra_statics/dist/assets/ExclamationCircleOutlined.f71fa58b.js +0 -2
  202. abstra_statics/dist/assets/Home.23f0cff8.js +0 -2
  203. abstra_statics/dist/assets/Home.5ce6aade.js +0 -2
  204. abstra_statics/dist/assets/LoadingContainer.3ea9ec8d.js +0 -2
  205. abstra_statics/dist/assets/LoadingOutlined.11688830.js +0 -2
  206. abstra_statics/dist/assets/Login.2a4c7e48.js +0 -2
  207. abstra_statics/dist/assets/Logo.cc2d4a95.js +0 -2
  208. abstra_statics/dist/assets/Logs.f57284a4.js +0 -2
  209. abstra_statics/dist/assets/LogsController.353f0002.js +0 -2
  210. abstra_statics/dist/assets/Main.635e443c.js +0 -2
  211. abstra_statics/dist/assets/Navbar.dacca218.js +0 -2
  212. abstra_statics/dist/assets/OidcLoginCallback.0dff6320.js +0 -2
  213. abstra_statics/dist/assets/OidcLogoutCallback.1f077fbb.js +0 -2
  214. abstra_statics/dist/assets/Organizations.3dbd99e1.js +0 -2
  215. abstra_statics/dist/assets/Steps.4c7bc2b0.js +0 -2
  216. abstra_statics/dist/assets/Tables.0da330b6.js +0 -2
  217. abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.2d88acf3.js +0 -2
  218. abstra_statics/dist/assets/ant-design.771baa34.js +0 -2
  219. abstra_statics/dist/assets/apiKey.8e591cfc.js +0 -2
  220. abstra_statics/dist/assets/asyncComputed.0f036eae.js +0 -2
  221. abstra_statics/dist/assets/colorHelpers.c4e8851c.js +0 -2
  222. abstra_statics/dist/assets/constants.6f822a9d.js +0 -2
  223. abstra_statics/dist/assets/datetime.7d5f4744.js +0 -2
  224. abstra_statics/dist/assets/dayjs.19defe89.js +0 -2
  225. abstra_statics/dist/assets/editor.6fa2da52.js +0 -2
  226. abstra_statics/dist/assets/editor.main.91c25311.js +0 -2
  227. abstra_statics/dist/assets/fetch.41281c28.js +0 -2
  228. abstra_statics/dist/assets/index.dccc1696.js +0 -2
  229. abstra_statics/dist/assets/index.e7bc0e19.js +0 -2
  230. abstra_statics/dist/assets/linters.5b679742.js +0 -2
  231. abstra_statics/dist/assets/player.c715ca22.js +0 -2
  232. abstra_statics/dist/assets/polling.5f85e241.js +0 -2
  233. abstra_statics/dist/assets/url.371275cf.js +0 -2
  234. abstra_statics/dist/assets/userStore.26658284.js +0 -2
  235. abstra_statics/dist/assets/uuid.ffd92735.js +0 -2
  236. abstra_statics/dist/assets/workspaceStore.9caf566c.js +0 -2
  237. {abstra-3.23.3.dist-info → abstra-3.23.5.dist-info}/WHEEL +0 -0
  238. {abstra-3.23.3.dist-info → abstra-3.23.5.dist-info}/entry_points.txt +0 -0
  239. {abstra-3.23.3.dist-info → abstra-3.23.5.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,10 @@
1
1
  from concurrent.futures import ThreadPoolExecutor
2
- from multiprocessing.connection import Client
3
2
  from uuid import uuid4
4
3
 
5
4
  from abstra_internals.controllers.execution.worker_process import process_main
6
5
  from abstra_internals.controllers.main import MainController
7
6
  from abstra_internals.environment import (
8
7
  EXECUTION_QUEUE_CONCURRENCY,
9
- HOST,
10
8
  PROCESS_TIMEOUT_SECONDS,
11
9
  )
12
10
  from abstra_internals.logger import AbstraLogger
@@ -35,36 +33,38 @@ class ConsumerController:
35
33
  f"[ConsumerController] Starting loop with {self.concurrency} threads"
36
34
  )
37
35
 
38
- with ThreadPoolExecutor(
36
+ thread_pool = ThreadPoolExecutor(
39
37
  max_workers=self.concurrency,
40
38
  thread_name_prefix="ExecutionConsumer",
41
- ) as executor:
39
+ )
40
+
41
+ try:
42
42
  for msg in self.consumer.iter():
43
43
  AbstraLogger.debug(
44
44
  f"[ConsumerController] Submitting message [{msg.delivery_tag}] for processing"
45
45
  )
46
- executor.submit(
46
+ thread_pool.submit(
47
47
  self.run_subprocess,
48
48
  msg=msg,
49
49
  )
50
+ finally:
51
+ AbstraLogger.warning(
52
+ "[ConsumerController] Consumer main loop exited, waiting for threads to finish"
53
+ )
54
+ thread_pool.shutdown(wait=True)
55
+ AbstraLogger.warning("[ConsumerController] All threads finished")
50
56
 
51
- AbstraLogger.warning(
52
- "[ConsumerController] Consumer main loop exited, waiting for threads to finish"
53
- )
54
- AbstraLogger.warning("[ConsumerController] All threads finished")
55
-
56
- # If the loop exits and there are running executions, gracefull shutdown has failed
57
- self.main_controller.fail_app_executions(
58
- app_id=self.app_id,
59
- reason="Failed to set status",
60
- )
57
+ # If the loop exits and there are running executions, gracefull shutdown has failed
58
+ self.main_controller.fail_app_executions(
59
+ app_id=self.app_id,
60
+ reason="Failed to set status",
61
+ )
61
62
 
62
- def run_subprocess(self, msg: QueueMessage) -> None:
63
- connection = None
63
+ def run_subprocess(self, msg: QueueMessage):
64
64
  worker_id = str(uuid4())
65
- try:
66
- head_id = worker_id.split("-")[0]
65
+ head_id = worker_id.split("-")[0]
67
66
 
67
+ try:
68
68
  mp_context = self.main_controller.repositories.mp_context.get_context()
69
69
  stage = self.main_controller.get_stage(msg.preexecution.stage_id)
70
70
 
@@ -74,11 +74,13 @@ class ConsumerController:
74
74
  )
75
75
  self.consumer.threadsafe_ack(msg)
76
76
  return
77
- connection = Client(
78
- (HOST, msg.preexecution.connection_port),
79
- family="AF_INET",
80
- authkey=msg.preexecution.connection_auth_key.encode(),
81
- )
77
+
78
+ local_queue = None
79
+ if isinstance(
80
+ self.main_controller.repositories.producer, LocalProducerRepository
81
+ ):
82
+ local_queue = self.main_controller.repositories.producer.queue
83
+
82
84
  p = mp_context.Process(
83
85
  target=process_main,
84
86
  name=f"Worker-{head_id}",
@@ -89,13 +91,7 @@ class ConsumerController:
89
91
  root_path=Settings.root_path,
90
92
  server_port=Settings.server_port,
91
93
  request=msg.preexecution.context,
92
- connection=connection,
93
- local_queue=self.main_controller.repositories.producer.queue
94
- if isinstance(
95
- self.main_controller.repositories.producer,
96
- LocalProducerRepository,
97
- )
98
- else None,
94
+ local_queue=local_queue,
99
95
  ),
100
96
  )
101
97
 
@@ -105,7 +101,6 @@ class ConsumerController:
105
101
  # If timeout is reached, the process is still alive
106
102
  if p.is_alive():
107
103
  p.terminate()
108
- p.join() # Wait for termination to complete
109
104
  raise NonCleanExit(
110
105
  f"Run took too long to complete ({PROCESS_TIMEOUT_SECONDS} secs) and was terminated. Please make sure all your requests calls have a timeout set."
111
106
  )
@@ -133,13 +128,6 @@ class ConsumerController:
133
128
  )
134
129
 
135
130
  self.consumer.threadsafe_nack(msg)
136
- finally:
137
- # Always close the connection when done
138
- if connection is not None:
139
- try:
140
- connection.close()
141
- except Exception:
142
- pass # Ignore errors when closing
143
131
 
144
132
  AbstraLogger.warning(
145
133
  f"[ConsumerController] Message [{msg.delivery_tag}] has been negatively acknowledged"
@@ -4,7 +4,10 @@ import traceback
4
4
  from pathlib import Path
5
5
  from typing import Literal, Optional, Tuple
6
6
 
7
- from abstra_internals.controllers.execution.execution_client import ExecutionClient
7
+ from abstra_internals.controllers.execution.execution_client import (
8
+ ExecutionClient,
9
+ HeadlessClient,
10
+ )
8
11
  from abstra_internals.controllers.execution.execution_client_form import ClientAbandoned
9
12
  from abstra_internals.controllers.sdk.sdk_context import SDKContext
10
13
  from abstra_internals.entities.execution import Execution
@@ -27,12 +30,12 @@ class ExecutionController:
27
30
  *,
28
31
  repositories: Repositories,
29
32
  stage: StageWithFile,
30
- client: ExecutionClient,
31
- context: ClientContext,
33
+ client: Optional[ExecutionClient] = None,
34
+ context: Optional[ClientContext] = None,
32
35
  ) -> None:
33
36
  self.repositories = repositories
34
37
  self.stage = stage
35
- self.client = client
38
+ self.client = client or HeadlessClient()
36
39
  self.context = context
37
40
 
38
41
  def run(self):
@@ -1,7 +1,4 @@
1
1
  import abc
2
- from multiprocessing.connection import Connection
3
-
4
- from abstra_internals.entities.execution_context import ClientContext
5
2
 
6
3
 
7
4
  class ExecutionClient(abc.ABC):
@@ -19,17 +16,11 @@ class ExecutionClient(abc.ABC):
19
16
 
20
17
 
21
18
  class HeadlessClient(ExecutionClient):
22
- context: ClientContext
23
-
24
- def __init__(self, context: ClientContext, conn: Connection):
25
- self.context = context
26
- self._conn = conn
27
-
28
19
  def handle_failure(self, e: Exception) -> None:
29
- self._conn.close()
20
+ pass
30
21
 
31
22
  def handle_success(self) -> None:
32
- self._conn.close()
23
+ pass
33
24
 
34
25
  def handle_start(self, execution_id: str):
35
26
  pass
@@ -1,6 +1,7 @@
1
- from multiprocessing.connection import Connection
2
1
  from typing import Dict, Optional
3
2
 
3
+ import flask_sock
4
+
4
5
  from abstra_internals.controllers.execution.execution_client import ExecutionClient
5
6
  from abstra_internals.entities.execution_context import FormContext
6
7
  from abstra_internals.entities.forms.form_entity import ButtonAction, RenderedForm
@@ -17,38 +18,38 @@ class FormClient(ExecutionClient):
17
18
 
18
19
  def __init__(
19
20
  self,
20
- conn: Connection,
21
+ ws: flask_sock.Server,
21
22
  context: FormContext,
22
23
  production_mode: bool,
23
24
  ) -> None:
24
25
  self.context = context
25
26
  self._production_mode = production_mode
26
- self._conn = conn
27
+ self._ws = ws
27
28
 
28
29
  # WEBSOCKET
29
30
 
30
31
  def _receive_message(self):
31
32
  try:
32
- str_data = self._conn.recv()
33
+ str_data = self._ws.receive()
33
34
  deserialized = deserialize(str_data)
34
35
  if not isinstance(deserialized, dict):
35
36
  raise ValueError("Invalid message format")
36
37
  return deserialized
37
- except (BrokenPipeError, EOFError):
38
+ except flask_sock.ConnectionClosed:
38
39
  raise ClientAbandoned()
39
40
 
40
41
  def _send(self, msg: forms_contract.Message) -> None:
41
42
  str_data = serialize(msg.to_json())
42
43
  try:
43
- self._conn.send(str_data)
44
- except (BrokenPipeError, EOFError):
44
+ self._ws.send(str_data)
45
+ except flask_sock.ConnectionClosed:
45
46
  pass
46
47
 
47
48
  def _user_code_send(self, msg: forms_contract.Message) -> None:
48
49
  str_data = serialize(msg.to_json())
49
50
  try:
50
- self._conn.send(str_data)
51
- except (BrokenPipeError, EOFError):
51
+ self._ws.send(str_data)
52
+ except flask_sock.ConnectionClosed:
52
53
  raise ClientAbandoned()
53
54
 
54
55
  def _wait_for_message(
@@ -161,14 +162,12 @@ class FormClient(ExecutionClient):
161
162
  self._send(
162
163
  forms_contract.ExecutionEndedMessage(close_dto, self._production_mode)
163
164
  )
164
- self._conn.close()
165
165
 
166
166
  def handle_success(self):
167
167
  close_dto = forms_contract.CloseDTO(exit_status="SUCCESS")
168
168
  self._send(
169
169
  forms_contract.ExecutionEndedMessage(close_dto, self._production_mode)
170
170
  )
171
- self._conn.close()
172
171
 
173
172
  ## Testing
174
173
 
@@ -1,4 +1,3 @@
1
- from multiprocessing.connection import Connection
2
1
  from typing import Dict
3
2
 
4
3
  from abstra_internals.controllers.execution.execution_client import ExecutionClient
@@ -7,23 +6,18 @@ from abstra_internals.entities.execution_context import HookContext, Request, Re
7
6
 
8
7
  class HookClient(ExecutionClient):
9
8
  context: HookContext
10
- conn: Connection
11
- response: Response
12
9
 
13
- def __init__(self, context: HookContext, conn: Connection) -> None:
10
+ def __init__(self, context: HookContext) -> None:
14
11
  self.context = context
15
- self.conn = conn
16
- self.response = Response(headers={}, status=200, body="")
17
12
 
18
13
  def handle_failure(self, e: Exception) -> None:
19
14
  self.set_response(500, "An exception occurred during execution.", {})
20
- self.conn.send(self.response)
21
15
 
22
16
  def handle_success(self) -> None:
23
- self.conn.send(self.response)
17
+ pass
24
18
 
25
19
  def set_response(self, status: int, body: str, headers: Dict[str, str]) -> None:
26
- self.response = Response(
20
+ self.context.response = Response(
27
21
  status=status,
28
22
  body=body,
29
23
  headers=headers,
@@ -13,10 +13,9 @@ from abstra_internals.env_masker import GLOBAL_MASKER
13
13
  from abstra_internals.environment import IS_PRODUCTION
14
14
  from abstra_internals.interface.sdk.user_exceptions import ExecutionNotFound
15
15
  from abstra_internals.logger import AbstraLogger
16
- from abstra_internals.utils import serialize
17
16
 
18
17
 
19
- class StdioController:
18
+ class BroadcastController:
20
19
  listeners: List[flask_sock.Server] = []
21
20
  _lock = threading.Lock()
22
21
 
@@ -32,22 +31,12 @@ class StdioController:
32
31
  cls.listeners.remove(listener)
33
32
 
34
33
  @classmethod
35
- def broadcast(
36
- cls,
37
- *,
38
- type: Literal["stdout", "stderr"],
39
- execution_id: str,
40
- stage_id: str,
41
- log: str,
42
- ):
43
- msg = serialize(
44
- dict(type=type, log=log, execution_id=execution_id, stage_id=stage_id)
45
- )
34
+ def broadcast(cls, *, msg: str):
46
35
  for listener in cls.listeners:
47
36
  try:
48
37
  listener.send(msg)
49
38
  except Exception:
50
- StdioController.unregister(listener)
39
+ BroadcastController.unregister(listener)
51
40
 
52
41
  def __init__(
53
42
  self,
@@ -1,4 +1,3 @@
1
- from multiprocessing import Pipe
2
1
  from pathlib import Path
3
2
 
4
3
  from abstra_internals.controllers.execution.execution import ExecutionController
@@ -25,7 +24,8 @@ class ExecutionControllerTest(BaseTest):
25
24
  headers={"auth": "some_secret_token"},
26
25
  query_params={"c": "3"},
27
26
  method="GET",
28
- )
27
+ ),
28
+ response=Response(headers={}, status=200, body=""),
29
29
  )
30
30
 
31
31
  self.project = self.repositories.project.load(include_disabled_stages=False)
@@ -39,8 +39,7 @@ class ExecutionControllerTest(BaseTest):
39
39
  self.project.add_stage(self.stage)
40
40
  self.repositories.project.save(self.project)
41
41
 
42
- self.parent_conn, child_conn = Pipe()
43
- self.hook_client = HookClient(self.context, conn=child_conn)
42
+ self.hook_client = HookClient(self.context)
44
43
 
45
44
  def test_run_initial_returns_dto(self):
46
45
  ExecutionController(
@@ -50,10 +49,7 @@ class ExecutionControllerTest(BaseTest):
50
49
  client=self.hook_client,
51
50
  ).run()
52
51
 
53
- response = self.parent_conn.recv()
54
- assert isinstance(response, Response)
55
-
56
- if not response:
52
+ if not self.context.response:
57
53
  self.fail("Response was not set")
58
54
 
59
- self.assertEqual(response.status, 200)
55
+ self.assertEqual(self.context.response.status, 200)
@@ -1,20 +1,16 @@
1
1
  import threading
2
2
  import traceback
3
3
  from multiprocessing import Queue
4
- from multiprocessing.connection import Connection
5
4
  from typing import Optional
6
5
 
7
6
  from abstra_internals.controllers.execution.execution import ExecutionController
8
- from abstra_internals.controllers.execution.execution_client import (
9
- ExecutionClient,
10
- HeadlessClient,
11
- )
12
- from abstra_internals.controllers.execution.execution_client_form import FormClient
13
- from abstra_internals.controllers.execution.execution_client_hook import HookClient
14
7
  from abstra_internals.controllers.main import MainController
15
8
  from abstra_internals.entities.execution import ClientContext
16
- from abstra_internals.entities.execution_context import FormContext, HookContext
17
- from abstra_internals.environment import IS_PRODUCTION, set_SERVER_UUID, set_WORKER_UUID
9
+ from abstra_internals.environment import (
10
+ IS_PRODUCTION,
11
+ set_SERVER_UUID,
12
+ set_WORKER_UUID,
13
+ )
18
14
  from abstra_internals.logger import AbstraLogger
19
15
  from abstra_internals.repositories.factory import (
20
16
  build_editor_repositories,
@@ -25,24 +21,6 @@ from abstra_internals.settings import Settings
25
21
  from abstra_internals.stdio_patcher import StdioPatcher
26
22
 
27
23
 
28
- def make_client_from_context(
29
- ctx: ClientContext, connection: Connection
30
- ) -> ExecutionClient:
31
- if isinstance(ctx, FormContext):
32
- return FormClient(
33
- context=ctx,
34
- conn=connection,
35
- production_mode=IS_PRODUCTION,
36
- )
37
- elif isinstance(ctx, HookContext):
38
- return HookClient(
39
- context=ctx,
40
- conn=connection,
41
- )
42
- else:
43
- return HeadlessClient(context=ctx, conn=connection)
44
-
45
-
46
24
  def process_main(
47
25
  *,
48
26
  root_path: str,
@@ -50,8 +28,7 @@ def process_main(
50
28
  worker_id: str,
51
29
  app_id: str,
52
30
  stage: StageWithFile,
53
- connection: Connection,
54
- request: ClientContext,
31
+ request: Optional[ClientContext] = None,
55
32
  local_queue: Optional[Queue] = None,
56
33
  ):
57
34
  thread = threading.current_thread()
@@ -66,7 +43,6 @@ def process_main(
66
43
  controller = MainController(repositories)
67
44
  else:
68
45
  AbstraLogger.init("local")
69
- assert local_queue is not None, "Local queue is not initialized"
70
46
  repositories = build_editor_repositories(local_queue)
71
47
  controller = MainController(repositories)
72
48
 
@@ -75,15 +51,14 @@ def process_main(
75
51
 
76
52
  StdioPatcher.apply(controller)
77
53
 
78
- execution_data = ExecutionController(
54
+ ExecutionController(
79
55
  repositories=controller.repositories,
80
56
  stage=stage,
81
- client=make_client_from_context(request, connection),
57
+ client=None,
82
58
  context=request,
83
59
  ).run()
84
60
 
85
61
  AbstraLogger.debug("[ConsumerController Subprocess] Finished")
86
- return execution_data
87
62
  except Exception as e:
88
63
  AbstraLogger.error(f"[ConsumerController Subprocess] Error: {e}")
89
64
  AbstraLogger.capture_exception(e)
@@ -1,7 +1,6 @@
1
1
  import datetime
2
2
  import pkgutil
3
3
  import webbrowser
4
- from multiprocessing import Pipe
5
4
  from pathlib import Path
6
5
  from shutil import move
7
6
  from tempfile import mkdtemp, mktemp
@@ -12,7 +11,6 @@ import flask
12
11
  from abstra_internals.cloud_api import get_api_key_info, get_project_info
13
12
  from abstra_internals.consts.filepaths import TEST_DATA_FILEPATH
14
13
  from abstra_internals.controllers.execution.execution import ExecutionController
15
- from abstra_internals.controllers.execution.execution_client import HeadlessClient
16
14
  from abstra_internals.controllers.execution.execution_client_form import FormClient
17
15
  from abstra_internals.controllers.execution.execution_client_hook import HookClient
18
16
  from abstra_internals.credentials import (
@@ -69,7 +67,6 @@ from abstra_internals.utils.ai import AiWs
69
67
  from abstra_internals.utils.diff import compute_updated_code_from_replacements
70
68
  from abstra_internals.utils.file import module2path, path2module
71
69
  from abstra_internals.utils.validate import validate_json
72
- from abstra_internals.utils.websockets import bind_ws_with_connection
73
70
 
74
71
 
75
72
  class UnknownNodeTypeError(Exception):
@@ -1684,13 +1681,12 @@ class MainController:
1684
1681
  if not job:
1685
1682
  raise Exception(f"Job with id {id} not found")
1686
1683
 
1687
- _, child_conn = Pipe()
1688
- ctx = JobContext()
1684
+ print(f"Running job {job.id} ({job.title})")
1685
+
1689
1686
  return ExecutionController(
1690
1687
  repositories=self.repositories,
1691
1688
  stage=job,
1692
- context=ctx,
1693
- client=HeadlessClient(context=ctx, conn=child_conn),
1689
+ context=JobContext(),
1694
1690
  ).run()
1695
1691
 
1696
1692
  def debug_run_hook(self, id: str, request: Request):
@@ -1717,9 +1713,14 @@ class MainController:
1717
1713
 
1718
1714
  context = HookContext(
1719
1715
  request=request,
1716
+ response=Response(
1717
+ body="",
1718
+ headers={},
1719
+ status=200,
1720
+ ),
1720
1721
  )
1721
- parent_conn, child_conn = Pipe()
1722
- client = HookClient(context=context, conn=child_conn)
1722
+
1723
+ client = HookClient(context=context)
1723
1724
 
1724
1725
  run_data = ExecutionController(
1725
1726
  repositories=self.repositories,
@@ -1728,13 +1729,13 @@ class MainController:
1728
1729
  context=context,
1729
1730
  ).run()
1730
1731
 
1731
- response = parent_conn.recv()
1732
- assert isinstance(response, Response)
1732
+ if context.response is None or client.context.response is None:
1733
+ flask.abort(500)
1733
1734
 
1734
1735
  return {
1735
- "body": response.body,
1736
- "status": response.status,
1737
- "headers": response.headers,
1736
+ "body": client.context.response.body,
1737
+ "status": context.response.status,
1738
+ "headers": context.response.headers,
1738
1739
  **run_data,
1739
1740
  }
1740
1741
 
@@ -1764,14 +1765,10 @@ class MainController:
1764
1765
  if not task_id:
1765
1766
  raise Exception("Task ID is required for script execution")
1766
1767
 
1767
- _, child_conn = Pipe()
1768
- ctx = ScriptContext(task_id=task_id)
1769
-
1770
1768
  return ExecutionController(
1771
1769
  repositories=self.repositories,
1772
1770
  stage=script,
1773
- context=ctx,
1774
- client=HeadlessClient(context=ctx, conn=child_conn),
1771
+ context=ScriptContext(task_id=task_id),
1775
1772
  ).run()
1776
1773
 
1777
1774
  def debug_run_form_with_ai(self, id, prompt: str, url_params: Dict[str, str] = {}):
@@ -1796,10 +1793,8 @@ class MainController:
1796
1793
  if not form:
1797
1794
  raise Exception(f"Form with id {id} not found")
1798
1795
 
1799
- parent_conn, child_conn = Pipe()
1800
- bind_ws_with_connection(ws, parent_conn, block=False)
1801
1796
  client = FormClient(
1802
- conn=child_conn,
1797
+ ws=ws, # type: ignore
1803
1798
  context=context,
1804
1799
  production_mode=False,
1805
1800
  )
@@ -1823,13 +1818,10 @@ class MainController:
1823
1818
  stage = self.create_job(title, str(tempfile), (0, 0))
1824
1819
  tempfile.write_text(code)
1825
1820
 
1826
- _, child_conn = Pipe()
1827
- ctx = JobContext()
1828
1821
  execution_result = ExecutionController(
1829
1822
  repositories=self.repositories,
1830
1823
  stage=stage,
1831
- context=ctx,
1832
- client=HeadlessClient(context=ctx, conn=child_conn),
1824
+ context=JobContext(),
1833
1825
  ).run()
1834
1826
 
1835
1827
  self.delete_job(stage.id, remove_file=True)
@@ -225,6 +225,39 @@ class WorkflowController:
225
225
  stage.workflow_transitions.append(transition)
226
226
  self.repos.project.save(project)
227
227
 
228
+ def delete_transition(self, transition_id: str):
229
+ """
230
+ Delete a transition from the workflow by its ID.
231
+ This method removes the specified transition from the workflow,
232
+ breaking the connection between two stages.
233
+
234
+ Args:
235
+ transition_id (str): ID of the transition to delete.
236
+
237
+ Raises:
238
+ ValueError: If the transition with the given ID does not exist.
239
+
240
+ Copywritings:
241
+ Delete a transition from the workflow
242
+ Deleting a transition from the workflow...
243
+ """
244
+ project = self.repos.project.load()
245
+ transition_found = False
246
+
247
+ for stage in project.workflow_stages:
248
+ for i, transition in enumerate(stage.workflow_transitions):
249
+ if transition.id == transition_id:
250
+ stage.workflow_transitions.pop(i)
251
+ transition_found = True
252
+ break
253
+ if transition_found:
254
+ break
255
+
256
+ if not transition_found:
257
+ raise ValueError(f"Transition {transition_id} does not exist.")
258
+
259
+ self.repos.project.save(project)
260
+
228
261
  def update_workflow(self, workflow_state_dto: Dict):
229
262
  """
230
263
  Update the entire workflow configuration with new stages and transitions.
@@ -43,6 +43,7 @@ class JobExecutionMock(ExecutionMock):
43
43
 
44
44
  class HookContext(Serializable):
45
45
  request: Request
46
+ response: Response
46
47
  sent_tasks: List[str] = Field(default_factory=list)
47
48
  legacy_thread_data: dict = Field(default_factory=dict)
48
49
  mock_execution: HookExecutionMock = Field(default_factory=HookExecutionMock)
@@ -2,7 +2,7 @@ from datetime import datetime
2
2
  from unittest import TestCase
3
3
 
4
4
  from abstra_internals.entities.execution import Execution
5
- from abstra_internals.entities.execution_context import HookContext, Request
5
+ from abstra_internals.entities.execution_context import HookContext, Request, Response
6
6
  from abstra_internals.repositories.project.project import HookStage
7
7
  from abstra_internals.utils import is_serializable
8
8
  from abstra_internals.utils.datetime import from_utc_iso_string
@@ -24,7 +24,9 @@ class ExecutionTest(TestCase):
24
24
  method="GET",
25
25
  )
26
26
 
27
- context = HookContext(request=request)
27
+ context = HookContext(
28
+ request=request, response=Response(headers={}, status=200, body="")
29
+ )
28
30
 
29
31
  execution: Execution[HookContext] = Execution.create(
30
32
  stage_id=mock_stage.id, context=context
@@ -55,7 +57,8 @@ class ExecutionTest(TestCase):
55
57
  headers={"auth": "secret_token"},
56
58
  query_params={"c": "3"},
57
59
  method="GET",
58
- )
60
+ ),
61
+ response=Response(headers={}, status=200, body=""),
59
62
  )
60
63
 
61
64
  execution: Execution[HookContext] = Execution.create(
@@ -49,7 +49,7 @@ def download_to_path(url: str) -> pathlib.Path:
49
49
  execution = SDKContextStore.get_execution()
50
50
  save_dir = get_uploads_dir() / execution.id
51
51
  save_dir.mkdir(parents=True, exist_ok=True)
52
- save_path = save_dir / url.split("/")[-1]
52
+ save_path = save_dir / pathlib.Path(url).name
53
53
 
54
54
  if url.startswith("http://") or url.startswith("https://"):
55
55
  with save_path.open("wb") as f, requests.get(url, stream=True) as r: