Flowfile 0.5.6__py3-none-any.whl → 0.6.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 (256) hide show
  1. flowfile/api.py +8 -6
  2. flowfile/web/static/assets/{AdminView-c2c7942b.js → AdminView-C4K1DdHI.js} +28 -33
  3. flowfile/web/static/assets/{CloudConnectionView-7a3042c6.js → CloudConnectionView-BZbPvPUL.js} +39 -50
  4. flowfile/web/static/assets/{CloudStorageReader-24c54524.css → CloudStorageReader-BDByiqPI.css} +25 -25
  5. flowfile/web/static/assets/{CloudStorageReader-709c4037.js → CloudStorageReader-DLVukNJ7.js} +30 -35
  6. flowfile/web/static/assets/{CloudStorageWriter-604c51a8.js → CloudStorageWriter-Bfi-C1QW.js} +32 -37
  7. flowfile/web/static/assets/{CloudStorageWriter-60547855.css → CloudStorageWriter-y8jL8yjG.css} +24 -24
  8. flowfile/web/static/assets/{ColumnActionInput-d63d6746.js → ColumnActionInput-BpiCApw9.js} +7 -12
  9. flowfile/web/static/assets/{ColumnSelector-0c8cd1cd.js → ColumnSelector-CEAwedI7.js} +1 -2
  10. flowfile/web/static/assets/ContextMenu-CdojQu0w.js +9 -0
  11. flowfile/web/static/assets/ContextMenu-D12mhsy1.js +9 -0
  12. flowfile/web/static/assets/ContextMenu-EWUR98va.js +9 -0
  13. flowfile/web/static/assets/{ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js → ContextMenu.vue_vue_type_script_setup_true_lang-I4rXXd6G.js} +4 -5
  14. flowfile/web/static/assets/{CrossJoin-38e5b99a.js → CrossJoin-BOFfxkJO.js} +19 -18
  15. flowfile/web/static/assets/{CrossJoin-71b4cc10.css → CrossJoin-Cmbyt9im.css} +18 -18
  16. flowfile/web/static/assets/{CustomNode-76e8f3f5.js → CustomNode-Bhpezobq.js} +12 -17
  17. flowfile/web/static/assets/{DatabaseConnectionSettings-38155669.js → DatabaseConnectionSettings-Dw3bSJKB.js} +10 -11
  18. flowfile/web/static/assets/{DatabaseReader-5bf8c75b.css → DatabaseReader-D6pUNUCs.css} +21 -21
  19. flowfile/web/static/assets/{DatabaseReader-2e549c8f.js → DatabaseReader-m87ghlw0.js} +36 -34
  20. flowfile/web/static/assets/{DatabaseView-dc877c29.js → DatabaseView-CisSAtpe.js} +30 -38
  21. flowfile/web/static/assets/{DatabaseWriter-ffb91864.js → DatabaseWriter-Bbj9JLdL.js} +33 -35
  22. flowfile/web/static/assets/{DatabaseWriter-bdcf2c8b.css → DatabaseWriter-RBqdFLj8.css} +17 -17
  23. flowfile/web/static/assets/{DesignerView-a4466dab.js → DesignerView-DemDevTQ.js} +1752 -2054
  24. flowfile/web/static/assets/{DesignerView-71d4e9a1.css → DesignerView-Dm6OzlIc.css} +209 -168
  25. flowfile/web/static/assets/{DocumentationView-979afc84.js → DocumentationView-BrC1ZR3H.js} +3 -4
  26. flowfile/web/static/assets/{ExploreData-e4b92aaf.js → ExploreData-BMKcDuRb.js} +8 -10
  27. flowfile/web/static/assets/{ExternalSource-d08e7227.js → ExternalSource-BXrNNS-f.js} +40 -42
  28. flowfile/web/static/assets/{ExternalSource-7ac7373f.css → ExternalSource-NB6WVl5R.css} +14 -14
  29. flowfile/web/static/assets/{Filter-7add806d.js → Filter-C2MjsN6P.js} +36 -33
  30. flowfile/web/static/assets/{Filter-7494ea97.css → Filter-DCMGGuGC.css} +9 -9
  31. flowfile/web/static/assets/{Formula-53d58c43.css → Formula-BYafbDj8.css} +4 -4
  32. flowfile/web/static/assets/{Formula-36ab24d2.js → Formula-ufuy4mVD.js} +27 -26
  33. flowfile/web/static/assets/{FuzzyMatch-ad6361d6.css → FuzzyMatch-BGJAwgd0.css} +42 -42
  34. flowfile/web/static/assets/{FuzzyMatch-cc01bb04.js → FuzzyMatch-BOHODq3h.js} +36 -38
  35. flowfile/web/static/assets/{GraphSolver-4fb98f3b.js → GraphSolver-B6ZzpNGO.js} +23 -21
  36. flowfile/web/static/assets/{GraphSolver-4b4d7db9.css → GraphSolver-DFN83sj3.css} +4 -4
  37. flowfile/web/static/assets/{GroupBy-b3c8f429.js → GroupBy-B9BRNcfe.js} +30 -29
  38. flowfile/web/static/assets/{Sort-4abb7fae.css → GroupBy-x4ooP5np.css} +1 -1
  39. flowfile/web/static/assets/Join-Bx_g5bZz.css +118 -0
  40. flowfile/web/static/assets/{Join-096b7b26.js → Join-DsBEy1IH.js} +48 -43
  41. flowfile/web/static/assets/{LoginView-c33a246a.js → LoginView-Ct0rhdcO.js} +1 -2
  42. flowfile/web/static/assets/{ManualInput-39111f19.css → ManualInput-DlZmtMdt.css} +48 -48
  43. flowfile/web/static/assets/{ManualInput-7307e9b1.js → ManualInput-bC4BUgnG.js} +40 -41
  44. flowfile/web/static/assets/{MultiSelect-14822c48.js → MultiSelect-DIQ8PuTC.js} +2 -2
  45. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-90c4d340.js → MultiSelect.vue_vue_type_script_setup_true_lang-BefHfqTI.js} +1 -1
  46. flowfile/web/static/assets/{NodeDesigner-5036c392.js → NodeDesigner-D39yzr2k.js} +178 -208
  47. flowfile/web/static/assets/{NodeDesigner-94cd4dd3.css → NodeDesigner-R0l6sYyY.css} +76 -76
  48. flowfile/web/static/assets/{NumericInput-15cf3b72.js → NumericInput-DMSX3oOr.js} +2 -2
  49. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-91e679d7.js → NumericInput.vue_vue_type_script_setup_true_lang-d0YlVHAl.js} +1 -1
  50. flowfile/web/static/assets/{Output-1f8ed42c.js → Output-D0VoXGcW.js} +26 -34
  51. flowfile/web/static/assets/{Output-692dd25d.css → Output-DsmglIDy.css} +5 -5
  52. flowfile/web/static/assets/{Pivot-0e153f4e.js → Pivot-BnMB4sEe.js} +26 -26
  53. flowfile/web/static/assets/{Pivot-0eda81b4.css → Pivot-qKTyWxop.css} +4 -4
  54. flowfile/web/static/assets/{PivotValidation-81ec2a33.js → PivotValidation-B2lWvugt.js} +7 -9
  55. flowfile/web/static/assets/{PivotValidation-5a4f7c79.js → PivotValidation-BPlhRjpL.js} +7 -9
  56. flowfile/web/static/assets/{PolarsCode-a39f15ac.js → PolarsCode-5h0tHnWR.js} +22 -20
  57. flowfile/web/static/assets/{PopOver-ddcfe4f6.js → PopOver-BHpt5rsj.js} +5 -9
  58. flowfile/web/static/assets/{PopOver-d96599db.css → PopOver-CyYM4-rV.css} +1 -1
  59. flowfile/web/static/assets/{Read-90f366bc.css → Read-DJxkrTb_.css} +10 -10
  60. flowfile/web/static/assets/Read-TsLEFh3B.js +227 -0
  61. flowfile/web/static/assets/{RecordCount-e9048ccd.js → RecordCount-DkVixq9v.js} +18 -17
  62. flowfile/web/static/assets/{RecordId-ad02521d.js → RecordId-C2UEGlCf.js} +42 -39
  63. flowfile/web/static/assets/{SQLQueryComponent-2eeecf0b.js → SQLQueryComponent-Dr5KMoD3.js} +2 -3
  64. flowfile/web/static/assets/{Sample-9a68c23d.js → Sample-Cb3eQNmd.js} +30 -30
  65. flowfile/web/static/assets/{SecretSelector-2429f35a.js → SecretSelector-De2L2bSx.js} +3 -4
  66. flowfile/web/static/assets/{SecretsView-c6afc915.js → SecretsView-CheC9BPV.js} +13 -16
  67. flowfile/web/static/assets/{Select-fcd002b6.js → Select-CI8TloRs.js} +41 -36
  68. flowfile/web/static/assets/{SettingsSection-5ce15962.js → SettingsSection-B39ulIiI.js} +1 -2
  69. flowfile/web/static/assets/{SettingsSection-c6b1362c.js → SettingsSection-BiCc7S9h.js} +1 -2
  70. flowfile/web/static/assets/{SettingsSection-cebb91d5.js → SettingsSection-CITK_R7o.js} +2 -3
  71. flowfile/web/static/assets/{SettingsSection-26fe48d4.css → SettingsSection-D2GgY-Aq.css} +4 -4
  72. flowfile/web/static/assets/{SetupView-2d12e01f.js → SetupView-C1aXRDvp.js} +1 -2
  73. flowfile/web/static/assets/{SingleSelect-b67de4eb.js → SingleSelect-Kr_hz90m.js} +2 -2
  74. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-eedb70eb.js → SingleSelect.vue_vue_type_script_setup_true_lang-Rxht5Z5N.js} +1 -1
  75. flowfile/web/static/assets/{SliderInput-fd8134ac.js → SliderInput-CLqpCxCb.js} +1 -2
  76. flowfile/web/static/assets/{GroupBy-5792782d.css → Sort-BIt2kc_p.css} +1 -1
  77. flowfile/web/static/assets/{Sort-c005a573.js → Sort-Dnw_J6Qi.js} +25 -25
  78. flowfile/web/static/assets/{TextInput-1bb31dab.js → TextInput-wdlunIZC.js} +2 -2
  79. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-a51fe730.js → TextInput.vue_vue_type_script_setup_true_lang-Bcj3ywzv.js} +1 -1
  80. flowfile/web/static/assets/{TextToRows-4f363753.js → TextToRows-BhtyGWPq.js} +42 -49
  81. flowfile/web/static/assets/{TextToRows-12afb4f4.css → TextToRows-DivDOLDx.css} +9 -9
  82. flowfile/web/static/assets/{ToggleSwitch-ca0f2e5e.js → ToggleSwitch-B-6WzfFf.js} +2 -2
  83. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-49aa41d8.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-Cj8LqT-b.js} +1 -1
  84. flowfile/web/static/assets/{UnavailableFields-f6147968.js → UnavailableFields-Yf6XSqFB.js} +2 -3
  85. flowfile/web/static/assets/{Union-c65f17b7.js → Union-CwpjeKYC.js} +20 -23
  86. flowfile/web/static/assets/{Unpivot-b6ad6427.css → Union-DQJcpp3-.css} +6 -6
  87. flowfile/web/static/assets/{Unique-a1d96fb2.js → Unique-25v3urqH.js} +75 -74
  88. flowfile/web/static/assets/{Union-d6a8d7d5.css → Unpivot-Deqh1gtI.css} +6 -6
  89. flowfile/web/static/assets/{Unpivot-c2657ff3.js → Unpivot-sYcTTXrq.js} +34 -27
  90. flowfile/web/static/assets/{UnpivotValidation-28e29a3b.js → UnpivotValidation-C5DDEKY2.js} +5 -7
  91. flowfile/web/static/assets/VueGraphicWalker-B8l1_Z92.js +131 -0
  92. flowfile/web/static/assets/VueGraphicWalker-Da_1-3me.css +21 -0
  93. flowfile/web/static/assets/{api-df48ec50.js → api-C0LvF-0C.js} +1 -1
  94. flowfile/web/static/assets/{api-ee542cf7.js → api-DaC83EO_.js} +1 -1
  95. flowfile/web/static/assets/client-C8Ygr6Gb.js +42 -0
  96. flowfile/web/static/assets/{dropDown-7576a76a.js → dropDown-D5YXaPRR.js} +7 -12
  97. flowfile/web/static/assets/{fullEditor-7583bef5.js → fullEditor-BVYnWm05.js} +300 -18
  98. flowfile/web/static/assets/genericNodeSettings-2wAu-QKn.css +75 -0
  99. flowfile/web/static/assets/genericNodeSettings-BBtW_Cpz.js +590 -0
  100. flowfile/web/static/assets/{VueGraphicWalker-2fc3ddd4.js → graphic-walker.es-VrK6vdGE.js} +92305 -89751
  101. flowfile/web/static/assets/index-BCJxPfM5.js +6693 -0
  102. flowfile/web/static/assets/{index-057d770d.js → index-CHPMUR0d.js} +150 -170
  103. flowfile/web/static/assets/index-DPkoZWq8.js +32 -0
  104. flowfile/web/static/assets/index-DnW_KC_I.js +277 -0
  105. flowfile/web/static/assets/index-UFXyfirV.css +10797 -0
  106. flowfile/web/static/assets/index-bcuE0Z0p.js +87456 -0
  107. flowfile/web/static/assets/{node.types-2c15bb7e.js → node.types-Dl4gtSW9.js} +2 -2
  108. flowfile/web/static/assets/{outputCsv-c492b15e.js → outputCsv-BELuBiJZ.js} +1 -2
  109. flowfile/web/static/assets/outputCsv-CdGkv-fN.css +2581 -0
  110. flowfile/web/static/assets/{outputExcel-13bfa10f.js → outputExcel-D0TTNM79.js} +1 -2
  111. flowfile/web/static/assets/{outputParquet-9be1523a.js → outputParquet-Cz9EbRHj.js} +1 -2
  112. flowfile/web/static/assets/{readCsv-5a49a8c9.js → readCsv-7bd3kUMI.js} +1 -2
  113. flowfile/web/static/assets/{readExcel-27c30ad8.js → readExcel-Cq8CCwIv.js} +3 -4
  114. flowfile/web/static/assets/{readParquet-c5244ad5.css → readParquet-CRDmBrsp.css} +4 -4
  115. flowfile/web/static/assets/{readParquet-446bde68.js → readParquet-DjR4mRaj.js} +4 -5
  116. flowfile/web/static/assets/{secrets.api-34431884.js → secrets.api-C9o2KE5V.js} +1 -1
  117. flowfile/web/static/assets/{selectDynamic-5754a2b1.js → selectDynamic-Bl5FVsME.js} +5 -7
  118. flowfile/web/static/assets/useNodeSettings-dMS9zmh_.js +69 -0
  119. flowfile/web/static/assets/{vue-codemirror.esm-8f46fb36.js → vue-codemirror.esm-CwaYwln0.js} +3469 -3064
  120. flowfile/web/static/assets/{vue-content-loader.es-808fe33a.js → vue-content-loader.es-CMoRXo7N.js} +3 -3
  121. flowfile/web/static/index.html +2 -3
  122. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/METADATA +2 -1
  123. flowfile-0.6.1.dist-info/RECORD +417 -0
  124. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/WHEEL +1 -1
  125. flowfile_core/auth/password.py +1 -0
  126. flowfile_core/database/init_db.py +7 -5
  127. flowfile_core/fileExplorer/funcs.py +2 -2
  128. flowfile_core/flowfile/code_generator/code_generator.py +13 -11
  129. flowfile_core/flowfile/filter_expressions.py +327 -0
  130. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +61 -59
  131. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +3 -29
  132. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +45 -14
  133. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +20 -3
  134. flowfile_core/flowfile/flow_data_engine/subprocess_operations/streaming.py +206 -0
  135. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +146 -24
  136. flowfile_core/flowfile/flow_graph.py +504 -190
  137. flowfile_core/flowfile/flow_node/__init__.py +32 -0
  138. flowfile_core/flowfile/flow_node/executor.py +404 -0
  139. flowfile_core/flowfile/flow_node/flow_node.py +207 -106
  140. flowfile_core/flowfile/flow_node/models.py +40 -0
  141. flowfile_core/flowfile/flow_node/output_field_config_applier.py +217 -0
  142. flowfile_core/flowfile/flow_node/schema_utils.py +78 -0
  143. flowfile_core/flowfile/flow_node/state.py +155 -0
  144. flowfile_core/flowfile/history_manager.py +401 -0
  145. flowfile_core/flowfile/manage/compatibility_enhancements.py +9 -0
  146. flowfile_core/flowfile/manage/io_flowfile.py +3 -1
  147. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +20 -4
  148. flowfile_core/flowfile/util/execution_orderer.py +89 -36
  149. flowfile_core/routes/auth.py +8 -9
  150. flowfile_core/routes/routes.py +320 -101
  151. flowfile_core/routes/user_defined_components.py +18 -16
  152. flowfile_core/schemas/history_schema.py +220 -0
  153. flowfile_core/schemas/input_schema.py +130 -6
  154. flowfile_core/schemas/schemas.py +9 -0
  155. flowfile_core/schemas/transform_schema.py +27 -5
  156. flowfile_core/schemas/yaml_types.py +23 -5
  157. flowfile_frame/adding_expr.py +18 -126
  158. flowfile_frame/callable_utils.py +261 -0
  159. flowfile_frame/database/connection_manager.py +0 -1
  160. flowfile_frame/expr.py +8 -4
  161. flowfile_frame/flow_frame.py +41 -41
  162. flowfile_frame/lazy.py +3 -12
  163. flowfile_frame/lazy_methods.py +5 -64
  164. flowfile_frame/utils.py +13 -32
  165. flowfile_worker/funcs.py +6 -4
  166. flowfile_worker/main.py +2 -0
  167. flowfile_worker/models.py +31 -11
  168. flowfile_worker/routes.py +60 -35
  169. flowfile_worker/spawner.py +7 -1
  170. flowfile_worker/streaming.py +335 -0
  171. flowfile/web/static/assets/ContextMenu-366bf1b4.js +0 -9
  172. flowfile/web/static/assets/ContextMenu-85cf5b44.js +0 -9
  173. flowfile/web/static/assets/ContextMenu-9d28ae6d.js +0 -9
  174. flowfile/web/static/assets/Join-28b5e18f.css +0 -109
  175. flowfile/web/static/assets/Read-39b63932.js +0 -222
  176. flowfile/web/static/assets/VueGraphicWalker-430f0b86.css +0 -6
  177. flowfile/web/static/assets/database_reader-ce1e55f3.svg +0 -24
  178. flowfile/web/static/assets/database_writer-b4ad0753.svg +0 -23
  179. flowfile/web/static/assets/element-icons-9c88a535.woff +0 -0
  180. flowfile/web/static/assets/element-icons-de5eb258.ttf +0 -0
  181. flowfile/web/static/assets/genericNodeSettings-0155288b.js +0 -136
  182. flowfile/web/static/assets/genericNodeSettings-3b2507ea.css +0 -46
  183. flowfile/web/static/assets/index-aeec439d.js +0 -38
  184. flowfile/web/static/assets/index-ca6799de.js +0 -62760
  185. flowfile/web/static/assets/index-d60c9dd4.css +0 -10777
  186. flowfile/web/static/assets/nodeInput-d478b9ac.js +0 -2
  187. flowfile/web/static/assets/outputCsv-cc84e09f.css +0 -2499
  188. flowfile-0.5.6.dist-info/RECORD +0 -407
  189. /flowfile/web/static/assets/{AdminView-f53bad23.css → AdminView-B2Dthl3u.css} +0 -0
  190. /flowfile/web/static/assets/{CloudConnectionView-cf85f943.css → CloudConnectionView-BdFYGWV7.css} +0 -0
  191. /flowfile/web/static/assets/{ColumnActionInput-c44b7aee.css → ColumnActionInput-dCasSIC9.css} +0 -0
  192. /flowfile/web/static/assets/{ColumnSelector-371637fb.css → ColumnSelector-j6sEOjo1.css} +0 -0
  193. /flowfile/web/static/assets/{CustomNode-edb9b939.css → CustomNode-VPlajG0j.css} +0 -0
  194. /flowfile/web/static/assets/{DatabaseConnectionSettings-c20a1e16.css → DatabaseConnectionSettings-B78hXYgu.css} +0 -0
  195. /flowfile/web/static/assets/{DatabaseView-6655afd6.css → DatabaseView-B-_adk1s.css} +0 -0
  196. /flowfile/web/static/assets/{DocumentationView-9ea6e871.css → DocumentationView-CL7iipFL.css} +0 -0
  197. /flowfile/web/static/assets/{ExploreData-10c5acc8.css → ExploreData-DHjv0Plr.css} +0 -0
  198. /flowfile/web/static/assets/{LoginView-d325d632.css → LoginView-DN1BXY3e.css} +0 -0
  199. /flowfile/web/static/assets/{PivotValidation-0e905b1a.css → PivotValidation-DK-FARWe.css} +0 -0
  200. /flowfile/web/static/assets/{PivotValidation-41b57ad6.css → PivotValidation-FUa9F47u.css} +0 -0
  201. /flowfile/web/static/assets/{PolarsCode-2b1f1f23.css → PolarsCode-G-gRSrSc.css} +0 -0
  202. /flowfile/web/static/assets/{SQLQueryComponent-edb90b98.css → SQLQueryComponent-oAbWw0r-.css} +0 -0
  203. /flowfile/web/static/assets/{SecretSelector-6329f743.css → SecretSelector-CJSadIZx.css} +0 -0
  204. /flowfile/web/static/assets/{SecretsView-aa291340.css → SecretsView-DbzIRAba.css} +0 -0
  205. /flowfile/web/static/assets/{SettingsSection-8f980839.css → SettingsSection-BGcJnH6q.css} +0 -0
  206. /flowfile/web/static/assets/{SettingsSection-07fbbc39.css → SettingsSection-DDWn_EGW.css} +0 -0
  207. /flowfile/web/static/assets/{SetupView-ec26f76a.css → SetupView-CI1nd-5Z.css} +0 -0
  208. /flowfile/web/static/assets/{SliderInput-f2e4f23c.css → SliderInput-BRk-q_Dk.css} +0 -0
  209. /flowfile/web/static/assets/{UnavailableFields-394a1f78.css → UnavailableFields-DRKDImKe.css} +0 -0
  210. /flowfile/web/static/assets/{Unique-2b705521.css → Unique-Absb0aON.css} +0 -0
  211. /flowfile/web/static/assets/{UnpivotValidation-d5ca3b7b.css → UnpivotValidation-DSBkFgS-.css} +0 -0
  212. /flowfile/web/static/assets/{airbyte-292aa232.png → airbyte-W0xvIXwZ.png} +0 -0
  213. /flowfile/web/static/assets/{cloud_storage_reader-aa1415d6.png → cloud_storage_reader-3GpSCk90.png} +0 -0
  214. /flowfile/web/static/assets/{cross_join-d30c0290.png → cross_join-B0qpgYoV.png} +0 -0
  215. /flowfile/web/static/assets/{dropDown-1d6acbd9.css → dropDown-CE0VF5_P.css} +0 -0
  216. /flowfile/web/static/assets/{explore_data-8a0a2861.png → explore_data-tX6olPPL.png} +0 -0
  217. /flowfile/web/static/assets/{fa-brands-400-808443ae.ttf → fa-brands-400-D1LuMI3I.ttf} +0 -0
  218. /flowfile/web/static/assets/{fa-brands-400-d7236a19.woff2 → fa-brands-400-D_cYUPeE.woff2} +0 -0
  219. /flowfile/web/static/assets/{fa-regular-400-e3456d12.woff2 → fa-regular-400-BjRzuEpd.woff2} +0 -0
  220. /flowfile/web/static/assets/{fa-regular-400-54cf6086.ttf → fa-regular-400-DZaxPHgR.ttf} +0 -0
  221. /flowfile/web/static/assets/{fa-solid-900-aa759986.woff2 → fa-solid-900-CTAAxXor.woff2} +0 -0
  222. /flowfile/web/static/assets/{fa-solid-900-d2f05935.ttf → fa-solid-900-D0aA9rwL.ttf} +0 -0
  223. /flowfile/web/static/assets/{fa-v4compatibility-0ce9033c.woff2 → fa-v4compatibility-C9RhG_FT.woff2} +0 -0
  224. /flowfile/web/static/assets/{fa-v4compatibility-30f6abf6.ttf → fa-v4compatibility-CCth-dXg.ttf} +0 -0
  225. /flowfile/web/static/assets/{filter-d7708bda.png → filter-WRdZyUOw.png} +0 -0
  226. /flowfile/web/static/assets/{formula-eeeb1611.png → formula-CgM7uHVI.png} +0 -0
  227. /flowfile/web/static/assets/{fullEditor-fe9f7e18.css → fullEditor-CmDI7T9F.css} +0 -0
  228. /flowfile/web/static/assets/{fuzzy_match-40c161b2.png → fuzzy_match-Yon3k5Tc.png} +0 -0
  229. /flowfile/web/static/assets/{graph_solver-8b7888b8.png → graph_solver-BlMrBttD.png} +0 -0
  230. /flowfile/web/static/assets/{group_by-80561fc3.png → group_by-Gici0CSS.png} +0 -0
  231. /flowfile/web/static/assets/{input_data-ab2eb678.png → input_data-BRdGecLc.png} +0 -0
  232. /flowfile/web/static/assets/{join-349043ae.png → join-BITWRu73.png} +0 -0
  233. /flowfile/web/static/assets/{manual_input-ae98f31d.png → manual_input-CFvo_EUS.png} +0 -0
  234. /flowfile/web/static/assets/{old_join-5d0eb604.png → old_join-B9bkpPqv.png} +0 -0
  235. /flowfile/web/static/assets/{output-06ec0371.png → output-Dp7-ZpC4.png} +0 -0
  236. /flowfile/web/static/assets/{outputExcel-f5d272b2.css → outputExcel-CKgRe2iT.css} +0 -0
  237. /flowfile/web/static/assets/{outputParquet-54597c3c.css → outputParquet-d7j407cK.css} +0 -0
  238. /flowfile/web/static/assets/{pivot-9660df51.png → pivot-DSxKhNlD.png} +0 -0
  239. /flowfile/web/static/assets/{polars_code-05ce5dc6.png → polars_code-DxiztZ1c.png} +0 -0
  240. /flowfile/web/static/assets/{readCsv-3bfac4c3.css → readCsv-BG-1Jilp.css} +0 -0
  241. /flowfile/web/static/assets/{readExcel-3db6b763.css → readExcel-DBQXKPtC.css} +0 -0
  242. /flowfile/web/static/assets/{record_count-dab44eb5.png → record_count-DCeaLtpS.png} +0 -0
  243. /flowfile/web/static/assets/{record_id-0b15856b.png → record_id-FeUjyIFh.png} +0 -0
  244. /flowfile/web/static/assets/{sample-693a88b5.png → sample-DeqfRiB-.png} +0 -0
  245. /flowfile/web/static/assets/{select-b0d0437a.png → select-D4JjbdjS.png} +0 -0
  246. /flowfile/web/static/assets/{selectDynamic-f2fb394f.css → selectDynamic-CjeTPUUo.css} +0 -0
  247. /flowfile/web/static/assets/{sort-2aa579f0.png → sort-DGwUG9WS.png} +0 -0
  248. /flowfile/web/static/assets/{summarize-2a099231.png → summarize-DFaNHpfp.png} +0 -0
  249. /flowfile/web/static/assets/{text_to_rows-859b29ea.png → text_to_rows-BdiAewrN.png} +0 -0
  250. /flowfile/web/static/assets/{union-2d8609f4.png → union-DCK-LSMq.png} +0 -0
  251. /flowfile/web/static/assets/{unique-1958b98a.png → unique-CdP3zZIq.png} +0 -0
  252. /flowfile/web/static/assets/{unpivot-d3cb4b5b.png → unpivot-CHttrEt8.png} +0 -0
  253. /flowfile/web/static/assets/{user-defined-icon-0ae16c90.png → user-defined-icon-BcIp2Vzo.png} +0 -0
  254. /flowfile/web/static/assets/{view-7a0f0be1.png → view-DUSRwjvq.png} +0 -0
  255. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/entry_points.txt +0 -0
  256. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/licenses/LICENSE +0 -0
flowfile_worker/routes.py CHANGED
@@ -1,9 +1,8 @@
1
1
  import os
2
2
  import uuid
3
- from base64 import encodebytes
4
3
 
5
4
  import polars as pl
6
- from fastapi import APIRouter, BackgroundTasks, HTTPException, Response
5
+ from fastapi import APIRouter, BackgroundTasks, HTTPException, Request, Response
7
6
 
8
7
  from flowfile_worker import CACHE_DIR, PROCESS_MEMORY_USAGE, models, status_dict, status_dict_lock
9
8
  from flowfile_worker.configs import logger
@@ -23,32 +22,45 @@ def create_and_get_default_cache_dir(flowfile_flow_id: int) -> str:
23
22
 
24
23
 
25
24
  @router.post("/submit_query/")
26
- def submit_query(polars_script: models.PolarsScript, background_tasks: BackgroundTasks) -> models.Status:
27
- logger.info(f"Processing query with operation: {polars_script.operation_type}")
28
-
25
+ async def submit_query(request: Request, background_tasks: BackgroundTasks) -> models.Status:
26
+ """Accept raw binary data with metadata in headers for efficient transfer."""
29
27
  try:
30
- polars_script.task_id = str(uuid.uuid4()) if polars_script.task_id is None else polars_script.task_id
31
- default_cache_dir = create_and_get_default_cache_dir(polars_script.flowfile_flow_id)
28
+ # Read raw bytes directly from request body - no base64 decoding needed
29
+ polars_serializable_object = await request.body()
30
+
31
+ # Get metadata from headers
32
+ task_id = request.headers.get("X-Task-Id") or str(uuid.uuid4())
33
+ operation_type = request.headers.get("X-Operation-Type", "store")
34
+ flow_id = int(request.headers.get("X-Flow-Id", "1"))
35
+ node_id = request.headers.get("X-Node-Id", "-1")
36
+ # Try to parse node_id as int, fall back to string
37
+ try:
38
+ node_id = int(node_id)
39
+ except ValueError:
40
+ pass
41
+
42
+ logger.info(f"Processing query with operation: {operation_type}")
43
+
44
+ default_cache_dir = create_and_get_default_cache_dir(flow_id)
45
+ file_path = os.path.join(default_cache_dir, f"{task_id}.arrow")
46
+ result_type = "polars" if operation_type == "store" else "other"
32
47
 
33
- polars_script.cache_dir = polars_script.cache_dir if polars_script.cache_dir is not None else default_cache_dir
34
- polars_serializable_object = polars_script.polars_serializable_object()
35
- file_path = os.path.join(polars_script.cache_dir, f"{polars_script.task_id}.arrow")
36
- result_type = "polars" if polars_script.operation_type == "store" else "other"
37
48
  status = models.Status(
38
- background_task_id=polars_script.task_id, status="Starting", file_ref=file_path, result_type=result_type
49
+ background_task_id=task_id, status="Starting", file_ref=file_path, result_type=result_type
39
50
  )
40
- status_dict[polars_script.task_id] = status
51
+ status_dict[task_id] = status
52
+
41
53
  background_tasks.add_task(
42
54
  start_process,
43
55
  polars_serializable_object=polars_serializable_object,
44
- task_id=polars_script.task_id,
45
- operation=polars_script.operation_type,
56
+ task_id=task_id,
57
+ operation=operation_type,
46
58
  file_ref=file_path,
47
- flowfile_flow_id=polars_script.flowfile_flow_id,
48
- flowfile_node_id=polars_script.flowfile_node_id,
59
+ flowfile_flow_id=flow_id,
60
+ flowfile_node_id=node_id,
49
61
  kwargs={},
50
62
  )
51
- logger.info(f"Started background task: {polars_script.task_id}")
63
+ logger.info(f"Started background task: {task_id}")
52
64
  return status
53
65
 
54
66
  except Exception as e:
@@ -57,32 +69,44 @@ def submit_query(polars_script: models.PolarsScript, background_tasks: Backgroun
57
69
 
58
70
 
59
71
  @router.post("/store_sample/")
60
- def store_sample(polars_script: models.PolarsScriptSample, background_tasks: BackgroundTasks) -> models.Status:
61
- logger.info(f"Processing sample storage with size: {polars_script.sample_size}")
62
-
72
+ async def store_sample(request: Request, background_tasks: BackgroundTasks) -> models.Status:
73
+ """Accept raw binary data with metadata in headers for efficient transfer."""
63
74
  try:
64
- default_cache_dir = create_and_get_default_cache_dir(polars_script.flowfile_flow_id)
65
- polars_script.task_id = str(uuid.uuid4()) if polars_script.task_id is None else polars_script.task_id
66
- polars_script.cache_dir = polars_script.cache_dir if polars_script.cache_dir is not None else default_cache_dir
67
- polars_serializable_object = polars_script.polars_serializable_object()
75
+ # Read raw bytes directly from request body - no base64 decoding needed
76
+ polars_serializable_object = await request.body()
77
+
78
+ # Get metadata from headers
79
+ task_id = request.headers.get("X-Task-Id") or str(uuid.uuid4())
80
+ sample_size = int(request.headers.get("X-Sample-Size", "100"))
81
+ flow_id = int(request.headers.get("X-Flow-Id", "1"))
82
+ node_id = request.headers.get("X-Node-Id", "-1")
83
+ # Try to parse node_id as int, fall back to string
84
+ try:
85
+ node_id = int(node_id)
86
+ except ValueError:
87
+ pass
88
+
89
+ logger.info(f"Processing sample storage with size: {sample_size}")
90
+
91
+ default_cache_dir = create_and_get_default_cache_dir(flow_id)
92
+ file_path = os.path.join(default_cache_dir, f"{task_id}.arrow")
68
93
 
69
- file_path = os.path.join(polars_script.cache_dir, f"{polars_script.task_id}.arrow")
70
94
  status = models.Status(
71
- background_task_id=polars_script.task_id, status="Starting", file_ref=file_path, result_type="other"
95
+ background_task_id=task_id, status="Starting", file_ref=file_path, result_type="other"
72
96
  )
73
- status_dict[polars_script.task_id] = status
97
+ status_dict[task_id] = status
74
98
 
75
99
  background_tasks.add_task(
76
100
  start_process,
77
101
  polars_serializable_object=polars_serializable_object,
78
- task_id=polars_script.task_id,
79
- operation=polars_script.operation_type,
102
+ task_id=task_id,
103
+ operation="store_sample",
80
104
  file_ref=file_path,
81
- flowfile_flow_id=polars_script.flowfile_flow_id,
82
- flowfile_node_id=polars_script.flowfile_node_id,
83
- kwargs={"sample_size": polars_script.sample_size},
105
+ flowfile_flow_id=flow_id,
106
+ flowfile_node_id=node_id,
107
+ kwargs={"sample_size": sample_size},
84
108
  )
85
- logger.info(f"Started sample storage task: {polars_script.task_id}")
109
+ logger.info(f"Started sample storage task: {task_id}")
86
110
 
87
111
  return status
88
112
 
@@ -373,7 +397,8 @@ async def fetch_results(task_id: str):
373
397
  raise HTTPException(status_code=404, detail=f"An error occurred during processing: {status.error_message}")
374
398
  try:
375
399
  lf = pl.scan_parquet(status.file_ref)
376
- return {"task_id": task_id, "result": encodebytes(lf.serialize()).decode()}
400
+ # Return raw bytes - Pydantic/FastAPI will handle serialization
401
+ return {"task_id": task_id, "result": lf.serialize()}
377
402
  except Exception as e:
378
403
  logger.error(f"Error reading results: {str(e)}")
379
404
  raise HTTPException(status_code=500, detail="Error reading results")
@@ -1,4 +1,5 @@
1
1
  import gc
2
+ from base64 import b64encode
2
3
  from multiprocessing import Process
3
4
  from multiprocessing.queues import Queue
4
5
  from time import sleep
@@ -62,7 +63,12 @@ def handle_task(task_id: str, p: Process, progress: mp_context.Value, error_mess
62
63
  if final_progress == 100:
63
64
  status.status = "Completed"
64
65
  if not q.empty():
65
- status.results = q.get()
66
+ result = q.get()
67
+ # b64-encode bytes for JSON-safe storage in status_dict (REST responses)
68
+ if isinstance(result, bytes):
69
+ status.results = b64encode(result).decode("ascii")
70
+ else:
71
+ status.results = result
66
72
  elif final_progress != -1:
67
73
  status_dict[task_id].status = "Unknown Error"
68
74
 
@@ -0,0 +1,335 @@
1
+ """
2
+ WebSocket streaming endpoint for worker-core communication.
3
+
4
+ Replaces the HTTP poll-based pattern with a single WebSocket connection per task:
5
+ 1. Core sends JSON metadata + binary payload
6
+ 2. Worker streams progress updates as JSON
7
+ 3. Worker sends result as binary frame (no base64 encoding)
8
+
9
+ This eliminates:
10
+ - HTTP polling latency (0.5s+ per poll cycle)
11
+ - Base64 encode/decode overhead on result bytes
12
+ - Multiple HTTP round-trips per task
13
+ """
14
+
15
+ import asyncio
16
+ import gc
17
+ import os
18
+ import threading
19
+ import uuid
20
+ from dataclasses import dataclass
21
+ from multiprocessing import Process
22
+ from multiprocessing.queues import Queue
23
+ from typing import Any
24
+
25
+ from base64 import b64encode
26
+ from fastapi import APIRouter, WebSocket, WebSocketDisconnect
27
+
28
+ from flowfile_worker import CACHE_DIR, funcs, models, mp_context, status_dict, status_dict_lock
29
+ from flowfile_worker.configs import logger
30
+ from flowfile_worker.spawner import process_manager
31
+
32
+ streaming_router = APIRouter()
33
+
34
+ # Maps operation type to result type for status tracking
35
+ _POLARS_RESULT_OPERATIONS = frozenset({"store"})
36
+
37
+
38
+ def _get_result_type(operation: str) -> str:
39
+ return "polars" if operation in _POLARS_RESULT_OPERATIONS else "other"
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Task context
44
+ # ---------------------------------------------------------------------------
45
+
46
+ @dataclass
47
+ class _TaskContext:
48
+ """Parsed and resolved metadata for a WebSocket task."""
49
+ task_id: str
50
+ operation: str
51
+ flow_id: int
52
+ node_id: int | str
53
+ extra_kwargs: dict
54
+ file_path: str
55
+ result_type: str
56
+
57
+
58
+ def _parse_metadata(metadata: dict) -> _TaskContext:
59
+ """Parse raw WebSocket metadata into a resolved TaskContext."""
60
+ task_id = metadata.get("task_id") or str(uuid.uuid4())
61
+ operation = metadata.get("operation", "store")
62
+ flow_id = int(metadata.get("flow_id", 1))
63
+ node_id = metadata.get("node_id", -1)
64
+ extra_kwargs = metadata.get("kwargs", {})
65
+
66
+ try:
67
+ node_id = int(node_id)
68
+ except (ValueError, TypeError):
69
+ pass
70
+
71
+ # Set up cache directory and file path
72
+ cache_dir = CACHE_DIR / str(flow_id)
73
+ cache_dir.mkdir(parents=True, exist_ok=True)
74
+ file_path = os.path.join(str(cache_dir), f"{task_id}.arrow")
75
+ result_type = _get_result_type(operation)
76
+
77
+ return _TaskContext(
78
+ task_id=task_id,
79
+ operation=operation,
80
+ flow_id=flow_id,
81
+ node_id=node_id,
82
+ extra_kwargs=extra_kwargs,
83
+ file_path=file_path,
84
+ result_type=result_type,
85
+ )
86
+
87
+
88
+ def _register_status(ctx: _TaskContext) -> None:
89
+ """Register the task in status_dict for REST compatibility."""
90
+ status_dict[ctx.task_id] = models.Status(
91
+ background_task_id=ctx.task_id,
92
+ status="Starting",
93
+ file_ref=ctx.file_path,
94
+ result_type=ctx.result_type,
95
+ )
96
+
97
+
98
+ # ---------------------------------------------------------------------------
99
+ # Subprocess management
100
+ # ---------------------------------------------------------------------------
101
+
102
+ def _spawn_subprocess(ctx: _TaskContext, polars_bytes: bytes) -> tuple[Process, Any, Any, Queue]:
103
+ """Spawn a worker subprocess and return (process, progress, error_message, queue)."""
104
+ process_task = getattr(funcs, ctx.operation)
105
+
106
+ kwargs = dict(ctx.extra_kwargs)
107
+ kwargs["polars_serializable_object"] = polars_bytes
108
+
109
+ progress = mp_context.Value("i", 0)
110
+ error_message = mp_context.Array("c", 1024)
111
+ queue = mp_context.Queue(maxsize=1)
112
+
113
+ kwargs["progress"] = progress
114
+ kwargs["error_message"] = error_message
115
+ kwargs["queue"] = queue
116
+ kwargs["file_path"] = ctx.file_path
117
+ kwargs["flowfile_flow_id"] = ctx.flow_id
118
+ kwargs["flowfile_node_id"] = ctx.node_id
119
+
120
+ p = mp_context.Process(target=process_task, kwargs=kwargs)
121
+ p.start()
122
+ process_manager.add_process(ctx.task_id, p)
123
+
124
+ with status_dict_lock:
125
+ status_dict[ctx.task_id].status = "Processing"
126
+
127
+ logger.info(f"[WS] Started task {ctx.task_id} with operation: {ctx.operation}")
128
+ return p, progress, error_message, queue
129
+
130
+
131
+ def _read_error_message(error_message) -> str:
132
+ """Extract error string from shared ctypes array."""
133
+ with error_message.get_lock():
134
+ return error_message.value.decode().rstrip("\x00")
135
+
136
+
137
+ def _set_error_status(task_id: str, msg: str) -> None:
138
+ """Update status_dict to reflect an error."""
139
+ with status_dict_lock:
140
+ status_dict[task_id].status = "Error"
141
+ status_dict[task_id].error_message = msg
142
+
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # Progress monitoring
146
+ # ---------------------------------------------------------------------------
147
+
148
+ async def _monitor_progress(websocket: WebSocket, p: Process, progress, error_message, task_id: str) -> bool:
149
+ """Stream progress updates while subprocess is alive.
150
+
151
+ Returns True if an error was detected and sent to the client.
152
+ """
153
+ last_progress = -1
154
+
155
+ while p.is_alive():
156
+ await asyncio.sleep(0.3)
157
+
158
+ with progress.get_lock():
159
+ current = progress.value
160
+
161
+ if current != last_progress:
162
+ try:
163
+ await websocket.send_json({"type": "progress", "progress": current})
164
+ except Exception:
165
+ return False
166
+ last_progress = current
167
+
168
+ if current == -1:
169
+ msg = _read_error_message(error_message)
170
+ _set_error_status(task_id, msg)
171
+ await websocket.send_json({"type": "error", "error_message": msg})
172
+ return True
173
+
174
+ return False
175
+
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # Result handling
179
+ # ---------------------------------------------------------------------------
180
+
181
+ def _update_completed_status(task_id: str, result_data: Any) -> None:
182
+ """Update status_dict for a successfully completed task."""
183
+ with status_dict_lock:
184
+ status_dict[task_id].status = "Completed"
185
+ status_dict[task_id].progress = 100
186
+ if result_data is not None:
187
+ if isinstance(result_data, bytes):
188
+ status_dict[task_id].results = b64encode(result_data).decode("ascii")
189
+ else:
190
+ status_dict[task_id].results = result_data
191
+
192
+
193
+ async def _send_completion(websocket: WebSocket, task_id: str, result_type: str,
194
+ file_path: str, queue: Queue) -> None:
195
+ """Send completion message and result data over WebSocket."""
196
+ result_data = queue.get() if not queue.empty() else None
197
+ _update_completed_status(task_id, result_data)
198
+
199
+ has_result = result_data is not None
200
+ await websocket.send_json({
201
+ "type": "complete",
202
+ "result_type": result_type,
203
+ "file_ref": file_path,
204
+ "has_result": has_result,
205
+ })
206
+
207
+ if has_result:
208
+ if isinstance(result_data, bytes):
209
+ await websocket.send_bytes(result_data)
210
+ else:
211
+ await websocket.send_json({"type": "result_data", "data": result_data})
212
+
213
+ logger.info(f"[WS] Task {task_id} completed successfully")
214
+
215
+
216
+ async def _send_final_error(websocket: WebSocket, task_id: str, progress, error_message) -> None:
217
+ """Handle error or unexpected termination after subprocess exits."""
218
+ with progress.get_lock():
219
+ final = progress.value
220
+
221
+ if final == -1:
222
+ msg = _read_error_message(error_message)
223
+ _set_error_status(task_id, msg)
224
+ await websocket.send_json({"type": "error", "error_message": msg})
225
+ else:
226
+ with status_dict_lock:
227
+ status_dict[task_id].status = "Unknown Error"
228
+ await websocket.send_json({
229
+ "type": "error",
230
+ "error_message": "Process ended unexpectedly",
231
+ })
232
+
233
+
234
+ # ---------------------------------------------------------------------------
235
+ # Disconnect handling
236
+ # ---------------------------------------------------------------------------
237
+
238
+ def _handoff_to_background(task_id: str, p: Process, progress, error_message, queue: Queue) -> None:
239
+ """Hand off a running subprocess to a background thread for REST status updates."""
240
+ from flowfile_worker.spawner import handle_task as _handle_task
241
+
242
+ threading.Thread(
243
+ target=_handle_task,
244
+ args=(task_id, p, progress, error_message, queue),
245
+ daemon=True,
246
+ ).start()
247
+
248
+
249
+ # ---------------------------------------------------------------------------
250
+ # Cleanup
251
+ # ---------------------------------------------------------------------------
252
+
253
+ def _cleanup_process(task_id: str | None, p: Process | None) -> None:
254
+ """Clean up subprocess resources."""
255
+ if p is not None:
256
+ if p.is_alive():
257
+ p.join(timeout=1)
258
+ if p.is_alive():
259
+ p.terminate()
260
+ p.join()
261
+ process_manager.remove_process(task_id)
262
+
263
+
264
+ # ---------------------------------------------------------------------------
265
+ # WebSocket endpoint
266
+ # ---------------------------------------------------------------------------
267
+
268
+ @streaming_router.websocket("/ws/submit")
269
+ async def ws_submit(websocket: WebSocket):
270
+ """WebSocket endpoint for streaming task submission and result retrieval.
271
+
272
+ Protocol (Core -> Worker):
273
+ 1. JSON message: task metadata (task_id, operation, flow_id, node_id, kwargs)
274
+ 2. Binary message: serialized Polars LazyFrame bytes
275
+
276
+ Protocol (Worker -> Core):
277
+ - JSON: {"type": "progress", "progress": N} (0-100, sent periodically)
278
+ - JSON: {"type": "complete", "result_type": "polars"|"other", "file_ref": "...", "has_result": bool}
279
+ - Binary: raw result bytes (only if has_result=True and result_type="polars")
280
+ - JSON: {"type": "result_data", "data": ...} (only if has_result=True and result_type="other")
281
+ - JSON: {"type": "error", "error_message": "..."}
282
+ """
283
+ await websocket.accept()
284
+ p = None
285
+ task_id = None
286
+ progress = None
287
+ error_message = None
288
+ queue = None
289
+
290
+ try:
291
+ # 1. Receive metadata + binary payload
292
+ metadata = await websocket.receive_json()
293
+ ctx = _parse_metadata(metadata)
294
+ task_id = ctx.task_id
295
+ _register_status(ctx)
296
+
297
+ polars_bytes = await websocket.receive_bytes()
298
+
299
+ # 2. Spawn subprocess
300
+ p, progress, error_message, queue = _spawn_subprocess(ctx, polars_bytes)
301
+
302
+ # 3. Monitor progress (returns True if error was sent)
303
+ had_error = await _monitor_progress(websocket, p, progress, error_message, task_id)
304
+ if had_error:
305
+ return
306
+
307
+ p.join()
308
+
309
+ # 4. Send result or error
310
+ with progress.get_lock():
311
+ final = progress.value
312
+
313
+ if final == 100:
314
+ await _send_completion(websocket, task_id, ctx.result_type, ctx.file_path, queue)
315
+ else:
316
+ await _send_final_error(websocket, task_id, progress, error_message)
317
+
318
+ except WebSocketDisconnect:
319
+ logger.warning(f"[WS] Client disconnected for task {task_id}")
320
+ if p is not None and p.is_alive() and queue is not None:
321
+ _handoff_to_background(task_id, p, progress, error_message, queue)
322
+ # Prevent finally block from cleaning up - handle_task owns these now
323
+ p = None
324
+ progress = None
325
+ error_message = None
326
+ except Exception as e:
327
+ logger.error(f"[WS] Error for task {task_id}: {e}", exc_info=True)
328
+ try:
329
+ await websocket.send_json({"type": "error", "error_message": str(e)})
330
+ except Exception:
331
+ pass
332
+ finally:
333
+ _cleanup_process(task_id, p)
334
+ del p, progress, error_message
335
+ gc.collect()
@@ -1,9 +0,0 @@
1
- import "./index-ca6799de.js";
2
- import "./DesignerView-a4466dab.js";
3
- import { _ as _sfc_main } from "./ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js";
4
- import "./PopOver-ddcfe4f6.js";
5
- import "./index-057d770d.js";
6
- import "./vue-codemirror.esm-8f46fb36.js";
7
- export {
8
- _sfc_main as default
9
- };
@@ -1,9 +0,0 @@
1
- import "./index-ca6799de.js";
2
- import "./DesignerView-a4466dab.js";
3
- import { _ as _sfc_main } from "./ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js";
4
- import "./PopOver-ddcfe4f6.js";
5
- import "./index-057d770d.js";
6
- import "./vue-codemirror.esm-8f46fb36.js";
7
- export {
8
- _sfc_main as default
9
- };
@@ -1,9 +0,0 @@
1
- import "./index-ca6799de.js";
2
- import "./DesignerView-a4466dab.js";
3
- import { _ as _sfc_main } from "./ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js";
4
- import "./PopOver-ddcfe4f6.js";
5
- import "./index-057d770d.js";
6
- import "./vue-codemirror.esm-8f46fb36.js";
7
- export {
8
- _sfc_main as default
9
- };
@@ -1,109 +0,0 @@
1
-
2
- /* Join Type Selector */
3
- .join-type-selector[data-v-8435fe68] {
4
- display: flex;
5
- align-items: center;
6
- margin: 12px;
7
- gap: 10px;
8
- }
9
- .join-type-label[data-v-8435fe68] {
10
- font-size: 12px;
11
- color: var(--color-text-primary);
12
- font-weight: 500;
13
- min-width: 70px;
14
- }
15
-
16
- /* Join Mapping Section */
17
- .table-wrapper[data-v-8435fe68] {
18
- border: 1px solid #eee;
19
- border-radius: 6px;
20
- overflow: hidden;
21
- margin: 5px;
22
- }
23
- .selectors-header[data-v-8435fe68] {
24
- display: flex;
25
- justify-content: space-between;
26
- padding: 8px 16px;
27
- background-color: #fafafa;
28
- border-bottom: 1px solid #eee;
29
- }
30
- .selectors-title[data-v-8435fe68] {
31
- flex: 1;
32
- text-align: center;
33
- font-size: 12px;
34
- color: #666;
35
- font-weight: 500;
36
- }
37
- .selectors-container[data-v-8435fe68] {
38
- padding: 12px;
39
- box-sizing: border-box;
40
- width: 100%;
41
- display: flex;
42
- justify-content: space-between;
43
- flex-direction: column;
44
- }
45
- .selectors-row[data-v-8435fe68] {
46
- display: flex;
47
- gap: 12px;
48
- margin-bottom: 8px;
49
- width: 100%;
50
- display: flex;
51
- justify-content: space-between;
52
- }
53
- .selectors-row[data-v-8435fe68]:last-child {
54
- margin-bottom: 0;
55
- }
56
-
57
- /* Action Buttons */
58
- .action-buttons[data-v-8435fe68] {
59
- display: flex;
60
- gap: 4px;
61
- min-width: 60px;
62
- justify-content: center;
63
- }
64
- .action-button[data-v-8435fe68],
65
- .add-join-button[data-v-8435fe68],
66
- .remove-join-button[data-v-8435fe68] {
67
- cursor: pointer;
68
- width: 24px;
69
- height: 24px;
70
- border-radius: 4px;
71
- border: 1px solid #ddd;
72
- background-color: var(--color-background-primary);
73
- display: flex;
74
- align-items: center;
75
- justify-content: center;
76
- font-size: 14px;
77
- transition: all 0.2s ease;
78
- }
79
- .add-join-button[data-v-8435fe68] {
80
- color: #45a049;
81
- border-color: #45a049;
82
- }
83
- .add-join-button[data-v-8435fe68]:hover {
84
- background-color: #45a049;
85
- color: #fff;
86
- }
87
- .remove-join-button[data-v-8435fe68] {
88
- color: #d32f2f;
89
- border-color: #d32f2f;
90
- }
91
- .remove-join-button[data-v-8435fe68]:hover {
92
- background-color: #d32f2f;
93
- color: #fff;
94
- }
95
-
96
- /* Custom scrollbar */
97
- .selectors-container[data-v-8435fe68]::-webkit-scrollbar {
98
- width: 8px;
99
- }
100
- .selectors-container[data-v-8435fe68]::-webkit-scrollbar-track {
101
- background: transparent;
102
- }
103
- .selectors-container[data-v-8435fe68]::-webkit-scrollbar-thumb {
104
- background-color: rgba(0, 0, 0, 0.1);
105
- border-radius: 4px;
106
- }
107
- .selectors-container[data-v-8435fe68]::-webkit-scrollbar-thumb:hover {
108
- background-color: rgba(0, 0, 0, 0.2);
109
- }