Flowfile 0.5.4__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/__main__.py +94 -1
- flowfile/api.py +8 -6
- flowfile/web/static/assets/{AdminView-f9847d67.js → AdminView-C4K1DdHI.js} +28 -33
- flowfile/web/static/assets/{CloudConnectionView-faace55b.js → CloudConnectionView-BZbPvPUL.js} +39 -50
- flowfile/web/static/assets/{CloudStorageReader-24c54524.css → CloudStorageReader-BDByiqPI.css} +25 -25
- flowfile/web/static/assets/{CloudStorageReader-d86ecaa7.js → CloudStorageReader-DLVukNJ7.js} +30 -35
- flowfile/web/static/assets/{CloudStorageWriter-0f4d9a44.js → CloudStorageWriter-Bfi-C1QW.js} +32 -37
- flowfile/web/static/assets/{CloudStorageWriter-60547855.css → CloudStorageWriter-y8jL8yjG.css} +24 -24
- flowfile/web/static/assets/{ColumnActionInput-f4189ae0.js → ColumnActionInput-BpiCApw9.js} +7 -12
- flowfile/web/static/assets/{ColumnSelector-e66b33da.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-a1bd6314.js → ContextMenu.vue_vue_type_script_setup_true_lang-I4rXXd6G.js} +4 -5
- flowfile/web/static/assets/{CrossJoin-24694b8f.js → CrossJoin-BOFfxkJO.js} +19 -18
- flowfile/web/static/assets/{CrossJoin-71b4cc10.css → CrossJoin-Cmbyt9im.css} +18 -18
- flowfile/web/static/assets/{CustomNode-569d45ff.js → CustomNode-Bhpezobq.js} +12 -17
- flowfile/web/static/assets/{DatabaseConnectionSettings-cfc08938.js → DatabaseConnectionSettings-Dw3bSJKB.js} +10 -11
- flowfile/web/static/assets/{DatabaseReader-5bf8c75b.css → DatabaseReader-D6pUNUCs.css} +21 -21
- flowfile/web/static/assets/{DatabaseReader-701feabb.js → DatabaseReader-m87ghlw0.js} +36 -34
- flowfile/web/static/assets/{DatabaseView-0482e5b5.js → DatabaseView-CisSAtpe.js} +30 -38
- flowfile/web/static/assets/{DatabaseWriter-16721989.js → DatabaseWriter-Bbj9JLdL.js} +33 -35
- flowfile/web/static/assets/{DatabaseWriter-bdcf2c8b.css → DatabaseWriter-RBqdFLj8.css} +17 -17
- flowfile/web/static/assets/{DesignerView-f64749fb.js → DesignerView-DemDevTQ.js} +1841 -2090
- flowfile/web/static/assets/{DesignerView-49abb835.css → DesignerView-Dm6OzlIc.css} +244 -202
- flowfile/web/static/assets/{DocumentationView-61bd2990.js → DocumentationView-BrC1ZR3H.js} +3 -4
- flowfile/web/static/assets/{ExploreData-e2735b13.js → ExploreData-BMKcDuRb.js} +8 -10
- flowfile/web/static/assets/{ExternalSource-2535c3b2.js → ExternalSource-BXrNNS-f.js} +40 -42
- flowfile/web/static/assets/{ExternalSource-7ac7373f.css → ExternalSource-NB6WVl5R.css} +14 -14
- flowfile/web/static/assets/{Filter-2cdbc93c.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-fcda3c2c.js → Formula-ufuy4mVD.js} +27 -26
- flowfile/web/static/assets/{FuzzyMatch-ad6361d6.css → FuzzyMatch-BGJAwgd0.css} +42 -42
- flowfile/web/static/assets/{FuzzyMatch-f8d3b7d3.js → FuzzyMatch-BOHODq3h.js} +36 -38
- flowfile/web/static/assets/{GraphSolver-72eaa695.js → GraphSolver-B6ZzpNGO.js} +23 -21
- flowfile/web/static/assets/{GraphSolver-4b4d7db9.css → GraphSolver-DFN83sj3.css} +4 -4
- flowfile/web/static/assets/{GroupBy-8aa0598b.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-e40f0ffa.js → Join-DsBEy1IH.js} +48 -43
- flowfile/web/static/assets/{LoginView-5111c9ae.js → LoginView-Ct0rhdcO.js} +1 -2
- flowfile/web/static/assets/{ManualInput-3702e677.css → ManualInput-DlZmtMdt.css} +48 -48
- flowfile/web/static/assets/{ManualInput-9b6f3224.js → ManualInput-bC4BUgnG.js} +85 -44
- flowfile/web/static/assets/{MultiSelect-ef28e19e.js → MultiSelect-DIQ8PuTC.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-83b3bbfd.js → MultiSelect.vue_vue_type_script_setup_true_lang-BefHfqTI.js} +1 -1
- flowfile/web/static/assets/{NodeDesigner-d2b7ee2b.js → NodeDesigner-D39yzr2k.js} +178 -208
- flowfile/web/static/assets/{NodeDesigner-94cd4dd3.css → NodeDesigner-R0l6sYyY.css} +76 -76
- flowfile/web/static/assets/{NumericInput-1d789794.js → NumericInput-DMSX3oOr.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-7775f83e.js → NumericInput.vue_vue_type_script_setup_true_lang-d0YlVHAl.js} +1 -1
- flowfile/web/static/assets/{Output-cefef801.js → Output-D0VoXGcW.js} +26 -34
- flowfile/web/static/assets/{Output-692dd25d.css → Output-DsmglIDy.css} +5 -5
- flowfile/web/static/assets/{Pivot-bab1b75b.js → Pivot-BnMB4sEe.js} +26 -26
- flowfile/web/static/assets/{Pivot-0eda81b4.css → Pivot-qKTyWxop.css} +4 -4
- flowfile/web/static/assets/{PivotValidation-fba09336.js → PivotValidation-B2lWvugt.js} +7 -9
- flowfile/web/static/assets/{PivotValidation-e7941f91.js → PivotValidation-BPlhRjpL.js} +7 -9
- flowfile/web/static/assets/{PolarsCode-740e40fa.js → PolarsCode-5h0tHnWR.js} +22 -20
- flowfile/web/static/assets/PopOver-BHpt5rsj.js +134 -0
- 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-ffc71eca.js → RecordCount-DkVixq9v.js} +18 -17
- flowfile/web/static/assets/{RecordId-a70bb8df.js → RecordId-C2UEGlCf.js} +42 -39
- flowfile/web/static/assets/{SQLQueryComponent-15a421f5.js → SQLQueryComponent-Dr5KMoD3.js} +2 -3
- flowfile/web/static/assets/{Sample-6c26afc7.js → Sample-Cb3eQNmd.js} +30 -30
- flowfile/web/static/assets/{SecretSelector-ceed9496.js → SecretSelector-De2L2bSx.js} +3 -4
- flowfile/web/static/assets/{SecretsView-214d255a.js → SecretsView-CheC9BPV.js} +13 -16
- flowfile/web/static/assets/{Select-8fc29999.js → Select-CI8TloRs.js} +41 -36
- flowfile/web/static/assets/{SettingsSection-9f0d1725.js → SettingsSection-B39ulIiI.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-83090218.js → SettingsSection-BiCc7S9h.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-3f70e4c3.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-3fa0aa03.js → SetupView-C1aXRDvp.js} +3 -4
- flowfile/web/static/assets/{SetupView-e2da3442.css → SetupView-CI1nd-5Z.css} +38 -38
- flowfile/web/static/assets/{SingleSelect-a4a568cb.js → SingleSelect-Kr_hz90m.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-c8ebdd33.js → SingleSelect.vue_vue_type_script_setup_true_lang-Rxht5Z5N.js} +1 -1
- flowfile/web/static/assets/{SliderInput-be533e71.js → SliderInput-CLqpCxCb.js} +1 -2
- flowfile/web/static/assets/{GroupBy-5792782d.css → Sort-BIt2kc_p.css} +1 -1
- flowfile/web/static/assets/{Sort-154dad81.js → Sort-Dnw_J6Qi.js} +25 -25
- flowfile/web/static/assets/{TextInput-454e2bda.js → TextInput-wdlunIZC.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-e86510d0.js → TextInput.vue_vue_type_script_setup_true_lang-Bcj3ywzv.js} +1 -1
- flowfile/web/static/assets/{TextToRows-ea73433d.js → TextToRows-BhtyGWPq.js} +42 -49
- flowfile/web/static/assets/{TextToRows-12afb4f4.css → TextToRows-DivDOLDx.css} +9 -9
- flowfile/web/static/assets/{ToggleSwitch-9d7b30f1.js → ToggleSwitch-B-6WzfFf.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-00f2580e.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-Cj8LqT-b.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-b72a2c72.js → UnavailableFields-Yf6XSqFB.js} +2 -3
- flowfile/web/static/assets/{Union-1e44f263.js → Union-CwpjeKYC.js} +20 -23
- flowfile/web/static/assets/{Unpivot-b6ad6427.css → Union-DQJcpp3-.css} +6 -6
- flowfile/web/static/assets/{Unique-a3bc6d0a.js → Unique-25v3urqH.js} +75 -74
- flowfile/web/static/assets/{Union-d6a8d7d5.css → Unpivot-Deqh1gtI.css} +6 -6
- flowfile/web/static/assets/{Unpivot-e27935fc.js → Unpivot-sYcTTXrq.js} +34 -27
- flowfile/web/static/assets/{UnpivotValidation-72497680.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-a2102880.js → api-C0LvF-0C.js} +1 -1
- flowfile/web/static/assets/{api-f75042b0.js → api-DaC83EO_.js} +1 -1
- flowfile/web/static/assets/client-C8Ygr6Gb.js +42 -0
- flowfile/web/static/assets/{dropDown-2798a109.js → dropDown-D5YXaPRR.js} +7 -12
- flowfile/web/static/assets/{fullEditor-cf7d7d93.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-d9ab70a3.js → graphic-walker.es-VrK6vdGE.js} +92305 -89751
- flowfile/web/static/assets/index-BCJxPfM5.js +6693 -0
- flowfile/web/static/assets/{index-f0a6e5a5.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-3c1757e8.js → outputCsv-BELuBiJZ.js} +2 -3
- flowfile/web/static/assets/outputCsv-CdGkv-fN.css +2581 -0
- flowfile/web/static/assets/{outputExcel-686e1f48.js → outputExcel-D0TTNM79.js} +1 -2
- flowfile/web/static/assets/{outputParquet-df28faa7.js → outputParquet-Cz9EbRHj.js} +1 -2
- flowfile/web/static/assets/{readCsv-e37eee21.js → readCsv-7bd3kUMI.js} +1 -2
- flowfile/web/static/assets/{readExcel-a13f14bb.js → readExcel-Cq8CCwIv.js} +3 -4
- flowfile/web/static/assets/{readParquet-c5244ad5.css → readParquet-CRDmBrsp.css} +4 -4
- flowfile/web/static/assets/{readParquet-344cf746.js → readParquet-DjR4mRaj.js} +4 -5
- flowfile/web/static/assets/{secrets.api-ae198c5c.js → secrets.api-C9o2KE5V.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-6b4b0767.js → selectDynamic-Bl5FVsME.js} +5 -8
- flowfile/web/static/assets/useNodeSettings-dMS9zmh_.js +69 -0
- flowfile/web/static/assets/{vue-codemirror.esm-31ba0e0b.js → vue-codemirror.esm-CwaYwln0.js} +3469 -3064
- flowfile/web/static/assets/{vue-content-loader.es-4469c8ff.js → vue-content-loader.es-CMoRXo7N.js} +3 -3
- flowfile/web/static/index.html +2 -3
- {flowfile-0.5.4.dist-info → flowfile-0.6.1.dist-info}/METADATA +2 -1
- flowfile-0.6.1.dist-info/RECORD +417 -0
- {flowfile-0.5.4.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 +158 -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/main.py +2 -4
- 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-49463352.js +0 -9
- flowfile/web/static/assets/ContextMenu-dd5f3f25.js +0 -9
- flowfile/web/static/assets/ContextMenu-f709b884.js +0 -9
- flowfile/web/static/assets/Join-28b5e18f.css +0 -109
- flowfile/web/static/assets/PopOver-862d7e28.js +0 -939
- flowfile/web/static/assets/Read-225cc63f.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-14eac1c3.js +0 -137
- flowfile/web/static/assets/genericNodeSettings-3b2507ea.css +0 -46
- flowfile/web/static/assets/index-387a6f18.js +0 -60752
- flowfile/web/static/assets/index-6b367bb5.js +0 -38
- flowfile/web/static/assets/index-e96ab018.css +0 -10466
- flowfile/web/static/assets/nodeInput-ed2ae8d7.js +0 -2
- flowfile/web/static/assets/outputCsv-b9a072af.css +0 -2499
- flowfile-0.5.4.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/{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.4.dist-info → flowfile-0.6.1.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.4.dist-info → flowfile-0.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocket streaming client for core-to-worker communication.
|
|
3
|
+
|
|
4
|
+
Replaces the HTTP poll-based pattern with a single WebSocket connection:
|
|
5
|
+
- Sends task metadata + serialized LazyFrame as binary
|
|
6
|
+
- Receives progress updates as JSON pushes
|
|
7
|
+
- Receives result as raw binary frame (no base64 encoding)
|
|
8
|
+
|
|
9
|
+
Falls back to REST automatically if the worker doesn't support WebSocket.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import io
|
|
13
|
+
import json
|
|
14
|
+
from base64 import b64encode
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import polars as pl
|
|
18
|
+
from websockets.sync.client import connect
|
|
19
|
+
|
|
20
|
+
from flowfile_core.configs.settings import WORKER_URL
|
|
21
|
+
from flowfile_core.flowfile.flow_data_engine.subprocess_operations.models import Status
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_ws_url() -> str:
|
|
25
|
+
"""Convert HTTP worker URL to WebSocket URL."""
|
|
26
|
+
return WORKER_URL.replace("http://", "ws://").replace("https://", "wss://")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Message building
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
def _build_metadata(
|
|
34
|
+
task_id: str,
|
|
35
|
+
operation_type: str,
|
|
36
|
+
flow_id: int,
|
|
37
|
+
node_id: int | str,
|
|
38
|
+
kwargs: dict | None,
|
|
39
|
+
) -> dict:
|
|
40
|
+
"""Build the JSON metadata message for the WebSocket protocol."""
|
|
41
|
+
metadata = {
|
|
42
|
+
"task_id": task_id,
|
|
43
|
+
"operation": operation_type,
|
|
44
|
+
"flow_id": flow_id,
|
|
45
|
+
"node_id": node_id,
|
|
46
|
+
}
|
|
47
|
+
if kwargs:
|
|
48
|
+
metadata["kwargs"] = kwargs
|
|
49
|
+
return metadata
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Message receiving
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
def _handle_complete_message(data: dict, task_id: str) -> Status:
|
|
57
|
+
"""Build a partial Status from a 'complete' protocol message.
|
|
58
|
+
|
|
59
|
+
The ``results`` field is set to None here and populated by the caller
|
|
60
|
+
once the actual result payload has been received.
|
|
61
|
+
"""
|
|
62
|
+
return Status(
|
|
63
|
+
background_task_id=task_id,
|
|
64
|
+
status="Completed",
|
|
65
|
+
file_ref=data.get("file_ref", ""),
|
|
66
|
+
result_type=data.get("result_type", "polars"),
|
|
67
|
+
progress=100,
|
|
68
|
+
results=data.get("results", None),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _receive_raw_result(ws, task_id: str) -> tuple[Any, Status | None]:
|
|
73
|
+
"""Receive messages from the worker until a raw result or error arrives.
|
|
74
|
+
|
|
75
|
+
Returns the result **without** deserializing it so the caller can both
|
|
76
|
+
populate ``Status.results`` (b64-encoded bytes for polars) and
|
|
77
|
+
deserialize into the in-memory object.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
(raw_result, status) where raw_result is ``bytes`` for polars
|
|
81
|
+
results, arbitrary data for "other" results, or ``None``.
|
|
82
|
+
"""
|
|
83
|
+
raw_result = None
|
|
84
|
+
status = None
|
|
85
|
+
|
|
86
|
+
while True:
|
|
87
|
+
msg = ws.recv()
|
|
88
|
+
|
|
89
|
+
if isinstance(msg, bytes):
|
|
90
|
+
raw_result = msg
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
data = json.loads(msg)
|
|
94
|
+
msg_type = data.get("type")
|
|
95
|
+
|
|
96
|
+
if msg_type == "progress":
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
if msg_type == "complete":
|
|
100
|
+
status = _handle_complete_message(data, task_id)
|
|
101
|
+
if not data.get("has_result", False):
|
|
102
|
+
break
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if msg_type == "result_data":
|
|
106
|
+
raw_result = data.get("data")
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if msg_type == "error":
|
|
110
|
+
raise Exception(data.get("error_message", "Unknown worker error"))
|
|
111
|
+
|
|
112
|
+
return raw_result, status
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _deserialize_and_populate_status(
|
|
116
|
+
raw_result: Any, status: Status
|
|
117
|
+
) -> tuple[Any, Status]:
|
|
118
|
+
"""Deserialize the raw result and fill ``status.results``.
|
|
119
|
+
|
|
120
|
+
For polars results (bytes): deserializes into a LazyFrame and stores
|
|
121
|
+
the b64-encoded bytes in ``status.results`` (matching REST behaviour).
|
|
122
|
+
For other results: stores the value directly in ``status.results``.
|
|
123
|
+
"""
|
|
124
|
+
if raw_result is None:
|
|
125
|
+
return None, status
|
|
126
|
+
|
|
127
|
+
if isinstance(raw_result, bytes):
|
|
128
|
+
status.results = b64encode(raw_result).decode("ascii")
|
|
129
|
+
return pl.LazyFrame.deserialize(io.BytesIO(raw_result)), status
|
|
130
|
+
|
|
131
|
+
status.results = raw_result
|
|
132
|
+
return raw_result, status
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def streaming_start(
|
|
136
|
+
task_id: str,
|
|
137
|
+
operation_type: str,
|
|
138
|
+
flow_id: int,
|
|
139
|
+
node_id: int | str,
|
|
140
|
+
lf_bytes: bytes,
|
|
141
|
+
kwargs: dict | None = None,
|
|
142
|
+
):
|
|
143
|
+
"""Open a WebSocket connection and send the task.
|
|
144
|
+
|
|
145
|
+
Returns the **open** connection. The caller must eventually call
|
|
146
|
+
:func:`streaming_receive` (which closes the connection) or close it
|
|
147
|
+
manually.
|
|
148
|
+
|
|
149
|
+
Raises immediately on connection failure or send error.
|
|
150
|
+
"""
|
|
151
|
+
ws_url = _get_ws_url() + "/ws/submit"
|
|
152
|
+
metadata = _build_metadata(task_id, operation_type, flow_id, node_id, kwargs)
|
|
153
|
+
|
|
154
|
+
ws = connect(ws_url)
|
|
155
|
+
try:
|
|
156
|
+
ws.send(json.dumps(metadata))
|
|
157
|
+
ws.send(lf_bytes)
|
|
158
|
+
except Exception:
|
|
159
|
+
ws.close()
|
|
160
|
+
raise
|
|
161
|
+
return ws
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def streaming_receive(ws, task_id: str) -> tuple[Any, Status]:
|
|
165
|
+
"""Block until the worker sends back a result, then close the connection.
|
|
166
|
+
|
|
167
|
+
The returned ``Status`` object is fully populated (including
|
|
168
|
+
``results``) so it is equivalent to what the REST polling path
|
|
169
|
+
would produce.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Tuple of (result, Status)
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
raw_result, status = _receive_raw_result(ws, task_id)
|
|
176
|
+
finally:
|
|
177
|
+
ws.close()
|
|
178
|
+
|
|
179
|
+
if status is None:
|
|
180
|
+
status = Status(
|
|
181
|
+
background_task_id=task_id,
|
|
182
|
+
status="Completed",
|
|
183
|
+
file_ref="",
|
|
184
|
+
result_type="polars",
|
|
185
|
+
progress=100,
|
|
186
|
+
results=None,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return _deserialize_and_populate_status(raw_result, status)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def streaming_submit(
|
|
193
|
+
task_id: str,
|
|
194
|
+
operation_type: str,
|
|
195
|
+
flow_id: int,
|
|
196
|
+
node_id: int | str,
|
|
197
|
+
lf_bytes: bytes,
|
|
198
|
+
kwargs: dict | None = None,
|
|
199
|
+
) -> tuple[Any, Status]:
|
|
200
|
+
"""Submit a task via WebSocket and block until the result arrives.
|
|
201
|
+
|
|
202
|
+
Convenience wrapper around :func:`streaming_start` +
|
|
203
|
+
:func:`streaming_receive`.
|
|
204
|
+
"""
|
|
205
|
+
ws = streaming_start(task_id, operation_type, flow_id, node_id, lf_bytes, kwargs)
|
|
206
|
+
return streaming_receive(ws, task_id)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Standard library imports
|
|
2
2
|
import io
|
|
3
3
|
import threading
|
|
4
|
-
from base64 import
|
|
4
|
+
from base64 import b64decode
|
|
5
5
|
from time import sleep
|
|
6
6
|
from typing import Any, Literal
|
|
7
7
|
from uuid import uuid4
|
|
@@ -18,6 +18,11 @@ from flowfile_core.flowfile.flow_data_engine.subprocess_operations.models import
|
|
|
18
18
|
PolarsOperation,
|
|
19
19
|
Status,
|
|
20
20
|
)
|
|
21
|
+
from flowfile_core.flowfile.flow_data_engine.subprocess_operations.streaming import (
|
|
22
|
+
streaming_receive,
|
|
23
|
+
streaming_start,
|
|
24
|
+
streaming_submit,
|
|
25
|
+
)
|
|
21
26
|
from flowfile_core.flowfile.sources.external_sources.sql_source.models import (
|
|
22
27
|
DatabaseExternalReadSettings,
|
|
23
28
|
DatabaseExternalWriteSettings,
|
|
@@ -30,15 +35,15 @@ from flowfile_core.utils.arrow_reader import read
|
|
|
30
35
|
def trigger_df_operation(
|
|
31
36
|
flow_id: int, node_id: int | str, lf: pl.LazyFrame, file_ref: str, operation_type: OperationType = "store"
|
|
32
37
|
) -> Status:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
# Send raw bytes directly - no base64 encoding overhead
|
|
39
|
+
headers = {
|
|
40
|
+
"Content-Type": "application/octet-stream",
|
|
41
|
+
"X-Task-Id": file_ref,
|
|
42
|
+
"X-Operation-Type": operation_type,
|
|
43
|
+
"X-Flow-Id": str(flow_id),
|
|
44
|
+
"X-Node-Id": str(node_id),
|
|
40
45
|
}
|
|
41
|
-
v = requests.post(url=f"{WORKER_URL}/submit_query/",
|
|
46
|
+
v = requests.post(url=f"{WORKER_URL}/submit_query/", data=lf.serialize(), headers=headers)
|
|
42
47
|
if not v.ok:
|
|
43
48
|
raise Exception(f"trigger_df_operation: Could not cache the data, {v.text}")
|
|
44
49
|
return Status(**v.json())
|
|
@@ -47,16 +52,16 @@ def trigger_df_operation(
|
|
|
47
52
|
def trigger_sample_operation(
|
|
48
53
|
lf: pl.LazyFrame, file_ref: str, flow_id: int, node_id: str | int, sample_size: int = 100
|
|
49
54
|
) -> Status:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
55
|
+
# Send raw bytes directly - no base64 encoding overhead
|
|
56
|
+
headers = {
|
|
57
|
+
"Content-Type": "application/octet-stream",
|
|
58
|
+
"X-Task-Id": file_ref,
|
|
59
|
+
"X-Operation-Type": "store_sample",
|
|
60
|
+
"X-Sample-Size": str(sample_size),
|
|
61
|
+
"X-Flow-Id": str(flow_id),
|
|
62
|
+
"X-Node-Id": str(node_id),
|
|
58
63
|
}
|
|
59
|
-
v = requests.post(url=f"{WORKER_URL}/store_sample/",
|
|
64
|
+
v = requests.post(url=f"{WORKER_URL}/store_sample/", data=lf.serialize(), headers=headers)
|
|
60
65
|
if not v.ok:
|
|
61
66
|
raise Exception(f"trigger_sample_operation: Could not cache the data, {v.text}")
|
|
62
67
|
return Status(**v.json())
|
|
@@ -70,8 +75,9 @@ def trigger_fuzzy_match_operation(
|
|
|
70
75
|
flow_id: int,
|
|
71
76
|
node_id: int | str,
|
|
72
77
|
) -> Status:
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
# Use raw bytes - Pydantic will handle single base64 encoding for JSON transport
|
|
79
|
+
left_serializable_object = PolarsOperation(operation=left_df.serialize())
|
|
80
|
+
right_serializable_object = PolarsOperation(operation=right_df.serialize())
|
|
75
81
|
fuzzy_join_input = FuzzyJoinInput(
|
|
76
82
|
left_df_operation=left_serializable_object,
|
|
77
83
|
right_df_operation=right_serializable_object,
|
|
@@ -80,7 +86,6 @@ def trigger_fuzzy_match_operation(
|
|
|
80
86
|
flowfile_flow_id=flow_id,
|
|
81
87
|
flowfile_node_id=node_id,
|
|
82
88
|
)
|
|
83
|
-
print("fuzzy join input", fuzzy_join_input)
|
|
84
89
|
v = requests.post(f"{WORKER_URL}/add_fuzzy_join", data=fuzzy_join_input.model_dump_json())
|
|
85
90
|
if not v.ok:
|
|
86
91
|
raise Exception(f"trigger_fuzzy_match_operation: Could not cache the data, {v.text}")
|
|
@@ -137,6 +142,12 @@ def get_results(file_ref: str) -> Status | None:
|
|
|
137
142
|
|
|
138
143
|
|
|
139
144
|
def results_exists(file_ref: str):
|
|
145
|
+
from flowfile_core.configs.settings import OFFLOAD_TO_WORKER
|
|
146
|
+
|
|
147
|
+
# Skip worker check if worker communication is disabled
|
|
148
|
+
if not OFFLOAD_TO_WORKER:
|
|
149
|
+
return False
|
|
150
|
+
|
|
140
151
|
try:
|
|
141
152
|
f = requests.get(f"{WORKER_URL}/status/{file_ref}")
|
|
142
153
|
if f.status_code == 200:
|
|
@@ -159,6 +170,12 @@ def clear_task_from_worker(file_ref: str) -> bool:
|
|
|
159
170
|
Returns:
|
|
160
171
|
bool: True if the task was successfully cleared, False otherwise.
|
|
161
172
|
"""
|
|
173
|
+
from flowfile_core.configs.settings import OFFLOAD_TO_WORKER
|
|
174
|
+
|
|
175
|
+
# Skip worker call if worker communication is disabled
|
|
176
|
+
if not OFFLOAD_TO_WORKER:
|
|
177
|
+
return False
|
|
178
|
+
|
|
162
179
|
try:
|
|
163
180
|
f = requests.delete(f"{WORKER_URL}/clear_task/{file_ref}")
|
|
164
181
|
if f.status_code == 200:
|
|
@@ -169,9 +186,9 @@ def clear_task_from_worker(file_ref: str) -> bool:
|
|
|
169
186
|
return False
|
|
170
187
|
|
|
171
188
|
|
|
172
|
-
def get_df_result(
|
|
173
|
-
|
|
174
|
-
return pl.LazyFrame.deserialize(io.BytesIO(
|
|
189
|
+
def get_df_result(result_b64: str) -> pl.LazyFrame:
|
|
190
|
+
# Results are base64-encoded string from JSON response, decode once
|
|
191
|
+
return pl.LazyFrame.deserialize(io.BytesIO(b64decode(result_b64)))
|
|
175
192
|
|
|
176
193
|
|
|
177
194
|
def get_external_df_result(file_ref: str) -> pl.LazyFrame | None:
|
|
@@ -228,6 +245,9 @@ class BaseFetcher:
|
|
|
228
245
|
self._stop_event = threading.Event()
|
|
229
246
|
self._thread = None
|
|
230
247
|
|
|
248
|
+
# WebSocket connection for non-blocking streaming mode
|
|
249
|
+
self._ws = None
|
|
250
|
+
|
|
231
251
|
# State variables - use properties for thread-safe access
|
|
232
252
|
self._result: Any | None = None
|
|
233
253
|
self._started: bool = False
|
|
@@ -378,6 +398,16 @@ class BaseFetcher:
|
|
|
378
398
|
"""
|
|
379
399
|
logger.warning("Cancelling the operation")
|
|
380
400
|
|
|
401
|
+
# Close WebSocket if streaming (causes recv thread to exit)
|
|
402
|
+
with self._lock:
|
|
403
|
+
ws = self._ws
|
|
404
|
+
self._ws = None
|
|
405
|
+
if ws is not None:
|
|
406
|
+
try:
|
|
407
|
+
ws.close()
|
|
408
|
+
except Exception:
|
|
409
|
+
pass
|
|
410
|
+
|
|
381
411
|
# Cancel on the worker side
|
|
382
412
|
try:
|
|
383
413
|
cancel_task(self.file_ref)
|
|
@@ -439,6 +469,79 @@ class BaseFetcher:
|
|
|
439
469
|
with self._lock:
|
|
440
470
|
return self._error_code, self._error_description
|
|
441
471
|
|
|
472
|
+
def _execute_streaming(
|
|
473
|
+
self,
|
|
474
|
+
operation_type: str,
|
|
475
|
+
flow_id: int,
|
|
476
|
+
node_id: int | str,
|
|
477
|
+
lf_bytes: bytes,
|
|
478
|
+
kwargs: dict | None = None,
|
|
479
|
+
blocking: bool = True,
|
|
480
|
+
) -> None:
|
|
481
|
+
"""Execute via WebSocket streaming - no polling, binary result transfer.
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
blocking: If True (default), blocks until the result is available
|
|
485
|
+
and sets self._result directly. If False, opens the
|
|
486
|
+
connection, sends the task, and hands off to a background
|
|
487
|
+
thread that will set self._result when done.
|
|
488
|
+
|
|
489
|
+
Raises on connection or send error so the caller can fall back to REST.
|
|
490
|
+
"""
|
|
491
|
+
if blocking:
|
|
492
|
+
result, status = streaming_submit(
|
|
493
|
+
task_id=self.file_ref,
|
|
494
|
+
operation_type=operation_type,
|
|
495
|
+
flow_id=flow_id,
|
|
496
|
+
node_id=node_id,
|
|
497
|
+
lf_bytes=lf_bytes,
|
|
498
|
+
kwargs=kwargs,
|
|
499
|
+
)
|
|
500
|
+
with self._lock:
|
|
501
|
+
self._result = result
|
|
502
|
+
self._running = False
|
|
503
|
+
self._started = True
|
|
504
|
+
self.status = status
|
|
505
|
+
else:
|
|
506
|
+
# Non-blocking: open connection, send task, receive in background
|
|
507
|
+
ws = streaming_start(
|
|
508
|
+
task_id=self.file_ref,
|
|
509
|
+
operation_type=operation_type,
|
|
510
|
+
flow_id=flow_id,
|
|
511
|
+
node_id=node_id,
|
|
512
|
+
lf_bytes=lf_bytes,
|
|
513
|
+
kwargs=kwargs,
|
|
514
|
+
)
|
|
515
|
+
with self._lock:
|
|
516
|
+
self._ws = ws
|
|
517
|
+
self._running = True
|
|
518
|
+
self._started = True
|
|
519
|
+
self._thread = threading.Thread(
|
|
520
|
+
target=self._ws_receive_thread,
|
|
521
|
+
args=(ws,),
|
|
522
|
+
daemon=True,
|
|
523
|
+
)
|
|
524
|
+
self._thread.start()
|
|
525
|
+
|
|
526
|
+
def _ws_receive_thread(self, ws) -> None:
|
|
527
|
+
"""Background thread that receives results over an open WebSocket."""
|
|
528
|
+
try:
|
|
529
|
+
result, status = streaming_receive(ws, self.file_ref)
|
|
530
|
+
with self._condition:
|
|
531
|
+
self._result = result
|
|
532
|
+
self._running = False
|
|
533
|
+
self._ws = None
|
|
534
|
+
self._condition.notify_all()
|
|
535
|
+
self.status = status
|
|
536
|
+
except Exception as e:
|
|
537
|
+
logger.exception("Error in WebSocket receive thread")
|
|
538
|
+
with self._condition:
|
|
539
|
+
self._error_code = -1
|
|
540
|
+
self._error_description = str(e)
|
|
541
|
+
self._running = False
|
|
542
|
+
self._ws = None
|
|
543
|
+
self._condition.notify_all()
|
|
544
|
+
|
|
442
545
|
|
|
443
546
|
class ExternalDfFetcher(BaseFetcher):
|
|
444
547
|
status: Status | None = None
|
|
@@ -455,6 +558,21 @@ class ExternalDfFetcher(BaseFetcher):
|
|
|
455
558
|
):
|
|
456
559
|
super().__init__(file_ref=file_ref)
|
|
457
560
|
lf = lf.lazy() if isinstance(lf, pl.DataFrame) else lf
|
|
561
|
+
|
|
562
|
+
# Try WebSocket streaming first (blocking or non-blocking)
|
|
563
|
+
try:
|
|
564
|
+
self._execute_streaming(
|
|
565
|
+
operation_type=operation_type,
|
|
566
|
+
flow_id=flow_id,
|
|
567
|
+
node_id=node_id,
|
|
568
|
+
lf_bytes=lf.serialize(),
|
|
569
|
+
blocking=wait_on_completion,
|
|
570
|
+
)
|
|
571
|
+
return
|
|
572
|
+
except Exception as e:
|
|
573
|
+
logger.debug(f"WebSocket streaming unavailable ({e}), falling back to REST")
|
|
574
|
+
|
|
575
|
+
# REST fallback (original behavior)
|
|
458
576
|
r = trigger_df_operation(
|
|
459
577
|
lf=lf, file_ref=self.file_ref, operation_type=operation_type, node_id=node_id, flow_id=flow_id
|
|
460
578
|
)
|
|
@@ -478,6 +596,22 @@ class ExternalSampler(BaseFetcher):
|
|
|
478
596
|
):
|
|
479
597
|
super().__init__(file_ref=file_ref)
|
|
480
598
|
lf = lf.lazy() if isinstance(lf, pl.DataFrame) else lf
|
|
599
|
+
|
|
600
|
+
# Try WebSocket streaming first (blocking or non-blocking)
|
|
601
|
+
try:
|
|
602
|
+
self._execute_streaming(
|
|
603
|
+
operation_type="store_sample",
|
|
604
|
+
flow_id=flow_id,
|
|
605
|
+
node_id=node_id,
|
|
606
|
+
lf_bytes=lf.serialize(),
|
|
607
|
+
kwargs={"sample_size": sample_size},
|
|
608
|
+
blocking=wait_on_completion,
|
|
609
|
+
)
|
|
610
|
+
return
|
|
611
|
+
except Exception as e:
|
|
612
|
+
logger.debug(f"WebSocket streaming unavailable ({e}), falling back to REST")
|
|
613
|
+
|
|
614
|
+
# REST fallback (original behavior)
|
|
481
615
|
r = trigger_sample_operation(
|
|
482
616
|
lf=lf, file_ref=file_ref, sample_size=sample_size, node_id=node_id, flow_id=flow_id
|
|
483
617
|
)
|