Flowfile 0.5.1__py3-none-any.whl → 0.5.4__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.
- build_backends/main.py +25 -22
- build_backends/main_prd.py +10 -19
- flowfile/__init__.py +194 -74
- flowfile/__main__.py +10 -7
- flowfile/api.py +51 -57
- flowfile/web/__init__.py +14 -9
- flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
- flowfile/web/static/assets/AdminView-f9847d67.js +713 -0
- flowfile/web/static/assets/CloudConnectionView-cf85f943.css +72 -0
- flowfile/web/static/assets/{CloudConnectionManager-0dfba9f2.js → CloudConnectionView-faace55b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
- flowfile/web/static/assets/{CloudStorageReader-d5b1b6c9.js → CloudStorageReader-d86ecaa7.js} +10 -8
- flowfile/web/static/assets/{CloudStorageWriter-00d87aad.js → CloudStorageWriter-0f4d9a44.js} +10 -8
- flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
- flowfile/web/static/assets/ColumnActionInput-c44b7aee.css +159 -0
- flowfile/web/static/assets/ColumnActionInput-f4189ae0.js +330 -0
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-4685e75d.js → ColumnSelector-e66b33da.js} +3 -5
- flowfile/web/static/assets/ContextMenu-49463352.js +9 -0
- flowfile/web/static/assets/ContextMenu-dd5f3f25.js +9 -0
- flowfile/web/static/assets/ContextMenu-f709b884.js +9 -0
- flowfile/web/static/assets/ContextMenu.vue_vue_type_script_setup_true_lang-a1bd6314.js +59 -0
- flowfile/web/static/assets/{CrossJoin-702a3edd.js → CrossJoin-24694b8f.js} +12 -10
- flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
- flowfile/web/static/assets/{CustomNode-b1519993.js → CustomNode-569d45ff.js} +43 -24
- flowfile/web/static/assets/CustomNode-edb9b939.css +42 -0
- flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-c20a1e16.css} +23 -21
- flowfile/web/static/assets/{DatabaseConnectionSettings-6f3e4ea5.js → DatabaseConnectionSettings-cfc08938.js} +5 -4
- flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-5bf8c75b.css} +41 -46
- flowfile/web/static/assets/{DatabaseReader-d38c7295.js → DatabaseReader-701feabb.js} +25 -15
- flowfile/web/static/assets/{DatabaseManager-cf5ef661.js → DatabaseView-0482e5b5.js} +11 -11
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseWriter-b04ef46a.js → DatabaseWriter-16721989.js} +17 -10
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-bdcf2c8b.css} +29 -27
- flowfile/web/static/assets/{designer-8da3ba3a.css → DesignerView-49abb835.css} +783 -663
- flowfile/web/static/assets/{designer-9633482a.js → DesignerView-f64749fb.js} +1292 -3253
- flowfile/web/static/assets/{documentation-ca400224.js → DocumentationView-61bd2990.js} +5 -5
- flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-9ea6e871.css} +9 -9
- flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
- flowfile/web/static/assets/{ExploreData-5fa10ed8.js → ExploreData-e2735b13.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-d39af878.js → ExternalSource-2535c3b2.js} +9 -7
- flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-7ac7373f.css} +20 -20
- flowfile/web/static/assets/Filter-2cdbc93c.js +287 -0
- flowfile/web/static/assets/Filter-7494ea97.css +48 -0
- flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
- flowfile/web/static/assets/{Formula-6b04fb1d.js → Formula-fcda3c2c.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-999521f4.js → FuzzyMatch-f8d3b7d3.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-4b4d7db9.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-17dd2198.js → GraphSolver-72eaa695.js} +14 -12
- flowfile/web/static/assets/GroupBy-5792782d.css +9 -0
- flowfile/web/static/assets/{GroupBy-6b039e18.js → GroupBy-8aa0598b.js} +9 -7
- flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
- flowfile/web/static/assets/{Join-24d0f113.js → Join-e40f0ffa.js} +13 -11
- flowfile/web/static/assets/LoginView-5111c9ae.js +134 -0
- flowfile/web/static/assets/LoginView-d325d632.css +172 -0
- flowfile/web/static/assets/ManualInput-3702e677.css +293 -0
- flowfile/web/static/assets/{ManualInput-34639209.js → ManualInput-9b6f3224.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-0e8724a3.js → MultiSelect-ef28e19e.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-b0e538c2.js → MultiSelect.vue_vue_type_script_setup_true_lang-83b3bbfd.js} +1 -1
- flowfile/web/static/assets/NodeDesigner-94cd4dd3.css +1429 -0
- flowfile/web/static/assets/NodeDesigner-d2b7ee2b.js +2712 -0
- flowfile/web/static/assets/{NumericInput-3d63a470.js → NumericInput-1d789794.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-e0edeccc.js → NumericInput.vue_vue_type_script_setup_true_lang-7775f83e.js} +5 -2
- flowfile/web/static/assets/Output-692dd25d.css +37 -0
- flowfile/web/static/assets/{Output-edea9802.js → Output-cefef801.js} +14 -10
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-61d19301.js → Pivot-bab1b75b.js} +12 -10
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f97fec5b.js → PivotValidation-e7941f91.js} +3 -3
- flowfile/web/static/assets/{PivotValidation-de9f43fe.js → PivotValidation-fba09336.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-bc3c9984.js → PolarsCode-740e40fa.js} +18 -9
- flowfile/web/static/assets/PopOver-862d7e28.js +939 -0
- flowfile/web/static/assets/PopOver-d96599db.css +33 -0
- flowfile/web/static/assets/{Read-64a3f259.js → Read-225cc63f.js} +16 -12
- flowfile/web/static/assets/{Read-e808b239.css → Read-90f366bc.css} +15 -15
- flowfile/web/static/assets/{RecordCount-3d5039be.js → RecordCount-ffc71eca.js} +6 -4
- flowfile/web/static/assets/{RecordId-597510e0.js → RecordId-a70bb8df.js} +9 -7
- flowfile/web/static/assets/{SQLQueryComponent-df51adbe.js → SQLQueryComponent-15a421f5.js} +3 -3
- flowfile/web/static/assets/SQLQueryComponent-edb90b98.css +29 -0
- flowfile/web/static/assets/{Sample-4be0a507.js → Sample-6c26afc7.js} +6 -4
- flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
- flowfile/web/static/assets/SecretSelector-ceed9496.js +113 -0
- flowfile/web/static/assets/{SecretManager-4839be57.js → SecretsView-214d255a.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-9b72f201.js → Select-8fc29999.js} +9 -7
- flowfile/web/static/assets/{SettingsSection-71e6b7e3.css → SettingsSection-07fbbc39.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-5c696bee.css → SettingsSection-26fe48d4.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-7ded385d.js → SettingsSection-3f70e4c3.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-f0f75a42.js → SettingsSection-83090218.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-e1e9c953.js → SettingsSection-9f0d1725.js} +3 -3
- flowfile/web/static/assets/SetupView-3fa0aa03.js +160 -0
- flowfile/web/static/assets/SetupView-e2da3442.css +230 -0
- flowfile/web/static/assets/{SingleSelect-6c777aac.js → SingleSelect-a4a568cb.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-33e3ff9b.js → SingleSelect.vue_vue_type_script_setup_true_lang-c8ebdd33.js} +1 -1
- flowfile/web/static/assets/{SliderInput-7cb93e62.js → SliderInput-be533e71.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-6cbde21a.js → Sort-154dad81.js} +9 -7
- flowfile/web/static/assets/Sort-4abb7fae.css +9 -0
- flowfile/web/static/assets/{TextInput-d9a40c11.js → TextInput-454e2bda.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-5896c375.js → TextInput.vue_vue_type_script_setup_true_lang-e86510d0.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-c4fcbf4d.js → TextToRows-ea73433d.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-4ef91d19.js → ToggleSwitch-9d7b30f1.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-38478c20.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-00f2580e.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-394a1f78.css} +14 -14
- flowfile/web/static/assets/{UnavailableFields-a03f512c.js → UnavailableFields-b72a2c72.js} +4 -4
- flowfile/web/static/assets/{Union-bfe9b996.js → Union-1e44f263.js} +8 -6
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/Unique-2b705521.css +3 -0
- flowfile/web/static/assets/{Unique-5d023a27.js → Unique-a3bc6d0a.js} +13 -10
- flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-b6ad6427.css} +7 -7
- flowfile/web/static/assets/{Unpivot-91cc5354.js → Unpivot-e27935fc.js} +11 -9
- flowfile/web/static/assets/{UnpivotValidation-7ee2de44.js → UnpivotValidation-72497680.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{VueGraphicWalker-e51b9924.js → VueGraphicWalker-d9ab70a3.js} +4 -4
- flowfile/web/static/assets/{api-cf1221f0.js → api-a2102880.js} +1 -1
- flowfile/web/static/assets/{api-c1bad5ca.js → api-f75042b0.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-1d6acbd9.css} +41 -41
- flowfile/web/static/assets/{dropDown-614b998d.js → dropDown-2798a109.js} +3 -3
- flowfile/web/static/assets/{fullEditor-f7971590.js → fullEditor-cf7d7d93.js} +11 -10
- flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-fe9f7e18.css} +77 -65
- flowfile/web/static/assets/{genericNodeSettings-4fe5f36b.js → genericNodeSettings-14eac1c3.js} +5 -5
- flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
- flowfile/web/static/assets/{index-5429bbf8.js → index-387a6f18.js} +41806 -40958
- flowfile/web/static/assets/index-6b367bb5.js +38 -0
- flowfile/web/static/assets/{index-50508d4d.css → index-e96ab018.css} +2184 -569
- flowfile/web/static/assets/index-f0a6e5a5.js +2696 -0
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-ed2ae8d7.js +2 -0
- flowfile/web/static/assets/{outputCsv-076b85ab.js → outputCsv-3c1757e8.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-0fd17dbe.js → outputExcel-686e1f48.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{outputParquet-b61e0847.js → outputParquet-df28faa7.js} +4 -4
- flowfile/web/static/assets/{readCsv-c767cb37.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readCsv-a8bb8b61.js → readCsv-e37eee21.js} +3 -3
- flowfile/web/static/assets/{readExcel-806d2826.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-67b4aee0.js → readExcel-a13f14bb.js} +5 -5
- flowfile/web/static/assets/{readParquet-92ce1dbc.js → readParquet-344cf746.js} +3 -3
- flowfile/web/static/assets/{readParquet-48c81530.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/secrets.api-ae198c5c.js +65 -0
- flowfile/web/static/assets/{selectDynamic-92e25ee3.js → selectDynamic-6b4b0767.js} +5 -5
- flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
- flowfile/web/static/assets/{vue-codemirror.esm-41b0e0d7.js → vue-codemirror.esm-31ba0e0b.js} +31 -640
- flowfile/web/static/assets/{vue-content-loader.es-2c8e608f.js → vue-content-loader.es-4469c8ff.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/METADATA +3 -4
- flowfile-0.5.4.dist-info/RECORD +407 -0
- flowfile_core/__init__.py +13 -6
- flowfile_core/auth/jwt.py +51 -16
- flowfile_core/auth/models.py +32 -7
- flowfile_core/auth/password.py +89 -0
- flowfile_core/auth/secrets.py +64 -19
- flowfile_core/configs/__init__.py +9 -7
- flowfile_core/configs/flow_logger.py +15 -14
- flowfile_core/configs/node_store/__init__.py +72 -4
- flowfile_core/configs/node_store/nodes.py +155 -172
- flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
- flowfile_core/configs/settings.py +28 -15
- flowfile_core/database/connection.py +7 -6
- flowfile_core/database/init_db.py +96 -2
- flowfile_core/database/models.py +3 -1
- flowfile_core/fileExplorer/__init__.py +17 -0
- flowfile_core/fileExplorer/funcs.py +145 -57
- flowfile_core/fileExplorer/utils.py +10 -11
- flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
- flowfile_core/flowfile/analytics/analytics_processor.py +26 -24
- flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
- flowfile_core/flowfile/analytics/utils.py +1 -1
- flowfile_core/flowfile/code_generator/__init__.py +11 -0
- flowfile_core/flowfile/code_generator/code_generator.py +706 -247
- flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
- flowfile_core/flowfile/connection_manager/models.py +1 -1
- flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
- flowfile_core/flowfile/database_connection_manager/models.py +1 -1
- flowfile_core/flowfile/extensions.py +17 -12
- flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
- flowfile_core/flowfile/flow_data_engine/create/funcs.py +115 -83
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +493 -423
- flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
- flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +31 -20
- flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
- flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +14 -15
- flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
- flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
- flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
- flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +190 -127
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
- flowfile_core/flowfile/flow_data_engine/utils.py +99 -67
- flowfile_core/flowfile/flow_graph.py +920 -571
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +379 -258
- flowfile_core/flowfile/flow_node/models.py +53 -41
- flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
- flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
- flowfile_core/flowfile/handler.py +80 -30
- flowfile_core/flowfile/manage/compatibility_enhancements.py +209 -126
- flowfile_core/flowfile/manage/io_flowfile.py +54 -57
- flowfile_core/flowfile/node_designer/__init__.py +19 -13
- flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
- flowfile_core/flowfile/node_designer/custom_node.py +162 -36
- flowfile_core/flowfile/node_designer/ui_components.py +278 -34
- flowfile_core/flowfile/schema_callbacks.py +71 -51
- flowfile_core/flowfile/setting_generator/__init__.py +0 -1
- flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
- flowfile_core/flowfile/setting_generator/settings.py +64 -53
- flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
- flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
- flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
- flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
- flowfile_core/flowfile/util/calculate_layout.py +9 -13
- flowfile_core/flowfile/util/execution_orderer.py +25 -17
- flowfile_core/flowfile/util/node_skipper.py +4 -4
- flowfile_core/flowfile/utils.py +19 -21
- flowfile_core/main.py +26 -19
- flowfile_core/routes/auth.py +284 -11
- flowfile_core/routes/cloud_connections.py +25 -25
- flowfile_core/routes/logs.py +21 -29
- flowfile_core/routes/public.py +46 -4
- flowfile_core/routes/routes.py +70 -34
- flowfile_core/routes/secrets.py +25 -27
- flowfile_core/routes/user_defined_components.py +483 -4
- flowfile_core/run_lock.py +0 -1
- flowfile_core/schemas/__init__.py +4 -6
- flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
- flowfile_core/schemas/cloud_storage_schemas.py +96 -66
- flowfile_core/schemas/input_schema.py +231 -144
- flowfile_core/schemas/output_model.py +49 -34
- flowfile_core/schemas/schemas.py +116 -89
- flowfile_core/schemas/transform_schema.py +518 -263
- flowfile_core/schemas/yaml_types.py +21 -7
- flowfile_core/secret_manager/secret_manager.py +123 -18
- flowfile_core/types.py +29 -9
- flowfile_core/utils/arrow_reader.py +7 -6
- flowfile_core/utils/excel_file_manager.py +3 -3
- flowfile_core/utils/fileManager.py +7 -7
- flowfile_core/utils/fl_executor.py +8 -10
- flowfile_core/utils/utils.py +4 -4
- flowfile_core/utils/validate_setup.py +5 -4
- flowfile_frame/__init__.py +117 -51
- flowfile_frame/adapters.py +2 -9
- flowfile_frame/adding_expr.py +73 -32
- flowfile_frame/cloud_storage/frame_helpers.py +27 -23
- flowfile_frame/cloud_storage/secret_manager.py +12 -26
- flowfile_frame/config.py +2 -5
- flowfile_frame/database/__init__.py +36 -0
- flowfile_frame/database/connection_manager.py +205 -0
- flowfile_frame/database/frame_helpers.py +249 -0
- flowfile_frame/expr.py +311 -218
- flowfile_frame/expr.pyi +160 -159
- flowfile_frame/expr_name.py +23 -23
- flowfile_frame/flow_frame.py +571 -476
- flowfile_frame/flow_frame.pyi +123 -104
- flowfile_frame/flow_frame_methods.py +227 -246
- flowfile_frame/group_frame.py +50 -20
- flowfile_frame/join.py +2 -2
- flowfile_frame/lazy.py +129 -87
- flowfile_frame/lazy_methods.py +83 -30
- flowfile_frame/list_name_space.py +55 -50
- flowfile_frame/selectors.py +148 -68
- flowfile_frame/series.py +9 -7
- flowfile_frame/utils.py +19 -21
- flowfile_worker/__init__.py +12 -7
- flowfile_worker/configs.py +41 -33
- flowfile_worker/create/__init__.py +14 -9
- flowfile_worker/create/funcs.py +114 -77
- flowfile_worker/create/models.py +46 -43
- flowfile_worker/create/pl_types.py +14 -15
- flowfile_worker/create/read_excel_tables.py +34 -41
- flowfile_worker/create/utils.py +22 -19
- flowfile_worker/external_sources/s3_source/main.py +18 -51
- flowfile_worker/external_sources/s3_source/models.py +34 -27
- flowfile_worker/external_sources/sql_source/main.py +8 -5
- flowfile_worker/external_sources/sql_source/models.py +13 -9
- flowfile_worker/flow_logger.py +10 -8
- flowfile_worker/funcs.py +214 -155
- flowfile_worker/main.py +11 -17
- flowfile_worker/models.py +35 -28
- flowfile_worker/process_manager.py +2 -3
- flowfile_worker/routes.py +121 -90
- flowfile_worker/secrets.py +114 -21
- flowfile_worker/spawner.py +89 -54
- flowfile_worker/utils.py +3 -2
- shared/__init__.py +2 -7
- shared/storage_config.py +25 -13
- test_utils/postgres/commands.py +3 -2
- test_utils/postgres/fixtures.py +9 -9
- test_utils/s3/commands.py +1 -1
- test_utils/s3/data_generator.py +3 -4
- test_utils/s3/demo_data_generator.py +4 -7
- test_utils/s3/fixtures.py +7 -5
- tools/migrate/__init__.py +1 -1
- tools/migrate/__main__.py +16 -29
- tools/migrate/legacy_schemas.py +251 -190
- tools/migrate/migrate.py +193 -181
- tools/migrate/tests/conftest.py +1 -3
- tools/migrate/tests/test_migrate.py +36 -41
- tools/migrate/tests/test_migration_e2e.py +28 -29
- tools/migrate/tests/test_node_migrations.py +50 -20
- flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
- flowfile/web/static/assets/ContextMenu-23e909da.js +0 -41
- flowfile/web/static/assets/ContextMenu-4c74eef1.css +0 -26
- flowfile/web/static/assets/ContextMenu-63cfa99b.css +0 -26
- flowfile/web/static/assets/ContextMenu-70ae0c79.js +0 -41
- flowfile/web/static/assets/ContextMenu-c13f91d0.css +0 -26
- flowfile/web/static/assets/ContextMenu-f149cf7c.js +0 -41
- flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
- flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
- flowfile/web/static/assets/Filter-9b6d08db.js +0 -164
- flowfile/web/static/assets/Filter-f62091b3.css +0 -20
- flowfile/web/static/assets/GroupBy-b9505323.css +0 -51
- flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
- flowfile/web/static/assets/Output-283fe388.css +0 -37
- flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
- flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
- flowfile/web/static/assets/SQLQueryComponent-36cef432.css +0 -27
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
- flowfile/web/static/assets/Sort-3643d625.css +0 -51
- flowfile/web/static/assets/Unique-f9fb0809.css +0 -51
- flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
- flowfile/web/static/assets/nodeInput-5d0d6b79.js +0 -41
- flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
- flowfile/web/static/assets/secretApi-68435402.js +0 -46
- flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
- flowfile-0.5.1.dist-info/RECORD +0 -388
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/WHEEL +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,22 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
from flowfile_core.flowfile.flow_data_engine.flow_file_column.main import FlowfileColumn
|
|
4
|
-
from flowfile_core.flowfile.flow_data_engine.flow_data_engine import FlowDataEngine
|
|
5
|
-
from flowfile_core.utils.arrow_reader import get_read_top_n
|
|
6
|
-
from flowfile_core.schemas import input_schema, schemas
|
|
7
|
-
from flowfile_core.configs.flow_logger import NodeLogger
|
|
8
|
-
|
|
9
|
-
from flowfile_core.schemas.output_model import TableExample, FileColumn, NodeData
|
|
10
|
-
from flowfile_core.flowfile.utils import get_hash
|
|
11
|
-
from flowfile_core.configs import node_store
|
|
12
|
-
from flowfile_core.flowfile.setting_generator import setting_generator, setting_updator
|
|
1
|
+
import threading
|
|
2
|
+
from collections.abc import Callable, Generator
|
|
13
3
|
from time import sleep
|
|
4
|
+
from typing import Any, Literal, Optional
|
|
5
|
+
|
|
6
|
+
from flowfile_core.configs import logger, node_store
|
|
7
|
+
from flowfile_core.configs.flow_logger import NodeLogger
|
|
8
|
+
from flowfile_core.flowfile.flow_data_engine.flow_data_engine import FlowDataEngine
|
|
9
|
+
from flowfile_core.flowfile.flow_data_engine.flow_file_column.main import FlowfileColumn
|
|
14
10
|
from flowfile_core.flowfile.flow_data_engine.subprocess_operations import (
|
|
15
|
-
|
|
16
|
-
ExternalDatabaseFetcher,
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
ExternalCloudWriter,
|
|
12
|
+
ExternalDatabaseFetcher,
|
|
13
|
+
ExternalDatabaseWriter,
|
|
14
|
+
ExternalDfFetcher,
|
|
15
|
+
ExternalSampler,
|
|
16
|
+
clear_task_from_worker,
|
|
17
|
+
get_external_df_result,
|
|
18
|
+
results_exists,
|
|
19
|
+
)
|
|
20
|
+
from flowfile_core.flowfile.flow_node.models import (
|
|
21
|
+
NodeResults,
|
|
22
|
+
NodeSchemaInformation,
|
|
23
|
+
NodeStepInputs,
|
|
24
|
+
NodeStepSettings,
|
|
25
|
+
NodeStepStats,
|
|
26
|
+
)
|
|
19
27
|
from flowfile_core.flowfile.flow_node.schema_callback import SingleExecutionFuture
|
|
28
|
+
from flowfile_core.flowfile.setting_generator import setting_generator, setting_updator
|
|
29
|
+
from flowfile_core.flowfile.utils import get_hash
|
|
30
|
+
from flowfile_core.schemas import input_schema, schemas
|
|
31
|
+
from flowfile_core.schemas.output_model import FileColumn, NodeData, TableExample
|
|
32
|
+
from flowfile_core.utils.arrow_reader import get_read_top_n
|
|
20
33
|
|
|
21
34
|
|
|
22
35
|
class FlowNode:
|
|
@@ -25,6 +38,7 @@ class FlowNode:
|
|
|
25
38
|
This class manages the node's state, its data processing function,
|
|
26
39
|
and its connections to other nodes within the graph.
|
|
27
40
|
"""
|
|
41
|
+
|
|
28
42
|
parent_uuid: str
|
|
29
43
|
node_type: str
|
|
30
44
|
node_template: node_store.NodeTemplate
|
|
@@ -34,31 +48,38 @@ class FlowNode:
|
|
|
34
48
|
node_stats: NodeStepStats
|
|
35
49
|
node_settings: NodeStepSettings
|
|
36
50
|
results: NodeResults
|
|
37
|
-
node_information:
|
|
38
|
-
leads_to_nodes:
|
|
39
|
-
user_provided_schema_callback:
|
|
51
|
+
node_information: schemas.NodeInformation | None = None
|
|
52
|
+
leads_to_nodes: list["FlowNode"] = [] # list with target flows, after execution the step will trigger those step(s)
|
|
53
|
+
user_provided_schema_callback: Callable | None = None # user provided callback function for schema calculation
|
|
40
54
|
_setting_input: Any = None
|
|
41
|
-
_hash:
|
|
55
|
+
_hash: str | None = None # host this for caching results
|
|
42
56
|
_function: Callable = None # the function that needs to be executed when triggered
|
|
43
57
|
_name: str = None # name of the node, used for display
|
|
44
|
-
_schema_callback:
|
|
58
|
+
_schema_callback: SingleExecutionFuture | None = None # Function that calculates the schema without executing
|
|
45
59
|
_state_needs_reset: bool = False
|
|
46
|
-
_fetch_cached_df:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
_fetch_cached_df: (
|
|
61
|
+
ExternalDfFetcher | ExternalDatabaseFetcher | ExternalDatabaseWriter | ExternalCloudWriter | None
|
|
62
|
+
) = None
|
|
63
|
+
_cache_progress: (
|
|
64
|
+
ExternalDfFetcher | ExternalDatabaseFetcher | ExternalDatabaseWriter | ExternalCloudWriter | None
|
|
65
|
+
) = None
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
node_id: str | int,
|
|
70
|
+
function: Callable,
|
|
71
|
+
parent_uuid: str,
|
|
72
|
+
setting_input: Any,
|
|
73
|
+
name: str,
|
|
74
|
+
node_type: str,
|
|
75
|
+
input_columns: list[str] = None,
|
|
76
|
+
output_schema: list[FlowfileColumn] = None,
|
|
77
|
+
drop_columns: list[str] = None,
|
|
78
|
+
renew_schema: bool = True,
|
|
79
|
+
pos_x: float = 0,
|
|
80
|
+
pos_y: float = 0,
|
|
81
|
+
schema_callback: Callable = None,
|
|
82
|
+
):
|
|
62
83
|
"""Initializes a FlowNode instance.
|
|
63
84
|
|
|
64
85
|
Args:
|
|
@@ -83,16 +104,17 @@ class FlowNode:
|
|
|
83
104
|
self.node_information.id = node_id
|
|
84
105
|
self.node_type = node_type
|
|
85
106
|
self.node_settings.renew_schema = renew_schema
|
|
86
|
-
self.update_node(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
self.update_node(
|
|
108
|
+
function=function,
|
|
109
|
+
input_columns=input_columns,
|
|
110
|
+
output_schema=output_schema,
|
|
111
|
+
drop_columns=drop_columns,
|
|
112
|
+
setting_input=setting_input,
|
|
113
|
+
name=name,
|
|
114
|
+
pos_x=pos_x,
|
|
115
|
+
pos_y=pos_y,
|
|
116
|
+
schema_callback=schema_callback,
|
|
117
|
+
)
|
|
96
118
|
|
|
97
119
|
def post_init(self):
|
|
98
120
|
"""Initializes or resets the node's attributes to their default states."""
|
|
@@ -107,6 +129,7 @@ class FlowNode:
|
|
|
107
129
|
self._cache_progress = None
|
|
108
130
|
self._schema_callback = None
|
|
109
131
|
self._state_needs_reset = False
|
|
132
|
+
self._execution_lock = threading.RLock() # Protects concurrent access to get_resulting_data
|
|
110
133
|
|
|
111
134
|
@property
|
|
112
135
|
def state_needs_reset(self) -> bool:
|
|
@@ -126,23 +149,27 @@ class FlowNode:
|
|
|
126
149
|
"""
|
|
127
150
|
self._state_needs_reset = v
|
|
128
151
|
|
|
129
|
-
|
|
130
|
-
def create_schema_callback_from_function(f: Callable) -> Callable[[], List[FlowfileColumn]]:
|
|
152
|
+
def create_schema_callback_from_function(self, f: Callable) -> Callable[[], list[FlowfileColumn]]:
|
|
131
153
|
"""Wraps a node's function to create a schema callback that extracts the schema.
|
|
132
154
|
|
|
155
|
+
Thread-safe: uses _execution_lock to prevent concurrent execution with get_resulting_data.
|
|
156
|
+
|
|
133
157
|
Args:
|
|
134
158
|
f: The node's core function that returns a FlowDataEngine instance.
|
|
135
159
|
|
|
136
160
|
Returns:
|
|
137
161
|
A callable that, when executed, returns the output schema.
|
|
138
162
|
"""
|
|
139
|
-
|
|
163
|
+
|
|
164
|
+
def schema_callback() -> list[FlowfileColumn]:
|
|
140
165
|
try:
|
|
141
|
-
logger.info(
|
|
142
|
-
|
|
166
|
+
logger.info("Executing the schema callback function based on the node function")
|
|
167
|
+
with self._execution_lock:
|
|
168
|
+
return f().schema
|
|
143
169
|
except Exception as e:
|
|
144
|
-
logger.warning(f
|
|
170
|
+
logger.warning(f"Error with the schema callback: {e}")
|
|
145
171
|
return []
|
|
172
|
+
|
|
146
173
|
return schema_callback
|
|
147
174
|
|
|
148
175
|
@property
|
|
@@ -171,7 +198,7 @@ class FlowNode:
|
|
|
171
198
|
if f is None:
|
|
172
199
|
return
|
|
173
200
|
|
|
174
|
-
def error_callback(e: Exception) ->
|
|
201
|
+
def error_callback(e: Exception) -> list:
|
|
175
202
|
logger.warning(e)
|
|
176
203
|
|
|
177
204
|
self.node_settings.setup_errors = True
|
|
@@ -190,7 +217,7 @@ class FlowNode:
|
|
|
190
217
|
"""
|
|
191
218
|
return not self.has_input and self.node_template.input == 0
|
|
192
219
|
|
|
193
|
-
def get_input_type(self, node_id: int) ->
|
|
220
|
+
def get_input_type(self, node_id: int) -> list:
|
|
194
221
|
"""Gets the type of connection ('main', 'left', 'right') for a given input node ID.
|
|
195
222
|
|
|
196
223
|
Args:
|
|
@@ -201,24 +228,25 @@ class FlowNode:
|
|
|
201
228
|
"""
|
|
202
229
|
relation_type = []
|
|
203
230
|
if node_id in [n.node_id for n in self.node_inputs.main_inputs]:
|
|
204
|
-
relation_type.append(
|
|
231
|
+
relation_type.append("main")
|
|
205
232
|
if self.node_inputs.left_input is not None and node_id == self.node_inputs.left_input.node_id:
|
|
206
|
-
relation_type.append(
|
|
233
|
+
relation_type.append("left")
|
|
207
234
|
if self.node_inputs.right_input is not None and node_id == self.node_inputs.right_input.node_id:
|
|
208
|
-
relation_type.append(
|
|
235
|
+
relation_type.append("right")
|
|
209
236
|
return list(set(relation_type))
|
|
210
237
|
|
|
211
|
-
def update_node(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
238
|
+
def update_node(
|
|
239
|
+
self,
|
|
240
|
+
function: Callable,
|
|
241
|
+
input_columns: list[str] = None,
|
|
242
|
+
output_schema: list[FlowfileColumn] = None,
|
|
243
|
+
drop_columns: list[str] = None,
|
|
244
|
+
name: str = None,
|
|
245
|
+
setting_input: Any = None,
|
|
246
|
+
pos_x: float = 0,
|
|
247
|
+
pos_y: float = 0,
|
|
248
|
+
schema_callback: Callable = None,
|
|
249
|
+
):
|
|
222
250
|
"""Updates the properties of the node.
|
|
223
251
|
|
|
224
252
|
This is called during initialization and when settings are changed.
|
|
@@ -245,7 +273,7 @@ class FlowNode:
|
|
|
245
273
|
self.node_schema.output_columns = [] if output_schema is None else output_schema
|
|
246
274
|
self.node_schema.drop_columns = [] if drop_columns is None else drop_columns
|
|
247
275
|
self.node_settings.renew_schema = True
|
|
248
|
-
if hasattr(setting_input,
|
|
276
|
+
if hasattr(setting_input, "cache_results"):
|
|
249
277
|
self.node_settings.cache_results = setting_input.cache_results
|
|
250
278
|
|
|
251
279
|
self.results.errors = None
|
|
@@ -253,7 +281,7 @@ class FlowNode:
|
|
|
253
281
|
_ = self.hash
|
|
254
282
|
self.node_template = node_store.node_dict.get(self.node_type)
|
|
255
283
|
if self.node_template is None:
|
|
256
|
-
raise Exception(f
|
|
284
|
+
raise Exception(f"Node template {self.node_type} not found")
|
|
257
285
|
self.node_default = node_store.node_defaults.get(self.node_type)
|
|
258
286
|
self.setting_input = setting_input # wait until the end so that the hash is calculated correctly
|
|
259
287
|
|
|
@@ -292,10 +320,11 @@ class FlowNode:
|
|
|
292
320
|
Args:
|
|
293
321
|
setting_input: The new settings object.
|
|
294
322
|
"""
|
|
295
|
-
is_manual_input = (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
323
|
+
is_manual_input = (
|
|
324
|
+
self.node_type == "manual_input"
|
|
325
|
+
and isinstance(setting_input, input_schema.NodeManualInput)
|
|
326
|
+
and isinstance(self._setting_input, input_schema.NodeManualInput)
|
|
327
|
+
)
|
|
299
328
|
if is_manual_input:
|
|
300
329
|
_ = self.hash
|
|
301
330
|
self._setting_input = setting_input
|
|
@@ -309,7 +338,7 @@ class FlowNode:
|
|
|
309
338
|
self.reset()
|
|
310
339
|
|
|
311
340
|
@property
|
|
312
|
-
def node_id(self) ->
|
|
341
|
+
def node_id(self) -> str | int:
|
|
313
342
|
"""Gets the unique identifier of the node.
|
|
314
343
|
|
|
315
344
|
Returns:
|
|
@@ -336,7 +365,7 @@ class FlowNode:
|
|
|
336
365
|
return self.node_inputs.right_input
|
|
337
366
|
|
|
338
367
|
@property
|
|
339
|
-
def main_input(self) ->
|
|
368
|
+
def main_input(self) -> list["FlowNode"]:
|
|
340
369
|
"""Gets the list of nodes connected to the main input port(s).
|
|
341
370
|
|
|
342
371
|
Returns:
|
|
@@ -353,24 +382,29 @@ class FlowNode:
|
|
|
353
382
|
"""
|
|
354
383
|
if isinstance(self.setting_input, input_schema.NodePromise):
|
|
355
384
|
return False
|
|
356
|
-
return (
|
|
357
|
-
|
|
358
|
-
|
|
385
|
+
return (
|
|
386
|
+
self.node_template.input == len(self.node_inputs.get_all_inputs())
|
|
387
|
+
or (self.node_template.multi and len(self.node_inputs.get_all_inputs()) > 0)
|
|
388
|
+
or (self.node_template.multi and self.node_template.can_be_start)
|
|
389
|
+
)
|
|
359
390
|
|
|
360
391
|
def set_node_information(self):
|
|
361
392
|
"""Populates the `node_information` attribute with the current state.
|
|
362
393
|
|
|
363
394
|
This includes the node's connections, settings, and position.
|
|
364
395
|
"""
|
|
365
|
-
logger.info(
|
|
396
|
+
logger.info("setting node information")
|
|
366
397
|
node_information = self.node_information
|
|
367
398
|
node_information.left_input_id = self.node_inputs.left_input.node_id if self.left_input else None
|
|
368
399
|
node_information.right_input_id = self.node_inputs.right_input.node_id if self.right_input else None
|
|
369
|
-
node_information.input_ids =
|
|
370
|
-
|
|
400
|
+
node_information.input_ids = (
|
|
401
|
+
[mi.node_id for mi in self.node_inputs.main_inputs] if self.node_inputs.main_inputs is not None else None
|
|
402
|
+
)
|
|
371
403
|
node_information.setting_input = self.setting_input
|
|
372
404
|
node_information.outputs = [n.node_id for n in self.leads_to_nodes]
|
|
373
|
-
node_information.description =
|
|
405
|
+
node_information.description = (
|
|
406
|
+
self.setting_input.description if hasattr(self.setting_input, "description") else ""
|
|
407
|
+
)
|
|
374
408
|
node_information.is_setup = self.is_setup
|
|
375
409
|
node_information.x_position = self.setting_input.pos_x
|
|
376
410
|
node_information.y_position = self.setting_input.pos_y
|
|
@@ -404,7 +438,7 @@ class FlowNode:
|
|
|
404
438
|
self._function = function
|
|
405
439
|
|
|
406
440
|
@property
|
|
407
|
-
def all_inputs(self) ->
|
|
441
|
+
def all_inputs(self) -> list["FlowNode"]:
|
|
408
442
|
"""Gets a list of all nodes connected to any input port.
|
|
409
443
|
|
|
410
444
|
Returns:
|
|
@@ -436,8 +470,9 @@ class FlowNode:
|
|
|
436
470
|
self._hash = self.calculate_hash(self.setting_input)
|
|
437
471
|
return self._hash
|
|
438
472
|
|
|
439
|
-
def add_node_connection(
|
|
440
|
-
|
|
473
|
+
def add_node_connection(
|
|
474
|
+
self, from_node: "FlowNode", insert_type: Literal["main", "left", "right"] = "main"
|
|
475
|
+
) -> None:
|
|
441
476
|
"""Adds a connection from a source node to this node.
|
|
442
477
|
|
|
443
478
|
Args:
|
|
@@ -448,19 +483,19 @@ class FlowNode:
|
|
|
448
483
|
Exception: If the insert_type is invalid.
|
|
449
484
|
"""
|
|
450
485
|
from_node.leads_to_nodes.append(self)
|
|
451
|
-
if insert_type ==
|
|
486
|
+
if insert_type == "main":
|
|
452
487
|
if self.node_template.input <= 2 or self.node_inputs.main_inputs is None:
|
|
453
488
|
self.node_inputs.main_inputs = [from_node]
|
|
454
489
|
else:
|
|
455
490
|
self.node_inputs.main_inputs.append(from_node)
|
|
456
|
-
elif insert_type ==
|
|
491
|
+
elif insert_type == "right":
|
|
457
492
|
self.node_inputs.right_input = from_node
|
|
458
|
-
elif insert_type ==
|
|
493
|
+
elif insert_type == "left":
|
|
459
494
|
self.node_inputs.left_input = from_node
|
|
460
495
|
else:
|
|
461
|
-
raise Exception(
|
|
496
|
+
raise Exception("Cannot find the connection")
|
|
462
497
|
if self.setting_input.is_setup:
|
|
463
|
-
if hasattr(self.setting_input,
|
|
498
|
+
if hasattr(self.setting_input, "depending_on_id") and insert_type == "main":
|
|
464
499
|
self.setting_input.depending_on_id = from_node.node_id
|
|
465
500
|
self.reset()
|
|
466
501
|
from_node.reset()
|
|
@@ -472,7 +507,7 @@ class FlowNode:
|
|
|
472
507
|
deep: If True, the reset propagates recursively through the entire downstream graph.
|
|
473
508
|
"""
|
|
474
509
|
for node in self.leads_to_nodes:
|
|
475
|
-
self.print(f
|
|
510
|
+
self.print(f"resetting node: {node.node_id}")
|
|
476
511
|
node.reset(deep)
|
|
477
512
|
|
|
478
513
|
def get_flow_file_column_schema(self, col_name: str) -> FlowfileColumn | None:
|
|
@@ -488,7 +523,7 @@ class FlowNode:
|
|
|
488
523
|
if s.column_name == col_name:
|
|
489
524
|
return s
|
|
490
525
|
|
|
491
|
-
def get_predicted_schema(self, force: bool = False) ->
|
|
526
|
+
def get_predicted_schema(self, force: bool = False) -> list[FlowfileColumn] | None:
|
|
492
527
|
"""Predicts the output schema of the node without full execution.
|
|
493
528
|
|
|
494
529
|
It uses the schema_callback or infers from predicted data.
|
|
@@ -503,18 +538,18 @@ class FlowNode:
|
|
|
503
538
|
if self.node_schema.predicted_schema and not force:
|
|
504
539
|
return self.node_schema.predicted_schema
|
|
505
540
|
if self.schema_callback is not None and (self.node_schema.predicted_schema is None or force):
|
|
506
|
-
self.print(
|
|
541
|
+
self.print("Getting the data from a schema callback")
|
|
507
542
|
if force:
|
|
508
543
|
# Force the schema callback to reset, so that it will be executed again
|
|
509
544
|
self.schema_callback.reset()
|
|
510
545
|
schema = self.schema_callback()
|
|
511
546
|
if schema is not None and len(schema) > 0:
|
|
512
|
-
self.print(
|
|
547
|
+
self.print("Calculating the schema based on the schema callback")
|
|
513
548
|
self.node_schema.predicted_schema = schema
|
|
514
549
|
return self.node_schema.predicted_schema
|
|
515
550
|
predicted_data = self._predicted_data_getter()
|
|
516
551
|
if predicted_data is not None and predicted_data.schema is not None:
|
|
517
|
-
self.print(
|
|
552
|
+
self.print("Calculating the schema based on the predicted resulting data")
|
|
518
553
|
self.node_schema.predicted_schema = self._predicted_data_getter().schema
|
|
519
554
|
|
|
520
555
|
return self.node_schema.predicted_schema
|
|
@@ -527,7 +562,7 @@ class FlowNode:
|
|
|
527
562
|
True if the node is set up, False otherwise.
|
|
528
563
|
"""
|
|
529
564
|
if not self.node_information.is_setup:
|
|
530
|
-
if self.function.__name__ !=
|
|
565
|
+
if self.function.__name__ != "placeholder":
|
|
531
566
|
self.node_information.is_setup = True
|
|
532
567
|
self.setting_input.is_setup = True
|
|
533
568
|
return self.node_information.is_setup
|
|
@@ -538,12 +573,13 @@ class FlowNode:
|
|
|
538
573
|
Args:
|
|
539
574
|
v: The message or value to log.
|
|
540
575
|
"""
|
|
541
|
-
logger.info(f
|
|
576
|
+
logger.info(f"{self.node_type}, node_id: {self.node_id}: {v}")
|
|
542
577
|
|
|
543
578
|
def get_resulting_data(self) -> FlowDataEngine | None:
|
|
544
579
|
"""Executes the node's function to produce the actual output data.
|
|
545
580
|
|
|
546
581
|
Handles both regular functions and external data sources.
|
|
582
|
+
Thread-safe: uses _execution_lock to prevent concurrent execution.
|
|
547
583
|
|
|
548
584
|
Returns:
|
|
549
585
|
A FlowDataEngine instance containing the result, or None on error.
|
|
@@ -552,30 +588,40 @@ class FlowNode:
|
|
|
552
588
|
Exception: Propagates exceptions from the node's function execution.
|
|
553
589
|
"""
|
|
554
590
|
if self.is_setup:
|
|
555
|
-
|
|
556
|
-
self.
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
591
|
+
with self._execution_lock:
|
|
592
|
+
if self.results.resulting_data is None and self.results.errors is None:
|
|
593
|
+
self.print("getting resulting data")
|
|
594
|
+
try:
|
|
595
|
+
if isinstance(self.function, FlowDataEngine):
|
|
596
|
+
fl: FlowDataEngine = self.function
|
|
597
|
+
elif self.node_type == "external_source":
|
|
598
|
+
fl: FlowDataEngine = self.function()
|
|
599
|
+
fl.collect_external()
|
|
600
|
+
self.node_settings.streamable = False
|
|
601
|
+
else:
|
|
602
|
+
try:
|
|
603
|
+
self.print("Collecting input data from all inputs")
|
|
604
|
+
input_data = []
|
|
605
|
+
for i, v in enumerate(self.all_inputs):
|
|
606
|
+
self.print(f"Getting resulting data from input {i} (node {v.node_id})")
|
|
607
|
+
input_result = v.get_resulting_data()
|
|
608
|
+
self.print(f"Input {i} data type: {type(input_result)}, dataframe type: {type(input_result.data_frame) if input_result else 'None'}")
|
|
609
|
+
input_data.append(input_result)
|
|
610
|
+
self.print(f"All {len(input_data)} inputs collected, calling node function")
|
|
611
|
+
fl = self._function(*input_data)
|
|
612
|
+
self.print(f"Node function returned, result type: {type(fl)}")
|
|
613
|
+
except Exception as e:
|
|
614
|
+
raise e
|
|
615
|
+
fl.set_streamable(self.node_settings.streamable)
|
|
616
|
+
self.results.resulting_data = fl
|
|
617
|
+
self.node_schema.result_schema = fl.schema
|
|
618
|
+
except Exception as e:
|
|
619
|
+
self.results.resulting_data = FlowDataEngine()
|
|
620
|
+
self.results.errors = str(e)
|
|
621
|
+
self.node_stats.has_run_with_current_setup = False
|
|
622
|
+
self.node_stats.has_completed_last_run = False
|
|
623
|
+
raise e
|
|
624
|
+
return self.results.resulting_data
|
|
579
625
|
|
|
580
626
|
def _predicted_data_getter(self) -> FlowDataEngine | None:
|
|
581
627
|
"""Internal helper to get a predicted data result.
|
|
@@ -590,14 +636,14 @@ class FlowNode:
|
|
|
590
636
|
return fl
|
|
591
637
|
except ValueError as e:
|
|
592
638
|
if str(e) == "generator already executing":
|
|
593
|
-
logger.info(
|
|
639
|
+
logger.info("Generator already executing, waiting for the result")
|
|
594
640
|
sleep(1)
|
|
595
641
|
return self._predicted_data_getter()
|
|
596
642
|
fl = FlowDataEngine()
|
|
597
643
|
return fl
|
|
598
644
|
|
|
599
645
|
except Exception as e:
|
|
600
|
-
logger.warning(
|
|
646
|
+
logger.warning("there was an issue with the function, returning an empty Flowfile")
|
|
601
647
|
logger.warning(e)
|
|
602
648
|
|
|
603
649
|
def get_predicted_resulting_data(self) -> FlowDataEngine:
|
|
@@ -609,7 +655,7 @@ class FlowNode:
|
|
|
609
655
|
A FlowDataEngine instance with a schema but no data.
|
|
610
656
|
"""
|
|
611
657
|
if self.needs_run(False) and self.schema_callback is not None or self.node_schema.result_schema is not None:
|
|
612
|
-
self.print(
|
|
658
|
+
self.print("Getting data based on the schema")
|
|
613
659
|
|
|
614
660
|
_s = self.schema_callback() if self.node_schema.result_schema is None else self.node_schema.result_schema
|
|
615
661
|
return FlowDataEngine.create_from_schema(_s)
|
|
@@ -649,7 +695,7 @@ class FlowNode:
|
|
|
649
695
|
yield n
|
|
650
696
|
|
|
651
697
|
@property
|
|
652
|
-
def schema(self) ->
|
|
698
|
+
def schema(self) -> list[FlowfileColumn]:
|
|
653
699
|
"""Gets the definitive output schema of the node.
|
|
654
700
|
|
|
655
701
|
If not already run, it falls back to the predicted schema.
|
|
@@ -661,7 +707,7 @@ class FlowNode:
|
|
|
661
707
|
if self.is_setup and self.results.errors is None:
|
|
662
708
|
if self.node_schema.result_schema is not None and len(self.node_schema.result_schema) > 0:
|
|
663
709
|
return self.node_schema.result_schema
|
|
664
|
-
elif self.node_type ==
|
|
710
|
+
elif self.node_type == "output":
|
|
665
711
|
if len(self.node_inputs.main_inputs) > 0:
|
|
666
712
|
self.node_schema.result_schema = self.node_inputs.main_inputs[0].schema
|
|
667
713
|
else:
|
|
@@ -680,11 +726,15 @@ class FlowNode:
|
|
|
680
726
|
"""
|
|
681
727
|
|
|
682
728
|
if results_exists(self.hash):
|
|
683
|
-
logger.warning(
|
|
729
|
+
logger.warning("Not implemented")
|
|
684
730
|
clear_task_from_worker(self.hash)
|
|
685
731
|
|
|
686
|
-
def needs_run(
|
|
687
|
-
|
|
732
|
+
def needs_run(
|
|
733
|
+
self,
|
|
734
|
+
performance_mode: bool,
|
|
735
|
+
node_logger: NodeLogger = None,
|
|
736
|
+
execution_location: schemas.ExecutionLocationsLiteral = "remote",
|
|
737
|
+
) -> bool:
|
|
688
738
|
"""Determines if the node needs to be executed.
|
|
689
739
|
|
|
690
740
|
The decision is based on its run state, caching settings, and execution mode.
|
|
@@ -703,7 +753,7 @@ class FlowNode:
|
|
|
703
753
|
flow_logger = logger if node_logger is None else node_logger
|
|
704
754
|
cache_result_exists = results_exists(self.hash)
|
|
705
755
|
if not self.node_stats.has_run_with_current_setup:
|
|
706
|
-
flow_logger.info(
|
|
756
|
+
flow_logger.info("Node has not run, needs to run")
|
|
707
757
|
return True
|
|
708
758
|
if self.node_settings.cache_results and cache_result_exists:
|
|
709
759
|
return False
|
|
@@ -737,7 +787,9 @@ class FlowNode:
|
|
|
737
787
|
if example_data is None:
|
|
738
788
|
example_data = resulting_data.get_sample(100).to_arrow()
|
|
739
789
|
return example_data
|
|
790
|
+
|
|
740
791
|
return get_example_data
|
|
792
|
+
|
|
741
793
|
resulting_data = self.get_resulting_data()
|
|
742
794
|
|
|
743
795
|
if not performance_mode:
|
|
@@ -759,8 +811,13 @@ class FlowNode:
|
|
|
759
811
|
try:
|
|
760
812
|
resulting_data = self.get_resulting_data()
|
|
761
813
|
if not performance_mode:
|
|
762
|
-
external_sampler = ExternalSampler(
|
|
763
|
-
|
|
814
|
+
external_sampler = ExternalSampler(
|
|
815
|
+
lf=resulting_data.data_frame,
|
|
816
|
+
file_ref=self.hash,
|
|
817
|
+
wait_on_completion=True,
|
|
818
|
+
node_id=self.node_id,
|
|
819
|
+
flow_id=flow_id,
|
|
820
|
+
)
|
|
764
821
|
self.store_example_data_generator(external_sampler)
|
|
765
822
|
if self.results.errors is None and not self.node_stats.is_canceled:
|
|
766
823
|
self.node_stats.has_run_with_current_setup = True
|
|
@@ -790,48 +847,67 @@ class FlowNode:
|
|
|
790
847
|
Exception: If the node_logger is not provided or if execution fails.
|
|
791
848
|
"""
|
|
792
849
|
if node_logger is None:
|
|
793
|
-
raise Exception(
|
|
850
|
+
raise Exception("Node logger is not defined")
|
|
794
851
|
if self.node_settings.cache_results and results_exists(self.hash):
|
|
795
852
|
try:
|
|
796
853
|
self.results.resulting_data = get_external_df_result(self.hash)
|
|
797
854
|
self._cache_progress = None
|
|
798
855
|
return
|
|
799
|
-
except Exception
|
|
800
|
-
node_logger.warning(
|
|
801
|
-
if self.node_type ==
|
|
856
|
+
except Exception:
|
|
857
|
+
node_logger.warning("Failed to read the cache, rerunning the code")
|
|
858
|
+
if self.node_type == "output":
|
|
802
859
|
self.results.resulting_data = self.get_resulting_data()
|
|
803
860
|
self.node_stats.has_run_with_current_setup = True
|
|
804
861
|
return
|
|
862
|
+
|
|
805
863
|
try:
|
|
806
|
-
self.get_resulting_data()
|
|
864
|
+
result_data = self.get_resulting_data()
|
|
865
|
+
# Use 'is not None' instead of truthiness check to avoid triggering __len__()
|
|
866
|
+
# which calls .collect() on the LazyFrame and can cause issues
|
|
867
|
+
if result_data is None:
|
|
868
|
+
self.results.errors = "Error with creating the lazy frame, most likely due to invalid graph"
|
|
869
|
+
raise Exception("get_resulting_data returned None")
|
|
807
870
|
except Exception as e:
|
|
808
|
-
self.results.errors =
|
|
871
|
+
self.results.errors = "Error with creating the lazy frame, most likely due to invalid graph"
|
|
809
872
|
raise e
|
|
873
|
+
|
|
810
874
|
if not performance_mode:
|
|
811
|
-
external_df_fetcher = ExternalDfFetcher(
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
875
|
+
external_df_fetcher = ExternalDfFetcher(
|
|
876
|
+
lf=self.get_resulting_data().data_frame,
|
|
877
|
+
file_ref=self.hash,
|
|
878
|
+
wait_on_completion=False,
|
|
879
|
+
flow_id=node_logger.flow_id,
|
|
880
|
+
node_id=self.node_id,
|
|
881
|
+
)
|
|
815
882
|
self._fetch_cached_df = external_df_fetcher
|
|
883
|
+
|
|
816
884
|
try:
|
|
817
885
|
lf = external_df_fetcher.get_result()
|
|
818
886
|
self.results.resulting_data = FlowDataEngine(
|
|
819
|
-
lf,
|
|
820
|
-
|
|
887
|
+
lf,
|
|
888
|
+
number_of_records=ExternalDfFetcher(
|
|
889
|
+
lf=lf,
|
|
890
|
+
operation_type="calculate_number_of_records",
|
|
891
|
+
flow_id=node_logger.flow_id,
|
|
892
|
+
node_id=self.node_id,
|
|
893
|
+
).result,
|
|
821
894
|
)
|
|
895
|
+
|
|
822
896
|
if not performance_mode:
|
|
823
897
|
self.store_example_data_generator(external_df_fetcher)
|
|
824
898
|
self.node_stats.has_run_with_current_setup = True
|
|
825
899
|
|
|
826
900
|
except Exception as e:
|
|
827
|
-
node_logger.error(
|
|
901
|
+
node_logger.error("Error with external process")
|
|
828
902
|
if external_df_fetcher.error_code == -1:
|
|
829
903
|
try:
|
|
830
904
|
self.results.resulting_data = self.get_resulting_data()
|
|
831
|
-
self.results.warnings = (
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
905
|
+
self.results.warnings = (
|
|
906
|
+
"Error with external process (unknown error), "
|
|
907
|
+
"likely the process was killed by the server because of memory constraints, "
|
|
908
|
+
"continue with the process. "
|
|
909
|
+
"We cannot display example data..."
|
|
910
|
+
)
|
|
835
911
|
except Exception as e:
|
|
836
912
|
self.results.errors = str(e)
|
|
837
913
|
raise e
|
|
@@ -858,15 +934,18 @@ class FlowNode:
|
|
|
858
934
|
self._fetch_cached_df.cancel()
|
|
859
935
|
self.node_stats.is_canceled = True
|
|
860
936
|
else:
|
|
861
|
-
logger.warning(
|
|
937
|
+
logger.warning("No external process to cancel")
|
|
862
938
|
self.node_stats.is_canceled = True
|
|
863
939
|
|
|
864
|
-
def execute_node(
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
940
|
+
def execute_node(
|
|
941
|
+
self,
|
|
942
|
+
run_location: schemas.ExecutionLocationsLiteral,
|
|
943
|
+
reset_cache: bool = False,
|
|
944
|
+
performance_mode: bool = False,
|
|
945
|
+
retry: bool = True,
|
|
946
|
+
node_logger: NodeLogger = None,
|
|
947
|
+
optimize_for_downstream: bool = True,
|
|
948
|
+
):
|
|
870
949
|
"""Orchestrates the execution, handling location, caching, and retries.
|
|
871
950
|
|
|
872
951
|
Args:
|
|
@@ -882,7 +961,7 @@ class FlowNode:
|
|
|
882
961
|
Exception: If the node_logger is not defined.
|
|
883
962
|
"""
|
|
884
963
|
if node_logger is None:
|
|
885
|
-
raise Exception(
|
|
964
|
+
raise Exception("Flow logger is not defined")
|
|
886
965
|
# TODO: Simplify which route is being picked there are many duplicate checks
|
|
887
966
|
|
|
888
967
|
if reset_cache:
|
|
@@ -891,63 +970,78 @@ class FlowNode:
|
|
|
891
970
|
self.node_stats.has_completed_last_run = False
|
|
892
971
|
|
|
893
972
|
if self.is_setup:
|
|
894
|
-
node_logger.info(f
|
|
895
|
-
if (
|
|
896
|
-
|
|
973
|
+
node_logger.info(f"Starting to run {self.__name__}")
|
|
974
|
+
if (
|
|
975
|
+
self.needs_run(performance_mode, node_logger, run_location)
|
|
976
|
+
or self.node_template.node_group == "output"
|
|
977
|
+
and not (run_location == "local")
|
|
978
|
+
):
|
|
897
979
|
self.clear_table_example()
|
|
898
980
|
self.prepare_before_run()
|
|
899
981
|
self.reset()
|
|
900
982
|
try:
|
|
901
|
-
if (
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
983
|
+
if (
|
|
984
|
+
run_location == "remote"
|
|
985
|
+
or (self.node_default.transform_type == "wide" and optimize_for_downstream)
|
|
986
|
+
and not run_location == "local"
|
|
987
|
+
) or self.node_settings.cache_results:
|
|
988
|
+
node_logger.info("Running the node remotely")
|
|
906
989
|
if self.node_settings.cache_results:
|
|
907
990
|
performance_mode = False
|
|
908
|
-
self.execute_remote(
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
991
|
+
self.execute_remote(
|
|
992
|
+
performance_mode=(performance_mode if not self.node_settings.cache_results else False),
|
|
993
|
+
node_logger=node_logger,
|
|
994
|
+
)
|
|
912
995
|
else:
|
|
913
|
-
node_logger.info(
|
|
996
|
+
node_logger.info("Running the node locally")
|
|
914
997
|
self.execute_local(performance_mode=performance_mode, flow_id=node_logger.flow_id)
|
|
915
998
|
except Exception as e:
|
|
916
|
-
if
|
|
917
|
-
logger.warning(
|
|
918
|
-
all_inputs:
|
|
999
|
+
if "No such file or directory (os error" in str(e) and retry:
|
|
1000
|
+
logger.warning("Error with the input node, starting to rerun the input node...")
|
|
1001
|
+
all_inputs: list[FlowNode] = self.node_inputs.get_all_inputs()
|
|
919
1002
|
for node_input in all_inputs:
|
|
920
|
-
node_input.execute_node(
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1003
|
+
node_input.execute_node(
|
|
1004
|
+
run_location=run_location,
|
|
1005
|
+
performance_mode=performance_mode,
|
|
1006
|
+
retry=True,
|
|
1007
|
+
reset_cache=True,
|
|
1008
|
+
node_logger=node_logger,
|
|
1009
|
+
)
|
|
1010
|
+
self.execute_node(
|
|
1011
|
+
run_location=run_location,
|
|
1012
|
+
performance_mode=performance_mode,
|
|
1013
|
+
retry=False,
|
|
1014
|
+
node_logger=node_logger,
|
|
1015
|
+
)
|
|
927
1016
|
else:
|
|
928
1017
|
self.results.errors = str(e)
|
|
929
1018
|
if "Connection refused" in str(e) and "/submit_query/" in str(e):
|
|
930
|
-
node_logger.warning(
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1019
|
+
node_logger.warning(
|
|
1020
|
+
"There was an issue connecting to the remote worker, "
|
|
1021
|
+
"ensure the worker process is running, "
|
|
1022
|
+
"or change the settings to, so it executes locally"
|
|
1023
|
+
)
|
|
1024
|
+
node_logger.error(
|
|
1025
|
+
"Could not execute in the remote worker. (Re)start the worker service, or change settings to local settings."
|
|
1026
|
+
)
|
|
934
1027
|
else:
|
|
935
|
-
node_logger.error(f
|
|
936
|
-
elif (
|
|
937
|
-
|
|
1028
|
+
node_logger.error(f"Error with running the node: {e}")
|
|
1029
|
+
elif (run_location == "local") and (
|
|
1030
|
+
not self.node_stats.has_run_with_current_setup or self.node_template.node_group == "output"
|
|
1031
|
+
):
|
|
938
1032
|
try:
|
|
939
|
-
node_logger.info(
|
|
1033
|
+
node_logger.info("Executing fully locally")
|
|
940
1034
|
self.execute_full_local(performance_mode)
|
|
941
1035
|
except Exception as e:
|
|
942
1036
|
self.results.errors = str(e)
|
|
943
|
-
node_logger.error(f
|
|
1037
|
+
node_logger.error(f"Error with running the node: {e}")
|
|
944
1038
|
self.node_stats.error = str(e)
|
|
945
1039
|
self.node_stats.has_completed_last_run = False
|
|
946
1040
|
|
|
947
1041
|
else:
|
|
948
|
-
node_logger.info(
|
|
1042
|
+
node_logger.info("Node has already run, not running the node")
|
|
949
1043
|
else:
|
|
950
|
-
node_logger.warning(f
|
|
1044
|
+
node_logger.warning(f"Node {self.__name__} is not setup, cannot run the node")
|
|
951
1045
|
|
|
952
1046
|
def store_example_data_generator(self, external_df_fetcher: ExternalDfFetcher | ExternalSampler):
|
|
953
1047
|
"""Stores a generator function for fetching a sample of the result data.
|
|
@@ -960,7 +1054,7 @@ class FlowNode:
|
|
|
960
1054
|
self.results.example_data_path = file_ref
|
|
961
1055
|
self.results.example_data_generator = get_read_top_n(file_path=file_ref, n=100)
|
|
962
1056
|
else:
|
|
963
|
-
logger.error(
|
|
1057
|
+
logger.error("Could not get the sample data, the external process is not ready")
|
|
964
1058
|
|
|
965
1059
|
def needs_reset(self) -> bool:
|
|
966
1060
|
"""Checks if the node's hash has changed, indicating an outdated state.
|
|
@@ -980,7 +1074,7 @@ class FlowNode:
|
|
|
980
1074
|
"""
|
|
981
1075
|
needs_reset = self.needs_reset() or deep
|
|
982
1076
|
if needs_reset:
|
|
983
|
-
logger.info(f
|
|
1077
|
+
logger.info(f"{self.node_id}: Node needs reset")
|
|
984
1078
|
self.node_stats.has_run_with_current_setup = False
|
|
985
1079
|
self.results.reset()
|
|
986
1080
|
self.node_schema.result_schema = None
|
|
@@ -991,7 +1085,7 @@ class FlowNode:
|
|
|
991
1085
|
if self.is_correct:
|
|
992
1086
|
self._schema_callback = None # Ensure the schema callback is reset
|
|
993
1087
|
if self.schema_callback:
|
|
994
|
-
logger.info(f
|
|
1088
|
+
logger.info(f"{self.node_id}: Resetting the schema callback")
|
|
995
1089
|
self.schema_callback.start()
|
|
996
1090
|
self.evaluate_nodes()
|
|
997
1091
|
_ = self.hash # Recalculate the hash after reset
|
|
@@ -1005,17 +1099,18 @@ class FlowNode:
|
|
|
1005
1099
|
Returns:
|
|
1006
1100
|
True if the connection was found and removed, False otherwise.
|
|
1007
1101
|
"""
|
|
1008
|
-
logger.info(f
|
|
1102
|
+
logger.info(f"Deleting lead to node: {node_id}")
|
|
1009
1103
|
for i, lead_to_node in enumerate(self.leads_to_nodes):
|
|
1010
|
-
logger.info(f
|
|
1104
|
+
logger.info(f"Checking lead to node: {lead_to_node.node_id}")
|
|
1011
1105
|
if lead_to_node.node_id == node_id:
|
|
1012
|
-
logger.info(f
|
|
1106
|
+
logger.info(f"Found the node to delete: {node_id}")
|
|
1013
1107
|
self.leads_to_nodes.pop(i)
|
|
1014
1108
|
return True
|
|
1015
1109
|
return False
|
|
1016
1110
|
|
|
1017
|
-
def delete_input_node(
|
|
1018
|
-
|
|
1111
|
+
def delete_input_node(
|
|
1112
|
+
self, node_id: int, connection_type: input_schema.InputConnectionClass = "input-0", complete: bool = False
|
|
1113
|
+
) -> bool:
|
|
1019
1114
|
"""Removes a connection from a specific input node.
|
|
1020
1115
|
|
|
1021
1116
|
Args:
|
|
@@ -1027,23 +1122,23 @@ class FlowNode:
|
|
|
1027
1122
|
True if a connection was found and removed, False otherwise.
|
|
1028
1123
|
"""
|
|
1029
1124
|
deleted: bool = False
|
|
1030
|
-
if connection_type ==
|
|
1125
|
+
if connection_type == "input-0":
|
|
1031
1126
|
for i, node in enumerate(self.node_inputs.main_inputs):
|
|
1032
1127
|
if node.node_id == node_id:
|
|
1033
1128
|
self.node_inputs.main_inputs.pop(i)
|
|
1034
1129
|
deleted = True
|
|
1035
1130
|
if not complete:
|
|
1036
1131
|
continue
|
|
1037
|
-
elif connection_type ==
|
|
1132
|
+
elif connection_type == "input-1" or complete:
|
|
1038
1133
|
if self.node_inputs.right_input is not None and self.node_inputs.right_input.node_id == node_id:
|
|
1039
1134
|
self.node_inputs.right_input = None
|
|
1040
1135
|
deleted = True
|
|
1041
|
-
elif connection_type ==
|
|
1136
|
+
elif connection_type == "input-2" or complete:
|
|
1042
1137
|
if self.node_inputs.left_input is not None and self.node_inputs.right_input.node_id == node_id:
|
|
1043
1138
|
self.node_inputs.left_input = None
|
|
1044
1139
|
deleted = True
|
|
1045
1140
|
else:
|
|
1046
|
-
logger.warning(
|
|
1141
|
+
logger.warning("Could not find the connection to delete...")
|
|
1047
1142
|
if deleted:
|
|
1048
1143
|
self.reset()
|
|
1049
1144
|
return deleted
|
|
@@ -1056,7 +1151,7 @@ class FlowNode:
|
|
|
1056
1151
|
"""
|
|
1057
1152
|
return f"Node id: {self.node_id} ({self.node_type})"
|
|
1058
1153
|
|
|
1059
|
-
def _get_readable_schema(self) ->
|
|
1154
|
+
def _get_readable_schema(self) -> list[dict] | None:
|
|
1060
1155
|
"""Helper to get a simplified, dictionary representation of the output schema.
|
|
1061
1156
|
|
|
1062
1157
|
Returns:
|
|
@@ -1074,11 +1169,14 @@ class FlowNode:
|
|
|
1074
1169
|
Returns:
|
|
1075
1170
|
A dictionary containing key information about the node.
|
|
1076
1171
|
"""
|
|
1077
|
-
return dict(
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1172
|
+
return dict(
|
|
1173
|
+
FlowNode=dict(
|
|
1174
|
+
node_id=self.node_id,
|
|
1175
|
+
step_name=self.__name__,
|
|
1176
|
+
output_columns=self.node_schema.output_columns,
|
|
1177
|
+
output_schema=self._get_readable_schema(),
|
|
1178
|
+
)
|
|
1179
|
+
)
|
|
1082
1180
|
|
|
1083
1181
|
@property
|
|
1084
1182
|
def number_of_leads_to_nodes(self) -> int | None:
|
|
@@ -1149,14 +1247,13 @@ class FlowNode:
|
|
|
1149
1247
|
Returns:
|
|
1150
1248
|
A `TableExample` object, or None if the node is not set up.
|
|
1151
1249
|
"""
|
|
1152
|
-
self.print(
|
|
1250
|
+
self.print("Getting a table example")
|
|
1153
1251
|
if self.is_setup and include_data and self.node_stats.has_completed_last_run:
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
self.print('getting the table example')
|
|
1252
|
+
if self.node_template.node_group == "output":
|
|
1253
|
+
self.print("getting the table example")
|
|
1157
1254
|
return self.main_input[0].get_table_example(include_data)
|
|
1158
1255
|
|
|
1159
|
-
logger.info(
|
|
1256
|
+
logger.info("getting the table example since the node has run")
|
|
1160
1257
|
example_data_getter = self.results.example_data_generator
|
|
1161
1258
|
if example_data_getter is not None:
|
|
1162
1259
|
data = example_data_getter().to_pylist()
|
|
@@ -1168,26 +1265,34 @@ class FlowNode:
|
|
|
1168
1265
|
fl = self.get_resulting_data()
|
|
1169
1266
|
has_example_data = self.results.example_data_generator is not None
|
|
1170
1267
|
|
|
1171
|
-
return TableExample(
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1268
|
+
return TableExample(
|
|
1269
|
+
node_id=self.node_id,
|
|
1270
|
+
name=str(self.node_id),
|
|
1271
|
+
number_of_records=999,
|
|
1272
|
+
number_of_columns=fl.number_of_fields,
|
|
1273
|
+
table_schema=schema,
|
|
1274
|
+
columns=fl.columns,
|
|
1275
|
+
data=data,
|
|
1276
|
+
has_example_data=has_example_data,
|
|
1277
|
+
has_run_with_current_setup=self.node_stats.has_run_with_current_setup,
|
|
1278
|
+
)
|
|
1178
1279
|
else:
|
|
1179
|
-
logger.warning(
|
|
1280
|
+
logger.warning("getting the table example but the node has not run")
|
|
1180
1281
|
try:
|
|
1181
1282
|
schema = [FileColumn.model_validate(c.get_column_repr()) for c in self.schema]
|
|
1182
1283
|
except Exception as e:
|
|
1183
1284
|
logger.warning(e)
|
|
1184
1285
|
schema = []
|
|
1185
1286
|
columns = [s.name for s in schema]
|
|
1186
|
-
return TableExample(
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1287
|
+
return TableExample(
|
|
1288
|
+
node_id=self.node_id,
|
|
1289
|
+
name=str(self.node_id),
|
|
1290
|
+
number_of_records=0,
|
|
1291
|
+
number_of_columns=len(columns),
|
|
1292
|
+
table_schema=schema,
|
|
1293
|
+
columns=columns,
|
|
1294
|
+
data=[],
|
|
1295
|
+
)
|
|
1191
1296
|
|
|
1192
1297
|
def get_node_data(self, flow_id: int, include_example: bool = False) -> NodeData:
|
|
1193
1298
|
"""Gathers all necessary data for representing the node in the UI.
|
|
@@ -1199,11 +1304,13 @@ class FlowNode:
|
|
|
1199
1304
|
Returns:
|
|
1200
1305
|
A `NodeData` object.
|
|
1201
1306
|
"""
|
|
1202
|
-
node = NodeData(
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1307
|
+
node = NodeData(
|
|
1308
|
+
flow_id=flow_id,
|
|
1309
|
+
node_id=self.node_id,
|
|
1310
|
+
has_run=self.node_stats.has_run_with_current_setup,
|
|
1311
|
+
setting_input=self.setting_input,
|
|
1312
|
+
flow_type=self.node_type,
|
|
1313
|
+
)
|
|
1207
1314
|
if self.main_input:
|
|
1208
1315
|
node.main_input = self.main_input[0].get_table_example()
|
|
1209
1316
|
if self.left_input:
|
|
@@ -1215,6 +1322,9 @@ class FlowNode:
|
|
|
1215
1322
|
node = setting_generator.get_setting_generator(self.node_type)(node)
|
|
1216
1323
|
|
|
1217
1324
|
node = setting_updator.get_setting_updator(self.node_type)(node)
|
|
1325
|
+
# Save the updated settings back to the node so they persist across calls
|
|
1326
|
+
if node.setting_input is not None and not isinstance(node.setting_input, input_schema.NodePromise):
|
|
1327
|
+
self.setting_input = node.setting_input
|
|
1218
1328
|
return node
|
|
1219
1329
|
|
|
1220
1330
|
def get_output_data(self) -> TableExample:
|
|
@@ -1231,12 +1341,14 @@ class FlowNode:
|
|
|
1231
1341
|
Returns:
|
|
1232
1342
|
A `NodeInput` object.
|
|
1233
1343
|
"""
|
|
1234
|
-
return schemas.NodeInput(
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1344
|
+
return schemas.NodeInput(
|
|
1345
|
+
pos_y=self.setting_input.pos_y,
|
|
1346
|
+
pos_x=self.setting_input.pos_x,
|
|
1347
|
+
id=self.node_id,
|
|
1348
|
+
**self.node_template.__dict__,
|
|
1349
|
+
)
|
|
1350
|
+
|
|
1351
|
+
def get_edge_input(self) -> list[schemas.NodeEdge]:
|
|
1240
1352
|
"""Generates `NodeEdge` objects for all input connections to this node.
|
|
1241
1353
|
|
|
1242
1354
|
Returns:
|
|
@@ -1245,24 +1357,33 @@ class FlowNode:
|
|
|
1245
1357
|
edges = []
|
|
1246
1358
|
if self.node_inputs.main_inputs is not None:
|
|
1247
1359
|
for i, main_input in enumerate(self.node_inputs.main_inputs):
|
|
1248
|
-
edges.append(
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1360
|
+
edges.append(
|
|
1361
|
+
schemas.NodeEdge(
|
|
1362
|
+
id=f"{main_input.node_id}-{self.node_id}-{i}",
|
|
1363
|
+
source=main_input.node_id,
|
|
1364
|
+
target=self.node_id,
|
|
1365
|
+
sourceHandle="output-0",
|
|
1366
|
+
targetHandle="input-0",
|
|
1367
|
+
)
|
|
1368
|
+
)
|
|
1254
1369
|
if self.node_inputs.left_input is not None:
|
|
1255
|
-
edges.append(
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1370
|
+
edges.append(
|
|
1371
|
+
schemas.NodeEdge(
|
|
1372
|
+
id=f"{self.node_inputs.left_input.node_id}-{self.node_id}-right",
|
|
1373
|
+
source=self.node_inputs.left_input.node_id,
|
|
1374
|
+
target=self.node_id,
|
|
1375
|
+
sourceHandle="output-0",
|
|
1376
|
+
targetHandle="input-2",
|
|
1377
|
+
)
|
|
1378
|
+
)
|
|
1261
1379
|
if self.node_inputs.right_input is not None:
|
|
1262
|
-
edges.append(
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1380
|
+
edges.append(
|
|
1381
|
+
schemas.NodeEdge(
|
|
1382
|
+
id=f"{self.node_inputs.right_input.node_id}-{self.node_id}-left",
|
|
1383
|
+
source=self.node_inputs.right_input.node_id,
|
|
1384
|
+
target=self.node_id,
|
|
1385
|
+
sourceHandle="output-0",
|
|
1386
|
+
targetHandle="input-1",
|
|
1387
|
+
)
|
|
1388
|
+
)
|
|
1268
1389
|
return edges
|