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,120 +1,125 @@
|
|
|
1
|
-
from typing import List, Optional, Literal, Iterator, Any
|
|
2
|
-
from flowfile_core.schemas import transform_schema
|
|
3
|
-
from pathlib import Path
|
|
4
1
|
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated, Any, Literal
|
|
4
|
+
|
|
5
|
+
import polars as pl
|
|
6
|
+
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
ConfigDict,
|
|
9
|
+
Field,
|
|
10
|
+
SecretStr,
|
|
11
|
+
StringConstraints,
|
|
12
|
+
ValidationInfo,
|
|
13
|
+
field_validator,
|
|
14
|
+
model_validator,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from flowfile_core.schemas import transform_schema
|
|
5
18
|
from flowfile_core.schemas.analysis_schemas import graphic_walker_schemas as gs_schemas
|
|
6
19
|
from flowfile_core.schemas.cloud_storage_schemas import CloudStorageReadSettings, CloudStorageWriteSettings
|
|
7
|
-
from flowfile_core.schemas.
|
|
20
|
+
from flowfile_core.schemas.yaml_types import (
|
|
21
|
+
NodeCrossJoinYaml,
|
|
22
|
+
NodeFuzzyMatchYaml,
|
|
23
|
+
NodeJoinYaml,
|
|
24
|
+
NodeOutputYaml,
|
|
25
|
+
NodeSelectYaml,
|
|
26
|
+
OutputSettingsYaml,
|
|
27
|
+
)
|
|
8
28
|
from flowfile_core.utils.utils import ensure_similarity_dicts, standardize_col_dtype
|
|
9
|
-
from pydantic import BaseModel, Field, model_validator, SecretStr, ConfigDict
|
|
10
|
-
import polars as pl
|
|
11
29
|
|
|
30
|
+
SecretRef = Annotated[
|
|
31
|
+
str, StringConstraints(min_length=1, max_length=100), Field(description="An ID referencing an encrypted secret.")
|
|
32
|
+
]
|
|
12
33
|
|
|
13
|
-
OutputConnectionClass = Literal['output-0', 'output-1', 'output-2', 'output-3', 'output-4',
|
|
14
|
-
'output-5', 'output-6', 'output-7', 'output-8', 'output-9']
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
35
|
+
OutputConnectionClass = Literal[
|
|
36
|
+
"output-0",
|
|
37
|
+
"output-1",
|
|
38
|
+
"output-2",
|
|
39
|
+
"output-3",
|
|
40
|
+
"output-4",
|
|
41
|
+
"output-5",
|
|
42
|
+
"output-6",
|
|
43
|
+
"output-7",
|
|
44
|
+
"output-8",
|
|
45
|
+
"output-9",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
InputConnectionClass = Literal[
|
|
49
|
+
"input-0", "input-1", "input-2", "input-3", "input-4", "input-5", "input-6", "input-7", "input-8", "input-9"
|
|
50
|
+
]
|
|
18
51
|
|
|
19
52
|
InputType = Literal["main", "left", "right"]
|
|
20
53
|
|
|
21
54
|
|
|
22
55
|
class NewDirectory(BaseModel):
|
|
23
56
|
"""Defines the information required to create a new directory."""
|
|
57
|
+
|
|
24
58
|
source_path: str
|
|
25
59
|
dir_name: str
|
|
26
60
|
|
|
27
61
|
|
|
28
62
|
class RemoveItem(BaseModel):
|
|
29
63
|
"""Represents a single item to be removed from a directory or list."""
|
|
64
|
+
|
|
30
65
|
path: str
|
|
31
66
|
id: int = -1
|
|
32
67
|
|
|
33
68
|
|
|
34
69
|
class RemoveItemsInput(BaseModel):
|
|
35
70
|
"""Defines a list of items to be removed."""
|
|
36
|
-
|
|
71
|
+
|
|
72
|
+
paths: list[RemoveItem]
|
|
37
73
|
source_path: str
|
|
38
74
|
|
|
39
75
|
|
|
40
76
|
class MinimalFieldInfo(BaseModel):
|
|
41
77
|
"""Represents the most basic information about a data field (column)."""
|
|
78
|
+
|
|
42
79
|
name: str
|
|
43
80
|
data_type: str = "String"
|
|
44
81
|
|
|
45
82
|
|
|
46
|
-
class
|
|
47
|
-
"""Base
|
|
48
|
-
id: Optional[int] = None
|
|
49
|
-
name: Optional[str]
|
|
50
|
-
path: str # This can be an absolute or relative path
|
|
51
|
-
directory: Optional[str] = None
|
|
52
|
-
analysis_file_available: bool = False
|
|
53
|
-
status: Optional[str] = None
|
|
54
|
-
file_type: Optional[str] = None
|
|
55
|
-
fields: List[MinimalFieldInfo] = Field(default_factory=list)
|
|
56
|
-
abs_file_path: Optional[str] = None
|
|
57
|
-
|
|
58
|
-
@classmethod
|
|
59
|
-
def create_from_path(cls, path: str):
|
|
60
|
-
"""Creates an instance from a file path string."""
|
|
61
|
-
filename = Path(path).name
|
|
62
|
-
return cls(name=filename, path=path)
|
|
83
|
+
class InputTableBase(BaseModel):
|
|
84
|
+
"""Base settings for input file operations."""
|
|
63
85
|
|
|
64
|
-
|
|
65
|
-
def file_path(self) -> str:
|
|
66
|
-
"""Constructs the full file path from the directory and name."""
|
|
67
|
-
if not self.name in self.path:
|
|
68
|
-
return os.path.join(self.path, self.name)
|
|
69
|
-
else:
|
|
70
|
-
return self.path
|
|
71
|
-
|
|
72
|
-
def set_absolute_filepath(self):
|
|
73
|
-
"""Resolves the path to an absolute file path."""
|
|
74
|
-
base_path = Path(self.path).expanduser()
|
|
75
|
-
if not base_path.is_absolute():
|
|
76
|
-
base_path = Path.cwd() / base_path
|
|
77
|
-
if self.name and self.name not in base_path.name:
|
|
78
|
-
base_path = base_path / self.name
|
|
79
|
-
self.abs_file_path = str(base_path.resolve())
|
|
80
|
-
|
|
81
|
-
@model_validator(mode='after')
|
|
82
|
-
def populate_abs_file_path(self):
|
|
83
|
-
"""Ensures the absolute file path is populated after validation."""
|
|
84
|
-
if not self.abs_file_path:
|
|
85
|
-
self.set_absolute_filepath()
|
|
86
|
-
return self
|
|
86
|
+
file_type: str # Will be overridden with Literal in subclasses
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
class
|
|
89
|
+
class InputCsvTable(InputTableBase):
|
|
90
90
|
"""Defines settings for reading a CSV file."""
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
|
|
92
|
+
file_type: Literal["csv"] = "csv"
|
|
93
|
+
reference: str = ""
|
|
93
94
|
starting_from_line: int = 0
|
|
94
|
-
delimiter: str =
|
|
95
|
+
delimiter: str = ","
|
|
95
96
|
has_headers: bool = True
|
|
96
|
-
encoding:
|
|
97
|
-
parquet_ref:
|
|
98
|
-
row_delimiter: str =
|
|
97
|
+
encoding: str = "utf-8"
|
|
98
|
+
parquet_ref: str | None = None
|
|
99
|
+
row_delimiter: str = "\n"
|
|
99
100
|
quote_char: str = '"'
|
|
100
101
|
infer_schema_length: int = 10_000
|
|
101
102
|
truncate_ragged_lines: bool = False
|
|
102
103
|
ignore_errors: bool = False
|
|
103
104
|
|
|
104
105
|
|
|
105
|
-
class
|
|
106
|
-
"""Defines settings for reading a JSON file
|
|
107
|
-
pass
|
|
106
|
+
class InputJsonTable(InputCsvTable):
|
|
107
|
+
"""Defines settings for reading a JSON file."""
|
|
108
108
|
|
|
109
|
+
file_type: Literal["json"] = "json"
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
|
|
112
|
+
class InputParquetTable(InputTableBase):
|
|
111
113
|
"""Defines settings for reading a Parquet file."""
|
|
112
|
-
|
|
114
|
+
|
|
115
|
+
file_type: Literal["parquet"] = "parquet"
|
|
113
116
|
|
|
114
117
|
|
|
115
|
-
class
|
|
118
|
+
class InputExcelTable(InputTableBase):
|
|
116
119
|
"""Defines settings for reading an Excel file."""
|
|
117
|
-
|
|
120
|
+
|
|
121
|
+
file_type: Literal["excel"] = "excel"
|
|
122
|
+
sheet_name: str | None = None
|
|
118
123
|
start_row: int = 0
|
|
119
124
|
start_column: int = 0
|
|
120
125
|
end_row: int = 0
|
|
@@ -122,50 +127,185 @@ class ReceivedExcelTable(ReceivedTableBase):
|
|
|
122
127
|
has_headers: bool = True
|
|
123
128
|
type_inference: bool = False
|
|
124
129
|
|
|
130
|
+
@model_validator(mode="after")
|
|
125
131
|
def validate_range_values(self):
|
|
126
132
|
"""Validates that the Excel cell range is logical."""
|
|
127
133
|
for attribute in [self.start_row, self.start_column, self.end_row, self.end_column]:
|
|
128
134
|
if not isinstance(attribute, int) or attribute < 0:
|
|
129
135
|
raise ValueError("Row and column indices must be non-negative integers")
|
|
130
|
-
if (self.end_row > 0 and self.start_row > self.end_row) or
|
|
131
|
-
|
|
136
|
+
if (self.end_row > 0 and self.start_row > self.end_row) or (
|
|
137
|
+
self.end_column > 0 and self.start_column > self.end_column
|
|
138
|
+
):
|
|
132
139
|
raise ValueError("Start row/column must not be greater than end row/column")
|
|
140
|
+
return self
|
|
141
|
+
|
|
133
142
|
|
|
143
|
+
# Create the discriminated union (similar to OutputTableSettings)
|
|
144
|
+
InputTableSettings = Annotated[
|
|
145
|
+
InputCsvTable | InputJsonTable | InputParquetTable | InputExcelTable, Field(discriminator="file_type")
|
|
146
|
+
]
|
|
134
147
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
|
|
149
|
+
# Now create the main ReceivedTable model
|
|
150
|
+
class ReceivedTable(BaseModel):
|
|
151
|
+
"""Model for defining a table received from an external source."""
|
|
152
|
+
|
|
153
|
+
# Metadata fields
|
|
154
|
+
id: int | None = None
|
|
155
|
+
name: str | None = None
|
|
156
|
+
path: str # This can be an absolute or relative path
|
|
157
|
+
directory: str | None = None
|
|
158
|
+
analysis_file_available: bool = False
|
|
159
|
+
status: str | None = None
|
|
160
|
+
fields: list[MinimalFieldInfo] = Field(default_factory=list)
|
|
161
|
+
abs_file_path: str | None = None
|
|
162
|
+
|
|
163
|
+
file_type: Literal["csv", "json", "parquet", "excel"]
|
|
164
|
+
|
|
165
|
+
table_settings: InputTableSettings
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def create_from_path(cls, path: str, file_type: Literal["csv", "json", "parquet", "excel"] = "csv"):
|
|
169
|
+
"""Creates an instance from a file path string."""
|
|
170
|
+
filename = Path(path).name
|
|
171
|
+
|
|
172
|
+
# Create appropriate table_settings based on file_type
|
|
173
|
+
settings_map = {
|
|
174
|
+
"csv": InputCsvTable(),
|
|
175
|
+
"json": InputJsonTable(),
|
|
176
|
+
"parquet": InputParquetTable(),
|
|
177
|
+
"excel": InputExcelTable(),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return cls(
|
|
181
|
+
name=filename, path=path, file_type=file_type, table_settings=settings_map.get(file_type, InputCsvTable())
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def file_path(self) -> str:
|
|
186
|
+
"""Constructs the full file path from the directory and name."""
|
|
187
|
+
if self.name and self.name not in self.path:
|
|
188
|
+
return os.path.join(self.path, self.name)
|
|
189
|
+
else:
|
|
190
|
+
return self.path
|
|
191
|
+
|
|
192
|
+
def set_absolute_filepath(self):
|
|
193
|
+
"""Resolves the path to an absolute file path."""
|
|
194
|
+
base_path = Path(self.path).expanduser()
|
|
195
|
+
if not base_path.is_absolute():
|
|
196
|
+
base_path = Path.cwd() / base_path
|
|
197
|
+
if self.name and self.name not in base_path.name:
|
|
198
|
+
base_path = base_path / self.name
|
|
199
|
+
self.abs_file_path = str(base_path.resolve())
|
|
200
|
+
|
|
201
|
+
@model_validator(mode="before")
|
|
202
|
+
@classmethod
|
|
203
|
+
def set_default_table_settings(cls, data):
|
|
204
|
+
"""Create default table_settings based on file_type if not provided."""
|
|
205
|
+
if isinstance(data, dict):
|
|
206
|
+
if "table_settings" not in data or data["table_settings"] is None:
|
|
207
|
+
data["table_settings"] = {}
|
|
208
|
+
|
|
209
|
+
if isinstance(data["table_settings"], dict) and "file_type" not in data["table_settings"]:
|
|
210
|
+
data["table_settings"]["file_type"] = data.get("file_type", "csv")
|
|
211
|
+
return data
|
|
212
|
+
|
|
213
|
+
@model_validator(mode="after")
|
|
214
|
+
def populate_abs_file_path(self):
|
|
215
|
+
"""Ensures the absolute file path is populated after validation."""
|
|
216
|
+
if not self.abs_file_path:
|
|
217
|
+
self.set_absolute_filepath()
|
|
218
|
+
return self
|
|
138
219
|
|
|
139
220
|
|
|
140
221
|
class OutputCsvTable(BaseModel):
|
|
141
222
|
"""Defines settings for writing a CSV file."""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
223
|
+
|
|
224
|
+
file_type: Literal["csv"] = "csv"
|
|
225
|
+
delimiter: str = ","
|
|
226
|
+
encoding: str = "utf-8"
|
|
145
227
|
|
|
146
228
|
|
|
147
229
|
class OutputParquetTable(BaseModel):
|
|
148
230
|
"""Defines settings for writing a Parquet file."""
|
|
149
|
-
|
|
231
|
+
|
|
232
|
+
file_type: Literal["parquet"] = "parquet"
|
|
150
233
|
|
|
151
234
|
|
|
152
235
|
class OutputExcelTable(BaseModel):
|
|
153
236
|
"""Defines settings for writing an Excel file."""
|
|
154
|
-
|
|
155
|
-
|
|
237
|
+
|
|
238
|
+
file_type: Literal["excel"] = "excel"
|
|
239
|
+
sheet_name: str = "Sheet1"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Create a discriminated union
|
|
243
|
+
OutputTableSettings = Annotated[
|
|
244
|
+
OutputCsvTable | OutputParquetTable | OutputExcelTable, Field(discriminator="file_type")
|
|
245
|
+
]
|
|
156
246
|
|
|
157
247
|
|
|
158
248
|
class OutputSettings(BaseModel):
|
|
159
249
|
"""Defines the complete settings for an output node."""
|
|
250
|
+
|
|
160
251
|
name: str
|
|
161
252
|
directory: str
|
|
162
|
-
file_type: str
|
|
163
|
-
fields:
|
|
164
|
-
write_mode: str =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
253
|
+
file_type: str # This drives which table_settings to use
|
|
254
|
+
fields: list[str] | None = Field(default_factory=list)
|
|
255
|
+
write_mode: str = "overwrite"
|
|
256
|
+
table_settings: OutputTableSettings
|
|
257
|
+
abs_file_path: str | None = None
|
|
258
|
+
|
|
259
|
+
def to_yaml_dict(self) -> OutputSettingsYaml:
|
|
260
|
+
"""Converts the output settings to a dictionary suitable for YAML serialization."""
|
|
261
|
+
result: OutputSettingsYaml = {
|
|
262
|
+
"name": self.name,
|
|
263
|
+
"directory": self.directory,
|
|
264
|
+
"file_type": self.file_type,
|
|
265
|
+
"write_mode": self.write_mode,
|
|
266
|
+
}
|
|
267
|
+
if self.abs_file_path:
|
|
268
|
+
result["abs_file_path"] = self.abs_file_path
|
|
269
|
+
if self.fields:
|
|
270
|
+
result["fields"] = self.fields
|
|
271
|
+
# Only include table_settings if it has non-default values beyond file_type
|
|
272
|
+
ts_dict = self.table_settings.model_dump(exclude={"file_type"})
|
|
273
|
+
if any(v for v in ts_dict.values()): # Has meaningful settings
|
|
274
|
+
result["table_settings"] = ts_dict
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def sheet_name(self) -> str | None:
|
|
279
|
+
if self.file_type == "excel":
|
|
280
|
+
return self.table_settings.sheet_name
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def delimiter(self) -> str | None:
|
|
284
|
+
if self.file_type == "csv":
|
|
285
|
+
return self.table_settings.delimiter
|
|
286
|
+
|
|
287
|
+
@field_validator("table_settings", mode="before")
|
|
288
|
+
@classmethod
|
|
289
|
+
def validate_table_settings(cls, v, info: ValidationInfo):
|
|
290
|
+
"""Ensures table_settings matches the file_type."""
|
|
291
|
+
if v is None:
|
|
292
|
+
file_type = info.data.get("file_type", "csv")
|
|
293
|
+
# Create default based on file_type
|
|
294
|
+
match file_type:
|
|
295
|
+
case "csv":
|
|
296
|
+
return OutputCsvTable()
|
|
297
|
+
case "parquet":
|
|
298
|
+
return OutputParquetTable()
|
|
299
|
+
case "excel":
|
|
300
|
+
return OutputExcelTable()
|
|
301
|
+
case _:
|
|
302
|
+
return OutputCsvTable()
|
|
303
|
+
|
|
304
|
+
# If it's a dict, add file_type if missing
|
|
305
|
+
if isinstance(v, dict) and "file_type" not in v:
|
|
306
|
+
v["file_type"] = info.data.get("file_type", "csv")
|
|
307
|
+
|
|
308
|
+
return v
|
|
169
309
|
|
|
170
310
|
def set_absolute_filepath(self):
|
|
171
311
|
"""Resolves the output directory and name into an absolute path."""
|
|
@@ -176,7 +316,7 @@ class OutputSettings(BaseModel):
|
|
|
176
316
|
base_path = base_path / self.name
|
|
177
317
|
self.abs_file_path = str(base_path.resolve())
|
|
178
318
|
|
|
179
|
-
@model_validator(mode=
|
|
319
|
+
@model_validator(mode="after")
|
|
180
320
|
def populate_abs_file_path(self):
|
|
181
321
|
"""Ensures the absolute file path is populated after validation."""
|
|
182
322
|
self.set_absolute_filepath()
|
|
@@ -185,63 +325,82 @@ class OutputSettings(BaseModel):
|
|
|
185
325
|
|
|
186
326
|
class NodeBase(BaseModel):
|
|
187
327
|
"""Base model for all nodes in a FlowGraph. Contains common metadata."""
|
|
328
|
+
|
|
188
329
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
189
330
|
flow_id: int
|
|
190
331
|
node_id: int
|
|
191
|
-
cache_results:
|
|
192
|
-
pos_x:
|
|
193
|
-
pos_y:
|
|
194
|
-
is_setup:
|
|
195
|
-
description:
|
|
196
|
-
user_id:
|
|
197
|
-
is_flow_output:
|
|
198
|
-
is_user_defined:
|
|
332
|
+
cache_results: bool | None = False
|
|
333
|
+
pos_x: float | None = 0
|
|
334
|
+
pos_y: float | None = 0
|
|
335
|
+
is_setup: bool | None = True
|
|
336
|
+
description: str | None = ""
|
|
337
|
+
user_id: int | None = None
|
|
338
|
+
is_flow_output: bool | None = False
|
|
339
|
+
is_user_defined: bool | None = False # Indicator if the node is a user defined node
|
|
199
340
|
|
|
200
341
|
|
|
201
342
|
class NodeSingleInput(NodeBase):
|
|
202
343
|
"""A base model for any node that takes a single data input."""
|
|
203
|
-
|
|
344
|
+
|
|
345
|
+
depending_on_id: int | None = -1
|
|
204
346
|
|
|
205
347
|
|
|
206
348
|
class NodeMultiInput(NodeBase):
|
|
207
349
|
"""A base model for any node that takes multiple data inputs."""
|
|
208
|
-
|
|
350
|
+
|
|
351
|
+
depending_on_ids: list[int] | None = Field(default_factory=list)
|
|
209
352
|
|
|
210
353
|
|
|
211
354
|
class NodeSelect(NodeSingleInput):
|
|
212
355
|
"""Settings for a node that selects, renames, and reorders columns."""
|
|
356
|
+
|
|
213
357
|
keep_missing: bool = True
|
|
214
|
-
select_input:
|
|
215
|
-
sorted_by:
|
|
358
|
+
select_input: list[transform_schema.SelectInput] = Field(default_factory=list)
|
|
359
|
+
sorted_by: Literal["none", "asc", "desc"] | None = "none"
|
|
360
|
+
|
|
361
|
+
def to_yaml_dict(self) -> NodeSelectYaml:
|
|
362
|
+
"""Converts the select node settings to a dictionary for YAML serialization."""
|
|
363
|
+
return {
|
|
364
|
+
"cache_results": self.cache_results,
|
|
365
|
+
"keep_missing": self.keep_missing,
|
|
366
|
+
"select_input": [s.to_yaml_dict() for s in self.select_input],
|
|
367
|
+
"sorted_by": self.sorted_by,
|
|
368
|
+
}
|
|
216
369
|
|
|
217
370
|
|
|
218
371
|
class NodeFilter(NodeSingleInput):
|
|
219
372
|
"""Settings for a node that filters rows based on a condition."""
|
|
373
|
+
|
|
220
374
|
filter_input: transform_schema.FilterInput
|
|
221
375
|
|
|
222
376
|
|
|
223
377
|
class NodeSort(NodeSingleInput):
|
|
224
378
|
"""Settings for a node that sorts the data by one or more columns."""
|
|
225
|
-
|
|
379
|
+
|
|
380
|
+
sort_input: list[transform_schema.SortByInput] = Field(default_factory=list)
|
|
226
381
|
|
|
227
382
|
|
|
228
383
|
class NodeTextToRows(NodeSingleInput):
|
|
229
384
|
"""Settings for a node that splits a text column into multiple rows."""
|
|
385
|
+
|
|
230
386
|
text_to_rows_input: transform_schema.TextToRowsInput
|
|
231
387
|
|
|
232
388
|
|
|
233
389
|
class NodeSample(NodeSingleInput):
|
|
234
390
|
"""Settings for a node that samples a subset of the data."""
|
|
391
|
+
|
|
235
392
|
sample_size: int = 1000
|
|
236
393
|
|
|
237
394
|
|
|
238
395
|
class NodeRecordId(NodeSingleInput):
|
|
239
396
|
"""Settings for a node that adds a unique record ID column."""
|
|
397
|
+
|
|
240
398
|
record_id_input: transform_schema.RecordIdInput
|
|
241
399
|
|
|
242
400
|
|
|
243
401
|
class NodeJoin(NodeMultiInput):
|
|
244
402
|
"""Settings for a node that performs a standard SQL-style join."""
|
|
403
|
+
|
|
245
404
|
auto_generate_selection: bool = True
|
|
246
405
|
verify_integrity: bool = True
|
|
247
406
|
join_input: transform_schema.JoinInput
|
|
@@ -249,9 +408,22 @@ class NodeJoin(NodeMultiInput):
|
|
|
249
408
|
auto_keep_right: bool = True
|
|
250
409
|
auto_keep_left: bool = True
|
|
251
410
|
|
|
411
|
+
def to_yaml_dict(self) -> NodeJoinYaml:
|
|
412
|
+
"""Converts the join node settings to a dictionary for YAML serialization."""
|
|
413
|
+
return {
|
|
414
|
+
"cache_results": self.cache_results,
|
|
415
|
+
"auto_generate_selection": self.auto_generate_selection,
|
|
416
|
+
"verify_integrity": self.verify_integrity,
|
|
417
|
+
"join_input": self.join_input.to_yaml_dict(),
|
|
418
|
+
"auto_keep_all": self.auto_keep_all,
|
|
419
|
+
"auto_keep_right": self.auto_keep_right,
|
|
420
|
+
"auto_keep_left": self.auto_keep_left,
|
|
421
|
+
}
|
|
422
|
+
|
|
252
423
|
|
|
253
424
|
class NodeCrossJoin(NodeMultiInput):
|
|
254
425
|
"""Settings for a node that performs a cross join."""
|
|
426
|
+
|
|
255
427
|
auto_generate_selection: bool = True
|
|
256
428
|
verify_integrity: bool = True
|
|
257
429
|
cross_join_input: transform_schema.CrossJoinInput
|
|
@@ -259,106 +431,138 @@ class NodeCrossJoin(NodeMultiInput):
|
|
|
259
431
|
auto_keep_right: bool = True
|
|
260
432
|
auto_keep_left: bool = True
|
|
261
433
|
|
|
434
|
+
def to_yaml_dict(self) -> NodeCrossJoinYaml:
|
|
435
|
+
"""Converts the cross join node settings to a dictionary for YAML serialization."""
|
|
436
|
+
return {
|
|
437
|
+
"cache_results": self.cache_results,
|
|
438
|
+
"auto_generate_selection": self.auto_generate_selection,
|
|
439
|
+
"verify_integrity": self.verify_integrity,
|
|
440
|
+
"cross_join_input": self.cross_join_input.to_yaml_dict(),
|
|
441
|
+
"auto_keep_all": self.auto_keep_all,
|
|
442
|
+
"auto_keep_right": self.auto_keep_right,
|
|
443
|
+
"auto_keep_left": self.auto_keep_left,
|
|
444
|
+
}
|
|
445
|
+
|
|
262
446
|
|
|
263
447
|
class NodeFuzzyMatch(NodeJoin):
|
|
264
448
|
"""Settings for a node that performs a fuzzy join based on string similarity."""
|
|
449
|
+
|
|
265
450
|
join_input: transform_schema.FuzzyMatchInput
|
|
266
451
|
|
|
452
|
+
def to_yaml_dict(self) -> NodeFuzzyMatchYaml:
|
|
453
|
+
"""Converts the fuzzy match node settings to a dictionary for YAML serialization."""
|
|
454
|
+
return {
|
|
455
|
+
"cache_results": self.cache_results,
|
|
456
|
+
"auto_generate_selection": self.auto_generate_selection,
|
|
457
|
+
"verify_integrity": self.verify_integrity,
|
|
458
|
+
"join_input": self.join_input.to_yaml_dict(),
|
|
459
|
+
"auto_keep_all": self.auto_keep_all,
|
|
460
|
+
"auto_keep_right": self.auto_keep_right,
|
|
461
|
+
"auto_keep_left": self.auto_keep_left,
|
|
462
|
+
}
|
|
463
|
+
|
|
267
464
|
|
|
268
465
|
class NodeDatasource(NodeBase):
|
|
269
466
|
"""Base settings for a node that acts as a data source."""
|
|
467
|
+
|
|
270
468
|
file_ref: str = None
|
|
271
469
|
|
|
272
470
|
|
|
273
471
|
class RawData(BaseModel):
|
|
274
472
|
"""Represents data in a raw, columnar format for manual input."""
|
|
275
|
-
|
|
276
|
-
|
|
473
|
+
|
|
474
|
+
columns: list[MinimalFieldInfo] = None
|
|
475
|
+
data: list[list]
|
|
277
476
|
|
|
278
477
|
@classmethod
|
|
279
|
-
def from_pylist(cls, pylist:
|
|
478
|
+
def from_pylist(cls, pylist: list[dict]):
|
|
280
479
|
"""Creates a RawData object from a list of Python dictionaries."""
|
|
281
480
|
if len(pylist) == 0:
|
|
282
481
|
return cls(columns=[], data=[])
|
|
283
482
|
pylist = ensure_similarity_dicts(pylist)
|
|
284
|
-
values = [standardize_col_dtype([vv for vv in c]) for c in
|
|
285
|
-
zip(*(r.values() for r in pylist))]
|
|
483
|
+
values = [standardize_col_dtype([vv for vv in c]) for c in zip(*(r.values() for r in pylist), strict=False)]
|
|
286
484
|
data_types = (pl.DataType.from_python(type(next((v for v in column_values), None))) for column_values in values)
|
|
287
485
|
columns = [MinimalFieldInfo(name=c, data_type=str(next(data_types))) for c in pylist[0].keys()]
|
|
288
486
|
return cls(columns=columns, data=values)
|
|
289
487
|
|
|
290
|
-
def to_pylist(self) ->
|
|
488
|
+
def to_pylist(self) -> list[dict]:
|
|
291
489
|
"""Converts the RawData object back into a list of Python dictionaries."""
|
|
292
490
|
return [{c.name: self.data[ci][ri] for ci, c in enumerate(self.columns)} for ri in range(len(self.data[0]))]
|
|
293
491
|
|
|
294
492
|
|
|
295
493
|
class NodeManualInput(NodeBase):
|
|
296
494
|
"""Settings for a node that allows direct data entry in the UI."""
|
|
297
|
-
|
|
495
|
+
|
|
496
|
+
raw_data_format: RawData | None = None
|
|
298
497
|
|
|
299
498
|
|
|
300
499
|
class NodeRead(NodeBase):
|
|
301
500
|
"""Settings for a node that reads data from a file."""
|
|
501
|
+
|
|
302
502
|
received_file: ReceivedTable
|
|
303
503
|
|
|
304
504
|
|
|
305
505
|
class DatabaseConnection(BaseModel):
|
|
306
506
|
"""Defines the connection parameters for a database."""
|
|
507
|
+
|
|
307
508
|
database_type: str = "postgresql"
|
|
308
|
-
username:
|
|
309
|
-
password_ref:
|
|
310
|
-
host:
|
|
311
|
-
port:
|
|
312
|
-
database:
|
|
313
|
-
url:
|
|
509
|
+
username: str | None = None
|
|
510
|
+
password_ref: SecretRef | None = None
|
|
511
|
+
host: str | None = None
|
|
512
|
+
port: int | None = None
|
|
513
|
+
database: str | None = None
|
|
514
|
+
url: str | None = None
|
|
314
515
|
|
|
315
516
|
|
|
316
517
|
class FullDatabaseConnection(BaseModel):
|
|
317
518
|
"""A complete database connection model including the secret password."""
|
|
519
|
+
|
|
318
520
|
connection_name: str
|
|
319
521
|
database_type: str = "postgresql"
|
|
320
522
|
username: str
|
|
321
523
|
password: SecretStr
|
|
322
|
-
host:
|
|
323
|
-
port:
|
|
324
|
-
database:
|
|
325
|
-
ssl_enabled:
|
|
326
|
-
url:
|
|
524
|
+
host: str | None = None
|
|
525
|
+
port: int | None = None
|
|
526
|
+
database: str | None = None
|
|
527
|
+
ssl_enabled: bool | None = False
|
|
528
|
+
url: str | None = None
|
|
327
529
|
|
|
328
530
|
|
|
329
531
|
class FullDatabaseConnectionInterface(BaseModel):
|
|
330
532
|
"""A database connection model intended for UI display, omitting the password."""
|
|
533
|
+
|
|
331
534
|
connection_name: str
|
|
332
535
|
database_type: str = "postgresql"
|
|
333
536
|
username: str
|
|
334
|
-
host:
|
|
335
|
-
port:
|
|
336
|
-
database:
|
|
337
|
-
ssl_enabled:
|
|
338
|
-
url:
|
|
537
|
+
host: str | None = None
|
|
538
|
+
port: int | None = None
|
|
539
|
+
database: str | None = None
|
|
540
|
+
ssl_enabled: bool | None = False
|
|
541
|
+
url: str | None = None
|
|
339
542
|
|
|
340
543
|
|
|
341
544
|
class DatabaseSettings(BaseModel):
|
|
342
545
|
"""Defines settings for reading from a database, either via table or query."""
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
546
|
+
|
|
547
|
+
connection_mode: Literal["inline", "reference"] | None = "inline"
|
|
548
|
+
database_connection: DatabaseConnection | None = None
|
|
549
|
+
database_connection_name: str | None = None
|
|
550
|
+
schema_name: str | None = None
|
|
551
|
+
table_name: str | None = None
|
|
552
|
+
query: str | None = None
|
|
553
|
+
query_mode: Literal["query", "table", "reference"] = "table"
|
|
554
|
+
|
|
555
|
+
@model_validator(mode="after")
|
|
352
556
|
def validate_table_or_query(self):
|
|
353
557
|
# Validate that either table_name or query is provided
|
|
354
|
-
if (not self.table_name and not self.query) and self.query_mode ==
|
|
558
|
+
if (not self.table_name and not self.query) and self.query_mode == "inline":
|
|
355
559
|
raise ValueError("Either 'table_name' or 'query' must be provided")
|
|
356
560
|
|
|
357
561
|
# Validate correct connection information based on connection_mode
|
|
358
|
-
if self.connection_mode ==
|
|
562
|
+
if self.connection_mode == "inline" and self.database_connection is None:
|
|
359
563
|
raise ValueError("When 'connection_mode' is 'inline', 'database_connection' must be provided")
|
|
360
564
|
|
|
361
|
-
if self.connection_mode ==
|
|
565
|
+
if self.connection_mode == "reference" and not self.database_connection_name:
|
|
362
566
|
raise ValueError("When 'connection_mode' is 'reference', 'database_connection_name' must be provided")
|
|
363
567
|
|
|
364
568
|
return self
|
|
@@ -366,44 +570,51 @@ class DatabaseSettings(BaseModel):
|
|
|
366
570
|
|
|
367
571
|
class DatabaseWriteSettings(BaseModel):
|
|
368
572
|
"""Defines settings for writing data to a database table."""
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
573
|
+
|
|
574
|
+
connection_mode: Literal["inline", "reference"] | None = "inline"
|
|
575
|
+
database_connection: DatabaseConnection | None = None
|
|
576
|
+
database_connection_name: str | None = None
|
|
372
577
|
table_name: str
|
|
373
|
-
schema_name:
|
|
374
|
-
if_exists:
|
|
578
|
+
schema_name: str | None = None
|
|
579
|
+
if_exists: Literal["append", "replace", "fail"] | None = "append"
|
|
375
580
|
|
|
376
581
|
|
|
377
582
|
class NodeDatabaseReader(NodeBase):
|
|
378
583
|
"""Settings for a node that reads from a database."""
|
|
584
|
+
|
|
379
585
|
database_settings: DatabaseSettings
|
|
380
|
-
fields:
|
|
586
|
+
fields: list[MinimalFieldInfo] | None = None
|
|
381
587
|
|
|
382
588
|
|
|
383
589
|
class NodeDatabaseWriter(NodeSingleInput):
|
|
384
590
|
"""Settings for a node that writes data to a database."""
|
|
591
|
+
|
|
385
592
|
database_write_settings: DatabaseWriteSettings
|
|
386
593
|
|
|
387
594
|
|
|
388
595
|
class NodeCloudStorageReader(NodeBase):
|
|
389
596
|
"""Settings for a node that reads from a cloud storage service (S3, GCS, etc.)."""
|
|
597
|
+
|
|
390
598
|
cloud_storage_settings: CloudStorageReadSettings
|
|
391
|
-
fields:
|
|
599
|
+
fields: list[MinimalFieldInfo] | None = None
|
|
392
600
|
|
|
393
601
|
|
|
394
602
|
class NodeCloudStorageWriter(NodeSingleInput):
|
|
395
603
|
"""Settings for a node that writes to a cloud storage service."""
|
|
604
|
+
|
|
396
605
|
cloud_storage_settings: CloudStorageWriteSettings
|
|
397
606
|
|
|
398
607
|
|
|
399
608
|
class ExternalSource(BaseModel):
|
|
400
609
|
"""Base model for data coming from a predefined external source."""
|
|
401
|
-
|
|
402
|
-
|
|
610
|
+
|
|
611
|
+
orientation: str = "row"
|
|
612
|
+
fields: list[MinimalFieldInfo] | None = None
|
|
403
613
|
|
|
404
614
|
|
|
405
615
|
class SampleUsers(ExternalSource):
|
|
406
616
|
"""Settings for generating a sample dataset of users."""
|
|
617
|
+
|
|
407
618
|
SAMPLE_USERS: bool
|
|
408
619
|
class_name: str = "sample_users"
|
|
409
620
|
size: int = 100
|
|
@@ -411,69 +622,91 @@ class SampleUsers(ExternalSource):
|
|
|
411
622
|
|
|
412
623
|
class NodeExternalSource(NodeBase):
|
|
413
624
|
"""Settings for a node that connects to a registered external data source."""
|
|
625
|
+
|
|
414
626
|
identifier: str
|
|
415
627
|
source_settings: SampleUsers
|
|
416
628
|
|
|
417
629
|
|
|
418
630
|
class NodeFormula(NodeSingleInput):
|
|
419
631
|
"""Settings for a node that applies a formula to create/modify a column."""
|
|
632
|
+
|
|
420
633
|
function: transform_schema.FunctionInput = None
|
|
421
634
|
|
|
422
635
|
|
|
423
636
|
class NodeGroupBy(NodeSingleInput):
|
|
424
637
|
"""Settings for a node that performs a group-by and aggregation operation."""
|
|
638
|
+
|
|
425
639
|
groupby_input: transform_schema.GroupByInput = None
|
|
426
640
|
|
|
427
641
|
|
|
428
642
|
class NodePromise(NodeBase):
|
|
429
643
|
"""A placeholder node for an operation that has not yet been configured."""
|
|
644
|
+
|
|
430
645
|
is_setup: bool = False
|
|
431
646
|
node_type: str
|
|
432
647
|
|
|
433
648
|
|
|
434
649
|
class NodeInputConnection(BaseModel):
|
|
435
650
|
"""Represents the input side of a connection between two nodes."""
|
|
651
|
+
|
|
436
652
|
node_id: int
|
|
437
653
|
connection_class: InputConnectionClass
|
|
438
654
|
|
|
439
|
-
def get_node_input_connection_type(self) -> Literal[
|
|
655
|
+
def get_node_input_connection_type(self) -> Literal["main", "right", "left"]:
|
|
440
656
|
"""Determines the semantic type of the input (e.g., for a join)."""
|
|
441
657
|
match self.connection_class:
|
|
442
|
-
case
|
|
443
|
-
|
|
444
|
-
case
|
|
445
|
-
|
|
658
|
+
case "input-0":
|
|
659
|
+
return "main"
|
|
660
|
+
case "input-1":
|
|
661
|
+
return "right"
|
|
662
|
+
case "input-2":
|
|
663
|
+
return "left"
|
|
664
|
+
case _:
|
|
665
|
+
raise ValueError(f"Unexpected connection_class: {self.connection_class}")
|
|
446
666
|
|
|
447
667
|
|
|
448
668
|
class NodePivot(NodeSingleInput):
|
|
449
669
|
"""Settings for a node that pivots data from a long to a wide format."""
|
|
670
|
+
|
|
450
671
|
pivot_input: transform_schema.PivotInput = None
|
|
451
|
-
output_fields:
|
|
672
|
+
output_fields: list[MinimalFieldInfo] | None = None
|
|
452
673
|
|
|
453
674
|
|
|
454
675
|
class NodeUnpivot(NodeSingleInput):
|
|
455
676
|
"""Settings for a node that unpivots data from a wide to a long format."""
|
|
677
|
+
|
|
456
678
|
unpivot_input: transform_schema.UnpivotInput = None
|
|
457
679
|
|
|
458
680
|
|
|
459
681
|
class NodeUnion(NodeMultiInput):
|
|
460
682
|
"""Settings for a node that concatenates multiple data inputs."""
|
|
683
|
+
|
|
461
684
|
union_input: transform_schema.UnionInput = Field(default_factory=transform_schema.UnionInput)
|
|
462
685
|
|
|
463
686
|
|
|
464
687
|
class NodeOutput(NodeSingleInput):
|
|
465
688
|
"""Settings for a node that writes its input to a file."""
|
|
689
|
+
|
|
466
690
|
output_settings: OutputSettings
|
|
467
691
|
|
|
692
|
+
def to_yaml_dict(self) -> NodeOutputYaml:
|
|
693
|
+
"""Converts the output node settings to a dictionary for YAML serialization."""
|
|
694
|
+
return {
|
|
695
|
+
"cache_results": self.cache_results,
|
|
696
|
+
"output_settings": self.output_settings.to_yaml_dict(),
|
|
697
|
+
}
|
|
698
|
+
|
|
468
699
|
|
|
469
700
|
class NodeOutputConnection(BaseModel):
|
|
470
701
|
"""Represents the output side of a connection between two nodes."""
|
|
702
|
+
|
|
471
703
|
node_id: int
|
|
472
704
|
connection_class: OutputConnectionClass
|
|
473
705
|
|
|
474
706
|
|
|
475
707
|
class NodeConnection(BaseModel):
|
|
476
708
|
"""Represents a connection (edge) between two nodes in the graph."""
|
|
709
|
+
|
|
477
710
|
input_connection: NodeInputConnection
|
|
478
711
|
output_connection: NodeOutputConnection
|
|
479
712
|
|
|
@@ -481,45 +714,56 @@ class NodeConnection(BaseModel):
|
|
|
481
714
|
def create_from_simple_input(cls, from_id: int, to_id: int, input_type: InputType = "input-0"):
|
|
482
715
|
"""Creates a standard connection between two nodes."""
|
|
483
716
|
match input_type:
|
|
484
|
-
case "main":
|
|
485
|
-
|
|
486
|
-
case "
|
|
487
|
-
|
|
717
|
+
case "main":
|
|
718
|
+
connection_class: InputConnectionClass = "input-0"
|
|
719
|
+
case "right":
|
|
720
|
+
connection_class: InputConnectionClass = "input-1"
|
|
721
|
+
case "left":
|
|
722
|
+
connection_class: InputConnectionClass = "input-2"
|
|
723
|
+
case _:
|
|
724
|
+
connection_class: InputConnectionClass = "input-0"
|
|
488
725
|
node_input = NodeInputConnection(node_id=to_id, connection_class=connection_class)
|
|
489
|
-
node_output = NodeOutputConnection(node_id=from_id, connection_class=
|
|
726
|
+
node_output = NodeOutputConnection(node_id=from_id, connection_class="output-0")
|
|
490
727
|
return cls(input_connection=node_input, output_connection=node_output)
|
|
491
728
|
|
|
492
729
|
|
|
493
730
|
class NodeDescription(BaseModel):
|
|
494
731
|
"""A simple model for updating a node's description text."""
|
|
495
|
-
|
|
732
|
+
|
|
733
|
+
description: str = ""
|
|
496
734
|
|
|
497
735
|
|
|
498
736
|
class NodeExploreData(NodeBase):
|
|
499
737
|
"""Settings for a node that provides an interactive data exploration interface."""
|
|
500
|
-
|
|
738
|
+
|
|
739
|
+
graphic_walker_input: gs_schemas.GraphicWalkerInput | None = None
|
|
501
740
|
|
|
502
741
|
|
|
503
742
|
class NodeGraphSolver(NodeSingleInput):
|
|
504
743
|
"""Settings for a node that solves graph-based problems (e.g., connected components)."""
|
|
744
|
+
|
|
505
745
|
graph_solver_input: transform_schema.GraphSolverInput
|
|
506
746
|
|
|
507
747
|
|
|
508
748
|
class NodeUnique(NodeSingleInput):
|
|
509
749
|
"""Settings for a node that returns the unique rows from the data."""
|
|
750
|
+
|
|
510
751
|
unique_input: transform_schema.UniqueInput
|
|
511
752
|
|
|
512
753
|
|
|
513
754
|
class NodeRecordCount(NodeSingleInput):
|
|
514
755
|
"""Settings for a node that counts the number of records."""
|
|
756
|
+
|
|
515
757
|
pass
|
|
516
758
|
|
|
517
759
|
|
|
518
760
|
class NodePolarsCode(NodeMultiInput):
|
|
519
761
|
"""Settings for a node that executes arbitrary user-provided Polars code."""
|
|
762
|
+
|
|
520
763
|
polars_code_input: transform_schema.PolarsCodeInput
|
|
521
764
|
|
|
522
765
|
|
|
523
766
|
class UserDefinedNode(NodeMultiInput):
|
|
524
767
|
"""Settings for a node that contains the user defined node information"""
|
|
768
|
+
|
|
525
769
|
settings: Any
|