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.
- flowfile/api.py +8 -6
- flowfile/web/static/assets/{AdminView-c2c7942b.js → AdminView-C4K1DdHI.js} +28 -33
- flowfile/web/static/assets/{CloudConnectionView-7a3042c6.js → CloudConnectionView-BZbPvPUL.js} +39 -50
- flowfile/web/static/assets/{CloudStorageReader-24c54524.css → CloudStorageReader-BDByiqPI.css} +25 -25
- flowfile/web/static/assets/{CloudStorageReader-709c4037.js → CloudStorageReader-DLVukNJ7.js} +30 -35
- flowfile/web/static/assets/{CloudStorageWriter-604c51a8.js → CloudStorageWriter-Bfi-C1QW.js} +32 -37
- flowfile/web/static/assets/{CloudStorageWriter-60547855.css → CloudStorageWriter-y8jL8yjG.css} +24 -24
- flowfile/web/static/assets/{ColumnActionInput-d63d6746.js → ColumnActionInput-BpiCApw9.js} +7 -12
- flowfile/web/static/assets/{ColumnSelector-0c8cd1cd.js → ColumnSelector-CEAwedI7.js} +1 -2
- flowfile/web/static/assets/ContextMenu-CdojQu0w.js +9 -0
- flowfile/web/static/assets/ContextMenu-D12mhsy1.js +9 -0
- flowfile/web/static/assets/ContextMenu-EWUR98va.js +9 -0
- 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
- flowfile/web/static/assets/{CrossJoin-38e5b99a.js → CrossJoin-BOFfxkJO.js} +19 -18
- flowfile/web/static/assets/{CrossJoin-71b4cc10.css → CrossJoin-Cmbyt9im.css} +18 -18
- flowfile/web/static/assets/{CustomNode-76e8f3f5.js → CustomNode-Bhpezobq.js} +12 -17
- flowfile/web/static/assets/{DatabaseConnectionSettings-38155669.js → DatabaseConnectionSettings-Dw3bSJKB.js} +10 -11
- flowfile/web/static/assets/{DatabaseReader-5bf8c75b.css → DatabaseReader-D6pUNUCs.css} +21 -21
- flowfile/web/static/assets/{DatabaseReader-2e549c8f.js → DatabaseReader-m87ghlw0.js} +36 -34
- flowfile/web/static/assets/{DatabaseView-dc877c29.js → DatabaseView-CisSAtpe.js} +30 -38
- flowfile/web/static/assets/{DatabaseWriter-ffb91864.js → DatabaseWriter-Bbj9JLdL.js} +33 -35
- flowfile/web/static/assets/{DatabaseWriter-bdcf2c8b.css → DatabaseWriter-RBqdFLj8.css} +17 -17
- flowfile/web/static/assets/{DesignerView-a4466dab.js → DesignerView-DemDevTQ.js} +1752 -2054
- flowfile/web/static/assets/{DesignerView-71d4e9a1.css → DesignerView-Dm6OzlIc.css} +209 -168
- flowfile/web/static/assets/{DocumentationView-979afc84.js → DocumentationView-BrC1ZR3H.js} +3 -4
- flowfile/web/static/assets/{ExploreData-e4b92aaf.js → ExploreData-BMKcDuRb.js} +8 -10
- flowfile/web/static/assets/{ExternalSource-d08e7227.js → ExternalSource-BXrNNS-f.js} +40 -42
- flowfile/web/static/assets/{ExternalSource-7ac7373f.css → ExternalSource-NB6WVl5R.css} +14 -14
- flowfile/web/static/assets/{Filter-7add806d.js → Filter-C2MjsN6P.js} +36 -33
- flowfile/web/static/assets/{Filter-7494ea97.css → Filter-DCMGGuGC.css} +9 -9
- flowfile/web/static/assets/{Formula-53d58c43.css → Formula-BYafbDj8.css} +4 -4
- flowfile/web/static/assets/{Formula-36ab24d2.js → Formula-ufuy4mVD.js} +27 -26
- flowfile/web/static/assets/{FuzzyMatch-ad6361d6.css → FuzzyMatch-BGJAwgd0.css} +42 -42
- flowfile/web/static/assets/{FuzzyMatch-cc01bb04.js → FuzzyMatch-BOHODq3h.js} +36 -38
- flowfile/web/static/assets/{GraphSolver-4fb98f3b.js → GraphSolver-B6ZzpNGO.js} +23 -21
- flowfile/web/static/assets/{GraphSolver-4b4d7db9.css → GraphSolver-DFN83sj3.css} +4 -4
- flowfile/web/static/assets/{GroupBy-b3c8f429.js → GroupBy-B9BRNcfe.js} +30 -29
- flowfile/web/static/assets/{Sort-4abb7fae.css → GroupBy-x4ooP5np.css} +1 -1
- flowfile/web/static/assets/Join-Bx_g5bZz.css +118 -0
- flowfile/web/static/assets/{Join-096b7b26.js → Join-DsBEy1IH.js} +48 -43
- flowfile/web/static/assets/{LoginView-c33a246a.js → LoginView-Ct0rhdcO.js} +1 -2
- flowfile/web/static/assets/{ManualInput-39111f19.css → ManualInput-DlZmtMdt.css} +48 -48
- flowfile/web/static/assets/{ManualInput-7307e9b1.js → ManualInput-bC4BUgnG.js} +40 -41
- flowfile/web/static/assets/{MultiSelect-14822c48.js → MultiSelect-DIQ8PuTC.js} +2 -2
- 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
- flowfile/web/static/assets/{NodeDesigner-5036c392.js → NodeDesigner-D39yzr2k.js} +178 -208
- flowfile/web/static/assets/{NodeDesigner-94cd4dd3.css → NodeDesigner-R0l6sYyY.css} +76 -76
- flowfile/web/static/assets/{NumericInput-15cf3b72.js → NumericInput-DMSX3oOr.js} +2 -2
- 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
- flowfile/web/static/assets/{Output-1f8ed42c.js → Output-D0VoXGcW.js} +26 -34
- flowfile/web/static/assets/{Output-692dd25d.css → Output-DsmglIDy.css} +5 -5
- flowfile/web/static/assets/{Pivot-0e153f4e.js → Pivot-BnMB4sEe.js} +26 -26
- flowfile/web/static/assets/{Pivot-0eda81b4.css → Pivot-qKTyWxop.css} +4 -4
- flowfile/web/static/assets/{PivotValidation-81ec2a33.js → PivotValidation-B2lWvugt.js} +7 -9
- flowfile/web/static/assets/{PivotValidation-5a4f7c79.js → PivotValidation-BPlhRjpL.js} +7 -9
- flowfile/web/static/assets/{PolarsCode-a39f15ac.js → PolarsCode-5h0tHnWR.js} +22 -20
- flowfile/web/static/assets/{PopOver-ddcfe4f6.js → PopOver-BHpt5rsj.js} +5 -9
- flowfile/web/static/assets/{PopOver-d96599db.css → PopOver-CyYM4-rV.css} +1 -1
- flowfile/web/static/assets/{Read-90f366bc.css → Read-DJxkrTb_.css} +10 -10
- flowfile/web/static/assets/Read-TsLEFh3B.js +227 -0
- flowfile/web/static/assets/{RecordCount-e9048ccd.js → RecordCount-DkVixq9v.js} +18 -17
- flowfile/web/static/assets/{RecordId-ad02521d.js → RecordId-C2UEGlCf.js} +42 -39
- flowfile/web/static/assets/{SQLQueryComponent-2eeecf0b.js → SQLQueryComponent-Dr5KMoD3.js} +2 -3
- flowfile/web/static/assets/{Sample-9a68c23d.js → Sample-Cb3eQNmd.js} +30 -30
- flowfile/web/static/assets/{SecretSelector-2429f35a.js → SecretSelector-De2L2bSx.js} +3 -4
- flowfile/web/static/assets/{SecretsView-c6afc915.js → SecretsView-CheC9BPV.js} +13 -16
- flowfile/web/static/assets/{Select-fcd002b6.js → Select-CI8TloRs.js} +41 -36
- flowfile/web/static/assets/{SettingsSection-5ce15962.js → SettingsSection-B39ulIiI.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-c6b1362c.js → SettingsSection-BiCc7S9h.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-cebb91d5.js → SettingsSection-CITK_R7o.js} +2 -3
- flowfile/web/static/assets/{SettingsSection-26fe48d4.css → SettingsSection-D2GgY-Aq.css} +4 -4
- flowfile/web/static/assets/{SetupView-2d12e01f.js → SetupView-C1aXRDvp.js} +1 -2
- flowfile/web/static/assets/{SingleSelect-b67de4eb.js → SingleSelect-Kr_hz90m.js} +2 -2
- 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
- flowfile/web/static/assets/{SliderInput-fd8134ac.js → SliderInput-CLqpCxCb.js} +1 -2
- flowfile/web/static/assets/{GroupBy-5792782d.css → Sort-BIt2kc_p.css} +1 -1
- flowfile/web/static/assets/{Sort-c005a573.js → Sort-Dnw_J6Qi.js} +25 -25
- flowfile/web/static/assets/{TextInput-1bb31dab.js → TextInput-wdlunIZC.js} +2 -2
- 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
- flowfile/web/static/assets/{TextToRows-4f363753.js → TextToRows-BhtyGWPq.js} +42 -49
- flowfile/web/static/assets/{TextToRows-12afb4f4.css → TextToRows-DivDOLDx.css} +9 -9
- flowfile/web/static/assets/{ToggleSwitch-ca0f2e5e.js → ToggleSwitch-B-6WzfFf.js} +2 -2
- 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
- flowfile/web/static/assets/{UnavailableFields-f6147968.js → UnavailableFields-Yf6XSqFB.js} +2 -3
- flowfile/web/static/assets/{Union-c65f17b7.js → Union-CwpjeKYC.js} +20 -23
- flowfile/web/static/assets/{Unpivot-b6ad6427.css → Union-DQJcpp3-.css} +6 -6
- flowfile/web/static/assets/{Unique-a1d96fb2.js → Unique-25v3urqH.js} +75 -74
- flowfile/web/static/assets/{Union-d6a8d7d5.css → Unpivot-Deqh1gtI.css} +6 -6
- flowfile/web/static/assets/{Unpivot-c2657ff3.js → Unpivot-sYcTTXrq.js} +34 -27
- flowfile/web/static/assets/{UnpivotValidation-28e29a3b.js → UnpivotValidation-C5DDEKY2.js} +5 -7
- flowfile/web/static/assets/VueGraphicWalker-B8l1_Z92.js +131 -0
- flowfile/web/static/assets/VueGraphicWalker-Da_1-3me.css +21 -0
- flowfile/web/static/assets/{api-df48ec50.js → api-C0LvF-0C.js} +1 -1
- flowfile/web/static/assets/{api-ee542cf7.js → api-DaC83EO_.js} +1 -1
- flowfile/web/static/assets/client-C8Ygr6Gb.js +42 -0
- flowfile/web/static/assets/{dropDown-7576a76a.js → dropDown-D5YXaPRR.js} +7 -12
- flowfile/web/static/assets/{fullEditor-7583bef5.js → fullEditor-BVYnWm05.js} +300 -18
- flowfile/web/static/assets/genericNodeSettings-2wAu-QKn.css +75 -0
- flowfile/web/static/assets/genericNodeSettings-BBtW_Cpz.js +590 -0
- flowfile/web/static/assets/{VueGraphicWalker-2fc3ddd4.js → graphic-walker.es-VrK6vdGE.js} +92305 -89751
- flowfile/web/static/assets/index-BCJxPfM5.js +6693 -0
- flowfile/web/static/assets/{index-057d770d.js → index-CHPMUR0d.js} +150 -170
- flowfile/web/static/assets/index-DPkoZWq8.js +32 -0
- flowfile/web/static/assets/index-DnW_KC_I.js +277 -0
- flowfile/web/static/assets/index-UFXyfirV.css +10797 -0
- flowfile/web/static/assets/index-bcuE0Z0p.js +87456 -0
- flowfile/web/static/assets/{node.types-2c15bb7e.js → node.types-Dl4gtSW9.js} +2 -2
- flowfile/web/static/assets/{outputCsv-c492b15e.js → outputCsv-BELuBiJZ.js} +1 -2
- flowfile/web/static/assets/outputCsv-CdGkv-fN.css +2581 -0
- flowfile/web/static/assets/{outputExcel-13bfa10f.js → outputExcel-D0TTNM79.js} +1 -2
- flowfile/web/static/assets/{outputParquet-9be1523a.js → outputParquet-Cz9EbRHj.js} +1 -2
- flowfile/web/static/assets/{readCsv-5a49a8c9.js → readCsv-7bd3kUMI.js} +1 -2
- flowfile/web/static/assets/{readExcel-27c30ad8.js → readExcel-Cq8CCwIv.js} +3 -4
- flowfile/web/static/assets/{readParquet-c5244ad5.css → readParquet-CRDmBrsp.css} +4 -4
- flowfile/web/static/assets/{readParquet-446bde68.js → readParquet-DjR4mRaj.js} +4 -5
- flowfile/web/static/assets/{secrets.api-34431884.js → secrets.api-C9o2KE5V.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-5754a2b1.js → selectDynamic-Bl5FVsME.js} +5 -7
- flowfile/web/static/assets/useNodeSettings-dMS9zmh_.js +69 -0
- flowfile/web/static/assets/{vue-codemirror.esm-8f46fb36.js → vue-codemirror.esm-CwaYwln0.js} +3469 -3064
- flowfile/web/static/assets/{vue-content-loader.es-808fe33a.js → vue-content-loader.es-CMoRXo7N.js} +3 -3
- flowfile/web/static/index.html +2 -3
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/METADATA +2 -1
- flowfile-0.6.1.dist-info/RECORD +417 -0
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/WHEEL +1 -1
- flowfile_core/auth/password.py +1 -0
- flowfile_core/database/init_db.py +7 -5
- flowfile_core/fileExplorer/funcs.py +2 -2
- flowfile_core/flowfile/code_generator/code_generator.py +13 -11
- flowfile_core/flowfile/filter_expressions.py +327 -0
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +61 -59
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +3 -29
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +45 -14
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +20 -3
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/streaming.py +206 -0
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +146 -24
- flowfile_core/flowfile/flow_graph.py +504 -190
- flowfile_core/flowfile/flow_node/__init__.py +32 -0
- flowfile_core/flowfile/flow_node/executor.py +404 -0
- flowfile_core/flowfile/flow_node/flow_node.py +207 -106
- flowfile_core/flowfile/flow_node/models.py +40 -0
- flowfile_core/flowfile/flow_node/output_field_config_applier.py +217 -0
- flowfile_core/flowfile/flow_node/schema_utils.py +78 -0
- flowfile_core/flowfile/flow_node/state.py +155 -0
- flowfile_core/flowfile/history_manager.py +401 -0
- flowfile_core/flowfile/manage/compatibility_enhancements.py +9 -0
- flowfile_core/flowfile/manage/io_flowfile.py +3 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +20 -4
- flowfile_core/flowfile/util/execution_orderer.py +89 -36
- flowfile_core/routes/auth.py +8 -9
- flowfile_core/routes/routes.py +320 -101
- flowfile_core/routes/user_defined_components.py +18 -16
- flowfile_core/schemas/history_schema.py +220 -0
- flowfile_core/schemas/input_schema.py +130 -6
- flowfile_core/schemas/schemas.py +9 -0
- flowfile_core/schemas/transform_schema.py +27 -5
- flowfile_core/schemas/yaml_types.py +23 -5
- flowfile_frame/adding_expr.py +18 -126
- flowfile_frame/callable_utils.py +261 -0
- flowfile_frame/database/connection_manager.py +0 -1
- flowfile_frame/expr.py +8 -4
- flowfile_frame/flow_frame.py +41 -41
- flowfile_frame/lazy.py +3 -12
- flowfile_frame/lazy_methods.py +5 -64
- flowfile_frame/utils.py +13 -32
- flowfile_worker/funcs.py +6 -4
- flowfile_worker/main.py +2 -0
- flowfile_worker/models.py +31 -11
- flowfile_worker/routes.py +60 -35
- flowfile_worker/spawner.py +7 -1
- flowfile_worker/streaming.py +335 -0
- flowfile/web/static/assets/ContextMenu-366bf1b4.js +0 -9
- flowfile/web/static/assets/ContextMenu-85cf5b44.js +0 -9
- flowfile/web/static/assets/ContextMenu-9d28ae6d.js +0 -9
- flowfile/web/static/assets/Join-28b5e18f.css +0 -109
- flowfile/web/static/assets/Read-39b63932.js +0 -222
- flowfile/web/static/assets/VueGraphicWalker-430f0b86.css +0 -6
- flowfile/web/static/assets/database_reader-ce1e55f3.svg +0 -24
- flowfile/web/static/assets/database_writer-b4ad0753.svg +0 -23
- flowfile/web/static/assets/element-icons-9c88a535.woff +0 -0
- flowfile/web/static/assets/element-icons-de5eb258.ttf +0 -0
- flowfile/web/static/assets/genericNodeSettings-0155288b.js +0 -136
- flowfile/web/static/assets/genericNodeSettings-3b2507ea.css +0 -46
- flowfile/web/static/assets/index-aeec439d.js +0 -38
- flowfile/web/static/assets/index-ca6799de.js +0 -62760
- flowfile/web/static/assets/index-d60c9dd4.css +0 -10777
- flowfile/web/static/assets/nodeInput-d478b9ac.js +0 -2
- flowfile/web/static/assets/outputCsv-cc84e09f.css +0 -2499
- flowfile-0.5.6.dist-info/RECORD +0 -407
- /flowfile/web/static/assets/{AdminView-f53bad23.css → AdminView-B2Dthl3u.css} +0 -0
- /flowfile/web/static/assets/{CloudConnectionView-cf85f943.css → CloudConnectionView-BdFYGWV7.css} +0 -0
- /flowfile/web/static/assets/{ColumnActionInput-c44b7aee.css → ColumnActionInput-dCasSIC9.css} +0 -0
- /flowfile/web/static/assets/{ColumnSelector-371637fb.css → ColumnSelector-j6sEOjo1.css} +0 -0
- /flowfile/web/static/assets/{CustomNode-edb9b939.css → CustomNode-VPlajG0j.css} +0 -0
- /flowfile/web/static/assets/{DatabaseConnectionSettings-c20a1e16.css → DatabaseConnectionSettings-B78hXYgu.css} +0 -0
- /flowfile/web/static/assets/{DatabaseView-6655afd6.css → DatabaseView-B-_adk1s.css} +0 -0
- /flowfile/web/static/assets/{DocumentationView-9ea6e871.css → DocumentationView-CL7iipFL.css} +0 -0
- /flowfile/web/static/assets/{ExploreData-10c5acc8.css → ExploreData-DHjv0Plr.css} +0 -0
- /flowfile/web/static/assets/{LoginView-d325d632.css → LoginView-DN1BXY3e.css} +0 -0
- /flowfile/web/static/assets/{PivotValidation-0e905b1a.css → PivotValidation-DK-FARWe.css} +0 -0
- /flowfile/web/static/assets/{PivotValidation-41b57ad6.css → PivotValidation-FUa9F47u.css} +0 -0
- /flowfile/web/static/assets/{PolarsCode-2b1f1f23.css → PolarsCode-G-gRSrSc.css} +0 -0
- /flowfile/web/static/assets/{SQLQueryComponent-edb90b98.css → SQLQueryComponent-oAbWw0r-.css} +0 -0
- /flowfile/web/static/assets/{SecretSelector-6329f743.css → SecretSelector-CJSadIZx.css} +0 -0
- /flowfile/web/static/assets/{SecretsView-aa291340.css → SecretsView-DbzIRAba.css} +0 -0
- /flowfile/web/static/assets/{SettingsSection-8f980839.css → SettingsSection-BGcJnH6q.css} +0 -0
- /flowfile/web/static/assets/{SettingsSection-07fbbc39.css → SettingsSection-DDWn_EGW.css} +0 -0
- /flowfile/web/static/assets/{SetupView-ec26f76a.css → SetupView-CI1nd-5Z.css} +0 -0
- /flowfile/web/static/assets/{SliderInput-f2e4f23c.css → SliderInput-BRk-q_Dk.css} +0 -0
- /flowfile/web/static/assets/{UnavailableFields-394a1f78.css → UnavailableFields-DRKDImKe.css} +0 -0
- /flowfile/web/static/assets/{Unique-2b705521.css → Unique-Absb0aON.css} +0 -0
- /flowfile/web/static/assets/{UnpivotValidation-d5ca3b7b.css → UnpivotValidation-DSBkFgS-.css} +0 -0
- /flowfile/web/static/assets/{airbyte-292aa232.png → airbyte-W0xvIXwZ.png} +0 -0
- /flowfile/web/static/assets/{cloud_storage_reader-aa1415d6.png → cloud_storage_reader-3GpSCk90.png} +0 -0
- /flowfile/web/static/assets/{cross_join-d30c0290.png → cross_join-B0qpgYoV.png} +0 -0
- /flowfile/web/static/assets/{dropDown-1d6acbd9.css → dropDown-CE0VF5_P.css} +0 -0
- /flowfile/web/static/assets/{explore_data-8a0a2861.png → explore_data-tX6olPPL.png} +0 -0
- /flowfile/web/static/assets/{fa-brands-400-808443ae.ttf → fa-brands-400-D1LuMI3I.ttf} +0 -0
- /flowfile/web/static/assets/{fa-brands-400-d7236a19.woff2 → fa-brands-400-D_cYUPeE.woff2} +0 -0
- /flowfile/web/static/assets/{fa-regular-400-e3456d12.woff2 → fa-regular-400-BjRzuEpd.woff2} +0 -0
- /flowfile/web/static/assets/{fa-regular-400-54cf6086.ttf → fa-regular-400-DZaxPHgR.ttf} +0 -0
- /flowfile/web/static/assets/{fa-solid-900-aa759986.woff2 → fa-solid-900-CTAAxXor.woff2} +0 -0
- /flowfile/web/static/assets/{fa-solid-900-d2f05935.ttf → fa-solid-900-D0aA9rwL.ttf} +0 -0
- /flowfile/web/static/assets/{fa-v4compatibility-0ce9033c.woff2 → fa-v4compatibility-C9RhG_FT.woff2} +0 -0
- /flowfile/web/static/assets/{fa-v4compatibility-30f6abf6.ttf → fa-v4compatibility-CCth-dXg.ttf} +0 -0
- /flowfile/web/static/assets/{filter-d7708bda.png → filter-WRdZyUOw.png} +0 -0
- /flowfile/web/static/assets/{formula-eeeb1611.png → formula-CgM7uHVI.png} +0 -0
- /flowfile/web/static/assets/{fullEditor-fe9f7e18.css → fullEditor-CmDI7T9F.css} +0 -0
- /flowfile/web/static/assets/{fuzzy_match-40c161b2.png → fuzzy_match-Yon3k5Tc.png} +0 -0
- /flowfile/web/static/assets/{graph_solver-8b7888b8.png → graph_solver-BlMrBttD.png} +0 -0
- /flowfile/web/static/assets/{group_by-80561fc3.png → group_by-Gici0CSS.png} +0 -0
- /flowfile/web/static/assets/{input_data-ab2eb678.png → input_data-BRdGecLc.png} +0 -0
- /flowfile/web/static/assets/{join-349043ae.png → join-BITWRu73.png} +0 -0
- /flowfile/web/static/assets/{manual_input-ae98f31d.png → manual_input-CFvo_EUS.png} +0 -0
- /flowfile/web/static/assets/{old_join-5d0eb604.png → old_join-B9bkpPqv.png} +0 -0
- /flowfile/web/static/assets/{output-06ec0371.png → output-Dp7-ZpC4.png} +0 -0
- /flowfile/web/static/assets/{outputExcel-f5d272b2.css → outputExcel-CKgRe2iT.css} +0 -0
- /flowfile/web/static/assets/{outputParquet-54597c3c.css → outputParquet-d7j407cK.css} +0 -0
- /flowfile/web/static/assets/{pivot-9660df51.png → pivot-DSxKhNlD.png} +0 -0
- /flowfile/web/static/assets/{polars_code-05ce5dc6.png → polars_code-DxiztZ1c.png} +0 -0
- /flowfile/web/static/assets/{readCsv-3bfac4c3.css → readCsv-BG-1Jilp.css} +0 -0
- /flowfile/web/static/assets/{readExcel-3db6b763.css → readExcel-DBQXKPtC.css} +0 -0
- /flowfile/web/static/assets/{record_count-dab44eb5.png → record_count-DCeaLtpS.png} +0 -0
- /flowfile/web/static/assets/{record_id-0b15856b.png → record_id-FeUjyIFh.png} +0 -0
- /flowfile/web/static/assets/{sample-693a88b5.png → sample-DeqfRiB-.png} +0 -0
- /flowfile/web/static/assets/{select-b0d0437a.png → select-D4JjbdjS.png} +0 -0
- /flowfile/web/static/assets/{selectDynamic-f2fb394f.css → selectDynamic-CjeTPUUo.css} +0 -0
- /flowfile/web/static/assets/{sort-2aa579f0.png → sort-DGwUG9WS.png} +0 -0
- /flowfile/web/static/assets/{summarize-2a099231.png → summarize-DFaNHpfp.png} +0 -0
- /flowfile/web/static/assets/{text_to_rows-859b29ea.png → text_to_rows-BdiAewrN.png} +0 -0
- /flowfile/web/static/assets/{union-2d8609f4.png → union-DCK-LSMq.png} +0 -0
- /flowfile/web/static/assets/{unique-1958b98a.png → unique-CdP3zZIq.png} +0 -0
- /flowfile/web/static/assets/{unpivot-d3cb4b5b.png → unpivot-CHttrEt8.png} +0 -0
- /flowfile/web/static/assets/{user-defined-icon-0ae16c90.png → user-defined-icon-BcIp2Vzo.png} +0 -0
- /flowfile/web/static/assets/{view-7a0f0be1.png → view-DUSRwjvq.png} +0 -0
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/entry_points.txt +0 -0
- {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(
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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=
|
|
49
|
+
background_task_id=task_id, status="Starting", file_ref=file_path, result_type=result_type
|
|
39
50
|
)
|
|
40
|
-
status_dict[
|
|
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=
|
|
45
|
-
operation=
|
|
56
|
+
task_id=task_id,
|
|
57
|
+
operation=operation_type,
|
|
46
58
|
file_ref=file_path,
|
|
47
|
-
flowfile_flow_id=
|
|
48
|
-
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: {
|
|
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(
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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=
|
|
95
|
+
background_task_id=task_id, status="Starting", file_ref=file_path, result_type="other"
|
|
72
96
|
)
|
|
73
|
-
status_dict[
|
|
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=
|
|
79
|
-
operation=
|
|
102
|
+
task_id=task_id,
|
|
103
|
+
operation="store_sample",
|
|
80
104
|
file_ref=file_path,
|
|
81
|
-
flowfile_flow_id=
|
|
82
|
-
flowfile_node_id=
|
|
83
|
-
kwargs={"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: {
|
|
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
|
-
|
|
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")
|
flowfile_worker/spawner.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
}
|