Flowfile 0.4.1__py3-none-any.whl → 0.5.3__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 +179 -73
- flowfile/__main__.py +10 -7
- flowfile/api.py +52 -59
- flowfile/web/__init__.py +14 -9
- flowfile/web/static/assets/AdminView-49392a9a.js +713 -0
- flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
- flowfile/web/static/assets/CloudConnectionView-36bcd6df.css +72 -0
- flowfile/web/static/assets/{CloudConnectionManager-d3248f8d.js → CloudConnectionView-f13f202b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-d65bf041.js → CloudStorageReader-0023d4a5.js} +10 -8
- flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
- flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
- flowfile/web/static/assets/{CloudStorageWriter-e83be3ed.js → CloudStorageWriter-8e781e11.js} +10 -8
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-cce661cf.js → ColumnSelector-8ad68ea9.js} +3 -5
- flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-11a4652a.js → ContextMenu-31ee57f0.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-160afb08.js → ContextMenu-69a74055.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-cf18d2cc.js → ContextMenu-8e2051c6.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-4c74eef1.css → ContextMenu-8ec1729e.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-63cfa99b.css → ContextMenu-9b310c60.css} +6 -6
- flowfile/web/static/assets/{CrossJoin-d395d38c.js → CrossJoin-03df6938.js} +12 -10
- flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
- flowfile/web/static/assets/CustomNode-59e99a86.css +32 -0
- flowfile/web/static/assets/{CustomNode-b812dc0b.js → CustomNode-8479239b.js} +36 -24
- flowfile/web/static/assets/{DatabaseConnectionSettings-7000bf2c.js → DatabaseConnectionSettings-869e3efd.js} +5 -4
- flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-e91df89a.css} +13 -13
- flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-36898a00.css} +24 -24
- flowfile/web/static/assets/{DatabaseReader-4f035d0c.js → DatabaseReader-c58b9552.js} +25 -15
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseManager-9662ec5b.js → DatabaseView-d26a9140.js} +11 -11
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
- flowfile/web/static/assets/{DatabaseWriter-f65dcd54.js → DatabaseWriter-4d05ddc7.js} +17 -10
- flowfile/web/static/assets/{designer-e3c150ec.css → DesignerView-a6d0ee84.css} +629 -538
- flowfile/web/static/assets/{designer-f3656d8c.js → DesignerView-e6f5c0e8.js} +1214 -3209
- flowfile/web/static/assets/{documentation-52b241e7.js → DocumentationView-2e78ef1b.js} +5 -5
- flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-fd46c656.css} +7 -7
- flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
- flowfile/web/static/assets/{ExploreData-94c43dfc.js → ExploreData-7b54caca.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-ac04b3cc.js → ExternalSource-3fa399b2.js} +9 -7
- flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-47ab05a3.css} +17 -17
- flowfile/web/static/assets/Filter-7494ea97.css +48 -0
- flowfile/web/static/assets/Filter-8cbbdbf3.js +287 -0
- flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
- flowfile/web/static/assets/{Formula-71472193.js → Formula-aac42b1e.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-b317f631.js → FuzzyMatch-cd9bbfca.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-754a234f.js → GraphSolver-c7e6780e.js} +13 -11
- flowfile/web/static/assets/{GroupBy-6c6f9802.js → GroupBy-93c5d22b.js} +9 -7
- flowfile/web/static/assets/{GroupBy-b9505323.css → GroupBy-be7ac0bf.css} +10 -10
- flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
- flowfile/web/static/assets/{Join-a1b800be.js → Join-a19b2de2.js} +13 -11
- flowfile/web/static/assets/LoginView-0df4ed0a.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-a9640276.js → ManualInput-8d3374b2.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-97213888.js → MultiSelect-ad1b6243.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-6ffe088a.js → MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js} +1 -1
- flowfile/web/static/assets/NodeDesigner-40b647c9.js +2610 -0
- flowfile/web/static/assets/NodeDesigner-5f53be3f.css +1429 -0
- flowfile/web/static/assets/{NumericInput-e638088a.js → NumericInput-7100234c.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-90eb2cba.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
- flowfile/web/static/assets/{Output-ddc9079f.css → Output-35e97000.css} +6 -6
- flowfile/web/static/assets/{Output-76750610.js → Output-f5efd2aa.js} +60 -38
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-7814803f.js → Pivot-d981d23c.js} +11 -9
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f92137d2.js → PivotValidation-39386e95.js} +3 -3
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-76dd431a.js → PivotValidation-63de1f73.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-889c3008.js → PolarsCode-f9d69217.js} +18 -9
- flowfile/web/static/assets/PopOver-b22f049e.js +939 -0
- flowfile/web/static/assets/PopOver-d96599db.css +33 -0
- flowfile/web/static/assets/{Read-6b17491f.css → Read-36e7bd51.css} +12 -12
- flowfile/web/static/assets/{Read-637b72a7.js → Read-aec2e377.js} +83 -105
- flowfile/web/static/assets/{RecordCount-2b050c41.js → RecordCount-78ed6845.js} +6 -4
- flowfile/web/static/assets/{RecordId-81df7784.js → RecordId-2156e890.js} +8 -6
- flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
- flowfile/web/static/assets/{SQLQueryComponent-88dcfe53.js → SQLQueryComponent-48c72f5b.js} +3 -3
- flowfile/web/static/assets/{Sample-258ad2a9.js → Sample-1352ca74.js} +6 -4
- flowfile/web/static/assets/SecretSelector-22b5ff89.js +113 -0
- flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
- flowfile/web/static/assets/{SecretManager-2a2cb7e2.js → SecretsView-17df66ee.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-850215fd.js → Select-0aee4c54.js} +9 -7
- flowfile/web/static/assets/{SettingsSection-55bae608.js → SettingsSection-0784e157.js} +3 -3
- 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-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-0e8d9123.js → SettingsSection-cd341bb6.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-29b4fa6b.js → SettingsSection-f2002a6d.js} +3 -3
- flowfile/web/static/assets/{SingleSelect-bebd408b.js → SingleSelect-460cc0ea.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-6093741c.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
- flowfile/web/static/assets/{SliderInput-6a05ab61.js → SliderInput-5d926864.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-10ab48ed.js → Sort-3cdc971b.js} +9 -7
- flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
- flowfile/web/static/assets/{TextInput-df9d6259.js → TextInput-a2d0bfbd.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-000e1178.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-6c2d93d8.js → TextToRows-918945f7.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-0ff7ac52.js → ToggleSwitch-f0ef5196.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-c6dc3029.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
- flowfile/web/static/assets/{UnavailableFields-1bab97cb.js → UnavailableFields-bdad6144.js} +4 -4
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/{Union-b563478a.js → Union-e8ab8c86.js} +8 -6
- flowfile/web/static/assets/{Unique-f90db5db.js → Unique-8cd4f976.js} +13 -22
- flowfile/web/static/assets/{Sort-3643d625.css → Unique-9fb2f567.css} +10 -10
- flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-710a2948.css} +7 -7
- flowfile/web/static/assets/{Unpivot-bcb0025f.js → Unpivot-8da14095.js} +10 -8
- flowfile/web/static/assets/{UnpivotValidation-c4e73b04.js → UnpivotValidation-6f7d89ff.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-bb8535e2.js → VueGraphicWalker-3fb312e1.js} +4 -4
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{api-4c8e3822.js → api-24483f0d.js} +1 -1
- flowfile/web/static/assets/{api-2d6adc4f.js → api-8b81fa73.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
- flowfile/web/static/assets/{dropDown-1bca8a74.js → dropDown-ac0fda9d.js} +3 -3
- flowfile/web/static/assets/{fullEditor-2985687e.js → fullEditor-5497a84a.js} +11 -10
- flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-a0be62b3.css} +74 -62
- flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
- flowfile/web/static/assets/{genericNodeSettings-0476ba4e.js → genericNodeSettings-99014e1d.js} +5 -5
- flowfile/web/static/assets/index-07dda503.js +38 -0
- flowfile/web/static/assets/index-3ba44389.js +2696 -0
- flowfile/web/static/assets/{index-50508d4d.css → index-e6289dd0.css} +1945 -569
- flowfile/web/static/assets/{index-246f201c.js → index-fb6493ae.js} +41626 -40869
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
- flowfile/web/static/assets/{outputCsv-d686eeaf.js → outputCsv-8f8ba42d.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-8809ea2f.js → outputExcel-393f4fef.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/{outputParquet-53ba645a.js → outputParquet-07c81f65.js} +4 -4
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{readCsv-053bf97b.js → readCsv-07f6d9ad.js} +21 -20
- flowfile/web/static/assets/{readCsv-bca3ed53.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readExcel-e1b381ea.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-ad531eab.js → readExcel-ed69bc8f.js} +10 -12
- flowfile/web/static/assets/{readParquet-cee068e2.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/{readParquet-58e899a1.js → readParquet-e3ed4528.js} +4 -7
- flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
- flowfile/web/static/assets/{selectDynamic-b38de2ba.js → selectDynamic-80b92899.js} +5 -5
- flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
- flowfile/web/static/assets/{vue-codemirror.esm-db9b8936.js → vue-codemirror.esm-0965f39f.js} +31 -637
- flowfile/web/static/assets/{vue-content-loader.es-b5f3ac30.js → vue-content-loader.es-c506ad97.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +4 -4
- flowfile-0.5.3.dist-info/RECORD +402 -0
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +1 -1
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +1 -0
- flowfile_core/__init__.py +13 -3
- 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 +8 -6
- 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 +123 -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 +27 -24
- flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
- flowfile_core/flowfile/analytics/utils.py +1 -1
- flowfile_core/flowfile/code_generator/code_generator.py +391 -279
- 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 +152 -103
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +526 -477
- 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 +43 -32
- 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 +15 -11
- 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 +360 -191
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
- flowfile_core/flowfile/flow_data_engine/utils.py +101 -67
- flowfile_core/flowfile/flow_graph.py +1011 -561
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +332 -232
- flowfile_core/flowfile/flow_node/models.py +54 -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 +82 -32
- flowfile_core/flowfile/manage/compatibility_enhancements.py +493 -47
- flowfile_core/flowfile/manage/io_flowfile.py +391 -0
- flowfile_core/flowfile/node_designer/__init__.py +15 -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 +136 -35
- flowfile_core/flowfile/schema_callbacks.py +77 -54
- 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 +72 -55
- 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 +3 -3
- flowfile_core/routes/routes.py +77 -43
- 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 +59 -55
- flowfile_core/schemas/input_schema.py +398 -154
- flowfile_core/schemas/output_model.py +50 -35
- flowfile_core/schemas/schemas.py +207 -67
- flowfile_core/schemas/transform_schema.py +1360 -435
- flowfile_core/schemas/yaml_types.py +117 -0
- flowfile_core/secret_manager/secret_manager.py +17 -13
- flowfile_core/{flowfile/node_designer/data_types.py → types.py} +33 -3
- 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 +107 -50
- 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/expr.py +311 -218
- flowfile_frame/expr.pyi +160 -159
- flowfile_frame/expr_name.py +23 -23
- flowfile_frame/flow_frame.py +581 -489
- flowfile_frame/flow_frame.pyi +123 -104
- flowfile_frame/flow_frame_methods.py +236 -252
- 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 -4
- flowfile_worker/configs.py +11 -19
- flowfile_worker/create/__init__.py +14 -27
- flowfile_worker/create/funcs.py +143 -94
- flowfile_worker/create/models.py +139 -68
- 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 -93
- flowfile_worker/secrets.py +9 -6
- flowfile_worker/spawner.py +80 -49
- 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/README.md +56 -0
- tools/migrate/__init__.py +12 -0
- tools/migrate/__main__.py +118 -0
- tools/migrate/legacy_schemas.py +682 -0
- tools/migrate/migrate.py +610 -0
- tools/migrate/tests/__init__.py +0 -0
- tools/migrate/tests/conftest.py +21 -0
- tools/migrate/tests/test_migrate.py +622 -0
- tools/migrate/tests/test_migration_e2e.py +1009 -0
- tools/migrate/tests/test_node_migrations.py +843 -0
- flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
- flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
- flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
- flowfile/web/static/assets/Filter-812dcbca.js +0 -164
- flowfile/web/static/assets/Filter-f62091b3.css +0 -20
- flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
- flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
- flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
- flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
- flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
- flowfile/web/static/assets/secretApi-538058f3.js +0 -46
- flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
- flowfile-0.4.1.dist-info/RECORD +0 -376
- flowfile_core/flowfile/manage/open_flowfile.py +0 -143
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
- /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# Fixed custom_node.py with proper type hints
|
|
2
2
|
|
|
3
|
+
from typing import Any, TypeVar
|
|
4
|
+
|
|
3
5
|
import polars as pl
|
|
4
6
|
from pydantic import BaseModel
|
|
5
|
-
|
|
6
|
-
from flowfile_core.flowfile.node_designer.ui_components import
|
|
7
|
+
|
|
8
|
+
from flowfile_core.flowfile.node_designer.ui_components import (
|
|
9
|
+
FlowfileInComponent,
|
|
10
|
+
IncomingColumns,
|
|
11
|
+
SecretSelector,
|
|
12
|
+
Section,
|
|
13
|
+
)
|
|
7
14
|
from flowfile_core.schemas.schemas import NodeTemplate, NodeTypeLiteral, TransformTypeLiteral
|
|
8
15
|
|
|
9
16
|
|
|
@@ -22,9 +29,9 @@ def to_frontend_schema(model_instance: BaseModel) -> dict:
|
|
|
22
29
|
A dictionary representation of the model.
|
|
23
30
|
"""
|
|
24
31
|
result = {}
|
|
25
|
-
extra_fields = getattr(model_instance,
|
|
32
|
+
extra_fields = getattr(model_instance, "__pydantic_extra__", {})
|
|
26
33
|
model_fields = {k: getattr(model_instance, k) for k in model_instance.model_fields.keys()}
|
|
27
|
-
for key, value in (extra_fields|model_fields).items():
|
|
34
|
+
for key, value in (extra_fields | model_fields).items():
|
|
28
35
|
result[key] = _convert_value(value)
|
|
29
36
|
return result
|
|
30
37
|
|
|
@@ -34,25 +41,18 @@ def _convert_value(value: Any) -> Any:
|
|
|
34
41
|
Helper function to convert any value to a frontend-ready format.
|
|
35
42
|
"""
|
|
36
43
|
if isinstance(value, Section):
|
|
37
|
-
section_data = value.model_dump(
|
|
38
|
-
include={'title', 'description', 'hidden'},
|
|
39
|
-
exclude_none=True
|
|
40
|
-
)
|
|
44
|
+
section_data = value.model_dump(include={"title", "description", "hidden"}, exclude_none=True)
|
|
41
45
|
section_data["component_type"] = "Section"
|
|
42
|
-
section_data["components"] = {
|
|
43
|
-
key: _convert_value(comp)
|
|
44
|
-
for key, comp in value.get_components().items()
|
|
45
|
-
}
|
|
46
|
+
section_data["components"] = {key: _convert_value(comp) for key, comp in value.get_components().items()}
|
|
46
47
|
return section_data
|
|
47
48
|
|
|
48
49
|
elif isinstance(value, FlowfileInComponent):
|
|
49
50
|
component_dict = value.model_dump(exclude_none=True)
|
|
50
|
-
if
|
|
51
|
-
if component_dict[
|
|
52
|
-
|
|
53
|
-
issubclass(component_dict['options'], IncomingColumns)
|
|
51
|
+
if "options" in component_dict:
|
|
52
|
+
if component_dict["options"] is IncomingColumns or (
|
|
53
|
+
isinstance(component_dict["options"], type) and issubclass(component_dict["options"], IncomingColumns)
|
|
54
54
|
):
|
|
55
|
-
component_dict[
|
|
55
|
+
component_dict["options"] = {"__type__": "IncomingColumns"}
|
|
56
56
|
return component_dict
|
|
57
57
|
elif isinstance(value, BaseModel):
|
|
58
58
|
return to_frontend_schema(value)
|
|
@@ -67,7 +67,7 @@ def _convert_value(value: Any) -> Any:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
# Type variable for the Section factory
|
|
70
|
-
T = TypeVar(
|
|
70
|
+
T = TypeVar("T", bound=Section)
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def create_section(**components: FlowfileInComponent) -> Section:
|
|
@@ -104,17 +104,30 @@ class NodeSettings(BaseModel):
|
|
|
104
104
|
main_config = main_config_section
|
|
105
105
|
advanced_options = advanced_config_section
|
|
106
106
|
"""
|
|
107
|
+
|
|
107
108
|
class Config:
|
|
108
|
-
extra =
|
|
109
|
+
extra = "allow"
|
|
109
110
|
arbitrary_types_allowed = True
|
|
110
111
|
|
|
112
|
+
def has_sections(self) -> bool:
|
|
113
|
+
"""Check if this settings class has any sections defined."""
|
|
114
|
+
if self.model_fields:
|
|
115
|
+
return True
|
|
116
|
+
extra = getattr(self, "__pydantic_extra__", {})
|
|
117
|
+
return bool(extra)
|
|
118
|
+
|
|
119
|
+
def is_empty(self) -> bool:
|
|
120
|
+
"""Check if this is an empty settings class with no configuration."""
|
|
121
|
+
|
|
122
|
+
return not self.has_sections()
|
|
123
|
+
|
|
111
124
|
def __init__(self, **sections):
|
|
112
125
|
"""
|
|
113
126
|
Initialize NodeSettings with sections as keyword arguments.
|
|
114
127
|
"""
|
|
115
128
|
super().__init__(**sections)
|
|
116
129
|
|
|
117
|
-
def populate_values(self, values:
|
|
130
|
+
def populate_values(self, values: dict[str, Any]) -> "NodeSettings":
|
|
118
131
|
"""
|
|
119
132
|
Populates the settings with values received from the frontend.
|
|
120
133
|
|
|
@@ -133,7 +146,7 @@ class NodeSettings(BaseModel):
|
|
|
133
146
|
all_sections = {}
|
|
134
147
|
|
|
135
148
|
# Get extra fields
|
|
136
|
-
extra_fields = getattr(self,
|
|
149
|
+
extra_fields = getattr(self, "__pydantic_extra__", {})
|
|
137
150
|
all_sections.update(extra_fields)
|
|
138
151
|
|
|
139
152
|
# Get defined fields that are Sections
|
|
@@ -150,6 +163,80 @@ class NodeSettings(BaseModel):
|
|
|
150
163
|
component.set_value(section_values[component_name])
|
|
151
164
|
return self
|
|
152
165
|
|
|
166
|
+
def get_value(self, field_name: str) -> Any:
|
|
167
|
+
"""
|
|
168
|
+
Gets the current value of a field by name.
|
|
169
|
+
|
|
170
|
+
Searches through direct fields, extra fields, and sections.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
field_name: The name of the field to retrieve.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
The current value of the field, or None if not found.
|
|
177
|
+
"""
|
|
178
|
+
# Check direct model fields
|
|
179
|
+
if field_name in self.model_fields:
|
|
180
|
+
component = getattr(self, field_name, None)
|
|
181
|
+
if component is not None:
|
|
182
|
+
if isinstance(component, FlowfileInComponent):
|
|
183
|
+
return component.value
|
|
184
|
+
return component
|
|
185
|
+
|
|
186
|
+
# Check pydantic extra fields
|
|
187
|
+
extras = getattr(self, "__pydantic_extra__", {}) or {}
|
|
188
|
+
if field_name in extras:
|
|
189
|
+
component = extras[field_name]
|
|
190
|
+
if isinstance(component, FlowfileInComponent):
|
|
191
|
+
return component.value
|
|
192
|
+
return component
|
|
193
|
+
|
|
194
|
+
# Check within sections (both in model_fields and extras)
|
|
195
|
+
all_fields = {**{k: getattr(self, k) for k in self.model_fields}, **extras}
|
|
196
|
+
for value in all_fields.values():
|
|
197
|
+
if isinstance(value, Section):
|
|
198
|
+
components = value.get_components()
|
|
199
|
+
if field_name in components:
|
|
200
|
+
component = components[field_name]
|
|
201
|
+
if isinstance(component, FlowfileInComponent):
|
|
202
|
+
return component.value
|
|
203
|
+
return component
|
|
204
|
+
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
def get_all_components(self) -> dict[str, FlowfileInComponent]:
|
|
208
|
+
"""
|
|
209
|
+
Returns all UI components in the settings, including those nested in sections.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dictionary mapping field names to their FlowfileInComponent instances.
|
|
213
|
+
"""
|
|
214
|
+
components = {}
|
|
215
|
+
|
|
216
|
+
# Get from model fields
|
|
217
|
+
for field_name in self.model_fields:
|
|
218
|
+
value = getattr(self, field_name, None)
|
|
219
|
+
if isinstance(value, FlowfileInComponent):
|
|
220
|
+
components[field_name] = value
|
|
221
|
+
elif isinstance(value, Section):
|
|
222
|
+
components.update(value.get_components())
|
|
223
|
+
|
|
224
|
+
# Get from extra fields
|
|
225
|
+
extras = getattr(self, "__pydantic_extra__", {}) or {}
|
|
226
|
+
for field_name, value in extras.items():
|
|
227
|
+
if isinstance(value, FlowfileInComponent):
|
|
228
|
+
components[field_name] = value
|
|
229
|
+
elif isinstance(value, Section):
|
|
230
|
+
components.update(value.get_components())
|
|
231
|
+
|
|
232
|
+
return components
|
|
233
|
+
|
|
234
|
+
def set_secret_context(self, user_id: int, accessed_secrets: set):
|
|
235
|
+
"""Inject execution context into all SecretSelector components."""
|
|
236
|
+
for component in self.get_all_components().values():
|
|
237
|
+
if isinstance(component, SecretSelector):
|
|
238
|
+
component.set_execution_context(user_id, accessed_secrets)
|
|
239
|
+
|
|
153
240
|
|
|
154
241
|
def create_node_settings(**sections: Section) -> NodeSettings:
|
|
155
242
|
"""
|
|
@@ -187,13 +274,13 @@ class SectionBuilder:
|
|
|
187
274
|
advanced_section = builder.build()
|
|
188
275
|
"""
|
|
189
276
|
|
|
190
|
-
def __init__(self, title:
|
|
277
|
+
def __init__(self, title: str | None = None, description: str | None = None, hidden: bool = False):
|
|
191
278
|
self._section = Section(title=title, description=description, hidden=hidden)
|
|
192
279
|
|
|
193
|
-
def add_component(self, name: str, component: FlowfileInComponent) ->
|
|
280
|
+
def add_component(self, name: str, component: FlowfileInComponent) -> "SectionBuilder":
|
|
194
281
|
"""Add a component to the section."""
|
|
195
282
|
setattr(self._section, name, component)
|
|
196
|
-
extra = getattr(self._section,
|
|
283
|
+
extra = getattr(self._section, "__pydantic_extra__", {})
|
|
197
284
|
extra[name] = component
|
|
198
285
|
return self
|
|
199
286
|
|
|
@@ -219,10 +306,10 @@ class NodeSettingsBuilder:
|
|
|
219
306
|
def __init__(self):
|
|
220
307
|
self._settings = NodeSettings()
|
|
221
308
|
|
|
222
|
-
def add_section(self, name: str, section: Section) ->
|
|
309
|
+
def add_section(self, name: str, section: Section) -> "NodeSettingsBuilder":
|
|
223
310
|
"""Add a section to the node settings."""
|
|
224
311
|
setattr(self._settings, name, section)
|
|
225
|
-
extra = getattr(self._settings,
|
|
312
|
+
extra = getattr(self._settings, "__pydantic_extra__", {})
|
|
226
313
|
extra[name] = section
|
|
227
314
|
return self
|
|
228
315
|
|
|
@@ -238,25 +325,29 @@ class CustomNodeBase(BaseModel):
|
|
|
238
325
|
To create a new node, you should inherit from this class and define its
|
|
239
326
|
attributes and the `process` method.
|
|
240
327
|
"""
|
|
328
|
+
|
|
241
329
|
# Core node properties
|
|
242
330
|
node_name: str
|
|
243
331
|
node_category: str = "Custom"
|
|
244
332
|
node_icon: str = "user-defined-icon.png"
|
|
245
|
-
settings_schema:
|
|
333
|
+
settings_schema: NodeSettings | None = None
|
|
246
334
|
|
|
247
335
|
# I/O configuration
|
|
248
336
|
number_of_inputs: int = 1
|
|
249
337
|
number_of_outputs: int = 1
|
|
250
338
|
|
|
251
339
|
# Display properties in the UI
|
|
252
|
-
node_group:
|
|
253
|
-
title:
|
|
254
|
-
intro:
|
|
340
|
+
node_group: str | None = "custom"
|
|
341
|
+
title: str | None = "Custom Node"
|
|
342
|
+
intro: str | None = "A custom node for data processing"
|
|
255
343
|
|
|
256
344
|
# Behavior properties
|
|
257
345
|
node_type: NodeTypeLiteral = "process"
|
|
258
346
|
transform_type: TransformTypeLiteral = "wide"
|
|
259
347
|
|
|
348
|
+
_user_id: int | None = None
|
|
349
|
+
accessed_secrets: set[str] = set()
|
|
350
|
+
|
|
260
351
|
@property
|
|
261
352
|
def item(self):
|
|
262
353
|
"""A unique identifier for the node, derived from its name."""
|
|
@@ -269,11 +360,46 @@ class CustomNodeBase(BaseModel):
|
|
|
269
360
|
"""
|
|
270
361
|
Initialize the node, optionally populating settings from initial values.
|
|
271
362
|
"""
|
|
272
|
-
initial_values = data.pop(
|
|
363
|
+
initial_values = data.pop("initial_values", None)
|
|
273
364
|
super().__init__(**data)
|
|
274
365
|
if self.settings_schema and initial_values:
|
|
275
366
|
self.settings_schema.populate_values(initial_values)
|
|
276
367
|
|
|
368
|
+
def set_execution_context(self, user_id: int):
|
|
369
|
+
"""
|
|
370
|
+
Sets the execution context for the node.
|
|
371
|
+
Called by the framework before executing the node.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
user_id: The ID of the user executing this node.
|
|
375
|
+
"""
|
|
376
|
+
self._user_id = user_id
|
|
377
|
+
self.accessed_secrets = set()
|
|
378
|
+
|
|
379
|
+
def get_accessed_secrets(self) -> set[str]:
|
|
380
|
+
"""
|
|
381
|
+
Returns the set of secret values accessed during this execution.
|
|
382
|
+
Used by the output scanner to detect accidental leaks.
|
|
383
|
+
"""
|
|
384
|
+
return self.accessed_secrets.copy()
|
|
385
|
+
|
|
386
|
+
def get_secret_names(self) -> list[str]:
|
|
387
|
+
"""
|
|
388
|
+
Returns a list of all SecretSelector field names in the settings schema.
|
|
389
|
+
Useful for validation and debugging.
|
|
390
|
+
"""
|
|
391
|
+
from flowfile_core.flowfile.node_designer.ui_components import SecretSelector
|
|
392
|
+
|
|
393
|
+
if self.settings_schema is None:
|
|
394
|
+
return []
|
|
395
|
+
|
|
396
|
+
secret_fields = []
|
|
397
|
+
for name, field in self.settings_schema.get_all_components().items():
|
|
398
|
+
if isinstance(field, SecretSelector):
|
|
399
|
+
secret_fields.append(name)
|
|
400
|
+
|
|
401
|
+
return secret_fields
|
|
402
|
+
|
|
277
403
|
def get_frontend_schema(self) -> dict:
|
|
278
404
|
"""
|
|
279
405
|
Get the frontend-ready schema with current values.
|
|
@@ -303,20 +429,20 @@ class CustomNodeBase(BaseModel):
|
|
|
303
429
|
return schema
|
|
304
430
|
|
|
305
431
|
@classmethod
|
|
306
|
-
def from_frontend_schema(cls, schema: dict) ->
|
|
432
|
+
def from_frontend_schema(cls, schema: dict) -> "CustomNodeBase":
|
|
307
433
|
"""
|
|
308
434
|
Create a node instance from a frontend schema.
|
|
309
435
|
|
|
310
436
|
This is used when loading a node from a saved flow.
|
|
311
437
|
"""
|
|
312
|
-
settings_values = schema.pop(
|
|
438
|
+
settings_values = schema.pop("settings_schema", {})
|
|
313
439
|
node = cls(**schema)
|
|
314
440
|
if settings_values and node.settings_schema:
|
|
315
441
|
node.settings_schema.populate_values(settings_values)
|
|
316
442
|
return node
|
|
317
443
|
|
|
318
444
|
@classmethod
|
|
319
|
-
def from_settings(cls, settings_values: dict) ->
|
|
445
|
+
def from_settings(cls, settings_values: dict) -> "CustomNodeBase":
|
|
320
446
|
"""
|
|
321
447
|
Create a node instance with just its settings values.
|
|
322
448
|
|
|
@@ -327,7 +453,7 @@ class CustomNodeBase(BaseModel):
|
|
|
327
453
|
node.settings_schema.populate_values(settings_values)
|
|
328
454
|
return node
|
|
329
455
|
|
|
330
|
-
def update_settings(self, values:
|
|
456
|
+
def update_settings(self, values: dict[str, Any]) -> "CustomNodeBase":
|
|
331
457
|
"""
|
|
332
458
|
Update the settings with new values from the frontend.
|
|
333
459
|
"""
|
|
@@ -367,5 +493,5 @@ class CustomNodeBase(BaseModel):
|
|
|
367
493
|
drawer_intro=self.intro,
|
|
368
494
|
node_type=self.node_type,
|
|
369
495
|
transform_type=self.transform_type,
|
|
370
|
-
custom_node=True
|
|
496
|
+
custom_node=True,
|
|
371
497
|
)
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
from typing import Any, Literal
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
from pydantic import Field, BaseModel, computed_field
|
|
3
|
+
from pydantic import BaseModel, Field, SecretStr, computed_field
|
|
6
4
|
|
|
7
5
|
from flowfile_core.flowfile.node_designer._type_registry import normalize_type_spec
|
|
6
|
+
from flowfile_core.secret_manager.secret_manager import decrypt_secret, get_encrypted_secret
|
|
7
|
+
|
|
8
8
|
# Public API import
|
|
9
|
-
from flowfile_core.
|
|
9
|
+
from flowfile_core.types import DataType, TypeSpec
|
|
10
10
|
|
|
11
11
|
InputType = Literal["text", "number", "secret", "array", "date", "boolean"]
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def normalize_input_to_data_types(
|
|
15
|
-
v: Any
|
|
16
|
-
) -> Union[Literal["ALL"], List[DataType]]:
|
|
14
|
+
def normalize_input_to_data_types(v: Any) -> Literal["ALL"] | list[DataType]:
|
|
17
15
|
"""
|
|
18
16
|
Normalizes a wide variety of inputs to either 'ALL' or a sorted list of DataType enums.
|
|
19
17
|
This function is used as a Pydantic BeforeValidator.
|
|
@@ -46,9 +44,10 @@ class FlowfileInComponent(BaseModel):
|
|
|
46
44
|
It's not meant to be used directly, but rather to be inherited by specific
|
|
47
45
|
component classes.
|
|
48
46
|
"""
|
|
47
|
+
|
|
49
48
|
component_type: str = Field(..., description="Type of the UI component")
|
|
50
49
|
value: Any = None
|
|
51
|
-
label:
|
|
50
|
+
label: str | None = None
|
|
52
51
|
input_type: InputType
|
|
53
52
|
|
|
54
53
|
def set_value(self, value: Any):
|
|
@@ -83,6 +82,7 @@ class IncomingColumns:
|
|
|
83
82
|
options=IncomingColumns
|
|
84
83
|
)
|
|
85
84
|
"""
|
|
85
|
+
|
|
86
86
|
pass
|
|
87
87
|
|
|
88
88
|
|
|
@@ -94,25 +94,21 @@ class ColumnSelector(FlowfileInComponent):
|
|
|
94
94
|
This is particularly useful when a node operation should only be applied
|
|
95
95
|
to columns of a specific type (e.g., numeric, string, date).
|
|
96
96
|
"""
|
|
97
|
+
|
|
97
98
|
component_type: Literal["ColumnSelector"] = "ColumnSelector"
|
|
98
99
|
required: bool = False
|
|
99
100
|
multiple: bool = False
|
|
100
101
|
input_type: InputType = "text"
|
|
101
102
|
|
|
102
103
|
# Normalized output: either "ALL" or list of DataType enums
|
|
103
|
-
data_type_filter_input: TypeSpec = Field(
|
|
104
|
-
default="ALL",
|
|
105
|
-
alias="data_types",
|
|
106
|
-
repr=False,
|
|
107
|
-
exclude=True
|
|
108
|
-
)
|
|
104
|
+
data_type_filter_input: TypeSpec = Field(default="ALL", alias="data_types", repr=False, exclude=True)
|
|
109
105
|
|
|
110
106
|
class Config:
|
|
111
107
|
arbitrary_types_allowed = True
|
|
112
108
|
|
|
113
109
|
@computed_field
|
|
114
110
|
@property
|
|
115
|
-
def data_types_filter(self) ->
|
|
111
|
+
def data_types_filter(self) -> Literal["ALL"] | list[DataType]:
|
|
116
112
|
"""
|
|
117
113
|
A computed field that normalizes the `data_type_filter_input` into a
|
|
118
114
|
standardized format for the frontend.
|
|
@@ -125,16 +121,17 @@ class ColumnSelector(FlowfileInComponent):
|
|
|
125
121
|
correct format for the frontend.
|
|
126
122
|
"""
|
|
127
123
|
data = super().model_dump(**kwargs)
|
|
128
|
-
if
|
|
129
|
-
data[
|
|
124
|
+
if "data_types_filter" in data and data["data_types_filter"] != "ALL":
|
|
125
|
+
data["data_types"] = sorted([dt.value for dt in data["data_types_filter"]])
|
|
130
126
|
return data
|
|
131
127
|
|
|
132
128
|
|
|
133
129
|
class TextInput(FlowfileInComponent):
|
|
134
130
|
"""A standard text input field for capturing string values."""
|
|
131
|
+
|
|
135
132
|
component_type: Literal["TextInput"] = "TextInput"
|
|
136
|
-
default:
|
|
137
|
-
placeholder:
|
|
133
|
+
default: str | None = ""
|
|
134
|
+
placeholder: str | None = ""
|
|
138
135
|
input_type: InputType = "text"
|
|
139
136
|
|
|
140
137
|
def __init__(self, **data):
|
|
@@ -145,23 +142,43 @@ class TextInput(FlowfileInComponent):
|
|
|
145
142
|
|
|
146
143
|
class NumericInput(FlowfileInComponent):
|
|
147
144
|
"""A numeric input field with optional minimum and maximum value validation."""
|
|
145
|
+
|
|
148
146
|
component_type: Literal["NumericInput"] = "NumericInput"
|
|
149
|
-
default:
|
|
150
|
-
min_value:
|
|
151
|
-
max_value:
|
|
147
|
+
default: float | None = None
|
|
148
|
+
min_value: float | None = None
|
|
149
|
+
max_value: float | None = None
|
|
150
|
+
input_type: InputType = "number"
|
|
151
|
+
|
|
152
|
+
def __init__(self, **data):
|
|
153
|
+
super().__init__(**data)
|
|
154
|
+
if self.value is None and self.default is not None:
|
|
155
|
+
self.value = self.default
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class SliderInput(FlowfileInComponent):
|
|
159
|
+
"""A slider input for selecting a numeric value within a range."""
|
|
160
|
+
|
|
161
|
+
component_type: Literal["SliderInput"] = "SliderInput"
|
|
162
|
+
default: float | None = None
|
|
163
|
+
min_value: float = 0
|
|
164
|
+
max_value: float = 100
|
|
165
|
+
step: float = 1
|
|
152
166
|
input_type: InputType = "number"
|
|
153
167
|
|
|
154
168
|
def __init__(self, **data):
|
|
155
169
|
super().__init__(**data)
|
|
156
170
|
if self.value is None and self.default is not None:
|
|
157
171
|
self.value = self.default
|
|
172
|
+
elif self.value is None:
|
|
173
|
+
self.value = self.min_value
|
|
158
174
|
|
|
159
175
|
|
|
160
176
|
class ToggleSwitch(FlowfileInComponent):
|
|
161
177
|
"""A boolean toggle switch, typically used for enabling or disabling a feature."""
|
|
178
|
+
|
|
162
179
|
component_type: Literal["ToggleSwitch"] = "ToggleSwitch"
|
|
163
180
|
default: bool = False
|
|
164
|
-
description:
|
|
181
|
+
description: str | None = None
|
|
165
182
|
input_type: InputType = "boolean"
|
|
166
183
|
|
|
167
184
|
def __init__(self, **data):
|
|
@@ -182,9 +199,10 @@ class SingleSelect(FlowfileInComponent):
|
|
|
182
199
|
dynamically populated from the input dataframe's columns by using the
|
|
183
200
|
`IncomingColumns` marker.
|
|
184
201
|
"""
|
|
202
|
+
|
|
185
203
|
component_type: Literal["SingleSelect"] = "SingleSelect"
|
|
186
|
-
options:
|
|
187
|
-
default:
|
|
204
|
+
options: list[str | tuple[str, Any]] | type[IncomingColumns]
|
|
205
|
+
default: Any | None = None
|
|
188
206
|
input_type: InputType = "text"
|
|
189
207
|
|
|
190
208
|
def __init__(self, **data):
|
|
@@ -200,9 +218,10 @@ class MultiSelect(FlowfileInComponent):
|
|
|
200
218
|
Like `SingleSelect`, the options can be static or dynamically populated
|
|
201
219
|
from the input columns using the `IncomingColumns` marker.
|
|
202
220
|
"""
|
|
221
|
+
|
|
203
222
|
component_type: Literal["MultiSelect"] = "MultiSelect"
|
|
204
|
-
options:
|
|
205
|
-
default:
|
|
223
|
+
options: list[str | tuple[str, Any]] | type[IncomingColumns]
|
|
224
|
+
default: list[Any] = Field(default_factory=list)
|
|
206
225
|
input_type: InputType = "array"
|
|
207
226
|
|
|
208
227
|
def __init__(self, **data):
|
|
@@ -226,12 +245,13 @@ class Section(BaseModel):
|
|
|
226
245
|
my_text_input=TextInput(label="Enter a value")
|
|
227
246
|
)
|
|
228
247
|
"""
|
|
229
|
-
|
|
230
|
-
|
|
248
|
+
|
|
249
|
+
title: str | None = None
|
|
250
|
+
description: str | None = None
|
|
231
251
|
hidden: bool = False
|
|
232
252
|
|
|
233
253
|
class Config:
|
|
234
|
-
extra =
|
|
254
|
+
extra = "allow"
|
|
235
255
|
arbitrary_types_allowed = True
|
|
236
256
|
|
|
237
257
|
def __init__(self, **data):
|
|
@@ -240,7 +260,7 @@ class Section(BaseModel):
|
|
|
240
260
|
"""
|
|
241
261
|
super().__init__(**data)
|
|
242
262
|
|
|
243
|
-
def __call__(self, **kwargs) ->
|
|
263
|
+
def __call__(self, **kwargs) -> "Section":
|
|
244
264
|
"""
|
|
245
265
|
Allows adding components to the section after initialization.
|
|
246
266
|
|
|
@@ -250,7 +270,7 @@ class Section(BaseModel):
|
|
|
250
270
|
setattr(self, key, value)
|
|
251
271
|
return self
|
|
252
272
|
|
|
253
|
-
def get_components(self) ->
|
|
273
|
+
def get_components(self) -> dict[str, FlowfileInComponent]:
|
|
254
274
|
"""
|
|
255
275
|
Get all FlowfileInComponent instances from the section.
|
|
256
276
|
|
|
@@ -263,15 +283,96 @@ class Section(BaseModel):
|
|
|
263
283
|
components = {}
|
|
264
284
|
|
|
265
285
|
# Get from extra fields
|
|
266
|
-
for key, value in getattr(self,
|
|
286
|
+
for key, value in getattr(self, "__pydantic_extra__", {}).items():
|
|
267
287
|
if isinstance(value, FlowfileInComponent):
|
|
268
288
|
components[key] = value
|
|
269
289
|
|
|
270
290
|
# Get from defined fields (excluding metadata)
|
|
271
291
|
for field_name in self.model_fields:
|
|
272
|
-
if field_name not in {
|
|
292
|
+
if field_name not in {"title", "description", "hidden"}:
|
|
273
293
|
value = getattr(self, field_name, None)
|
|
274
294
|
if isinstance(value, FlowfileInComponent):
|
|
275
295
|
components[field_name] = value
|
|
276
296
|
|
|
277
297
|
return components
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class AvailableSecrets:
|
|
301
|
+
"""
|
|
302
|
+
A marker class used in `SecretSelector` components.
|
|
303
|
+
|
|
304
|
+
When `options` is set to this class, the component will be dynamically
|
|
305
|
+
populated with the secret names available to the current user.
|
|
306
|
+
This allows users to select from available secrets at runtime.
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
class MyNodeSettings(NodeSettings):
|
|
310
|
+
api_key = SecretSelector(
|
|
311
|
+
label="Select an API Key",
|
|
312
|
+
options=AvailableSecrets
|
|
313
|
+
)
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class SecretSelector(FlowfileInComponent):
|
|
320
|
+
component_type: Literal["SecretSelector"] = "SecretSelector"
|
|
321
|
+
options: type[AvailableSecrets] = AvailableSecrets
|
|
322
|
+
required: bool = False
|
|
323
|
+
description: str | None = None
|
|
324
|
+
input_type: InputType = "secret"
|
|
325
|
+
name_prefix: str | None = None
|
|
326
|
+
|
|
327
|
+
# Private fields for runtime context
|
|
328
|
+
_user_id: int | None = None
|
|
329
|
+
_accessed_secrets: set | None = None # Reference to node's tracking set
|
|
330
|
+
|
|
331
|
+
def set_execution_context(self, user_id: int, accessed_secrets: set):
|
|
332
|
+
"""Called by framework before process() runs."""
|
|
333
|
+
self._user_id = user_id
|
|
334
|
+
self._accessed_secrets = accessed_secrets
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def secret_value(self) -> SecretStr | None:
|
|
338
|
+
"""
|
|
339
|
+
Get the decrypted secret value.
|
|
340
|
+
|
|
341
|
+
Can only be called during node execution (after context is set).
|
|
342
|
+
Returns None if no secret is selected.
|
|
343
|
+
"""
|
|
344
|
+
if self.value is None:
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
if self._user_id is None:
|
|
348
|
+
raise ValueError(
|
|
349
|
+
"Secret can only be accessed during node execution. "
|
|
350
|
+
"Ensure you're calling this from within the process() method."
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
encrypted = get_encrypted_secret(current_user_id=self._user_id, secret_name=self.value)
|
|
354
|
+
|
|
355
|
+
if encrypted is None:
|
|
356
|
+
raise ValueError(
|
|
357
|
+
f"Secret '{self.value}' not found for user. " f"Please ensure the secret exists in your secrets store."
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
decrypted = decrypt_secret(encrypted)
|
|
361
|
+
|
|
362
|
+
if self._accessed_secrets is not None:
|
|
363
|
+
self._accessed_secrets.add(decrypted.get_secret_value())
|
|
364
|
+
else:
|
|
365
|
+
self._accessed_secrets = {decrypted.get_secret_value()}
|
|
366
|
+
return decrypted
|
|
367
|
+
|
|
368
|
+
def model_dump(self, **kwargs) -> dict:
|
|
369
|
+
"""
|
|
370
|
+
Overrides the default `model_dump` to signal to the frontend
|
|
371
|
+
that this needs dynamic population from available secrets.
|
|
372
|
+
"""
|
|
373
|
+
data = super().model_dump(**kwargs)
|
|
374
|
+
# Signal to frontend that options should be fetched from /secrets endpoint
|
|
375
|
+
data["options"] = {"__type__": "AvailableSecrets"}
|
|
376
|
+
if self.name_prefix:
|
|
377
|
+
data["name_prefix"] = self.name_prefix
|
|
378
|
+
return data
|