Flowfile 0.5.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 +178 -74
- flowfile/__main__.py +10 -7
- flowfile/api.py +51 -57
- 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-0dfba9f2.js → CloudConnectionView-f13f202b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-d5b1b6c9.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-00d87aad.js → CloudStorageWriter-8e781e11.js} +10 -8
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-4685e75d.js → ColumnSelector-8ad68ea9.js} +3 -5
- flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-23e909da.js → ContextMenu-31ee57f0.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-70ae0c79.js → ContextMenu-69a74055.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-f149cf7c.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-702a3edd.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-b1519993.js → CustomNode-8479239b.js} +36 -24
- flowfile/web/static/assets/{DatabaseConnectionSettings-6f3e4ea5.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-d38c7295.js → DatabaseReader-c58b9552.js} +25 -15
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseManager-cf5ef661.js → DatabaseView-d26a9140.js} +11 -11
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
- flowfile/web/static/assets/{DatabaseWriter-b04ef46a.js → DatabaseWriter-4d05ddc7.js} +17 -10
- flowfile/web/static/assets/{designer-8da3ba3a.css → DesignerView-a6d0ee84.css} +614 -546
- flowfile/web/static/assets/{designer-9633482a.js → DesignerView-e6f5c0e8.js} +1107 -3170
- flowfile/web/static/assets/{documentation-ca400224.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-5fa10ed8.js → ExploreData-7b54caca.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-d39af878.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-6b04fb1d.js → Formula-aac42b1e.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-999521f4.js → FuzzyMatch-cd9bbfca.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-17dd2198.js → GraphSolver-c7e6780e.js} +13 -11
- flowfile/web/static/assets/{GroupBy-6b039e18.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-24d0f113.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-34639209.js → ManualInput-8d3374b2.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-0e8724a3.js → MultiSelect-ad1b6243.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-b0e538c2.js → MultiSelect.vue_vue_type_script_setup_true_lang-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-3d63a470.js → NumericInput-7100234c.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-e0edeccc.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
- flowfile/web/static/assets/{Output-283fe388.css → Output-35e97000.css} +6 -6
- flowfile/web/static/assets/{Output-edea9802.js → Output-f5efd2aa.js} +12 -9
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-61d19301.js → Pivot-d981d23c.js} +11 -9
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f97fec5b.js → PivotValidation-39386e95.js} +3 -3
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-de9f43fe.js → PivotValidation-63de1f73.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-bc3c9984.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-e808b239.css → Read-36e7bd51.css} +12 -12
- flowfile/web/static/assets/{Read-64a3f259.js → Read-aec2e377.js} +14 -11
- flowfile/web/static/assets/{RecordCount-3d5039be.js → RecordCount-78ed6845.js} +6 -4
- flowfile/web/static/assets/{RecordId-597510e0.js → RecordId-2156e890.js} +8 -6
- flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
- flowfile/web/static/assets/{SQLQueryComponent-df51adbe.js → SQLQueryComponent-48c72f5b.js} +3 -3
- flowfile/web/static/assets/{Sample-4be0a507.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-4839be57.js → SecretsView-17df66ee.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-9b72f201.js → Select-0aee4c54.js} +9 -7
- flowfile/web/static/assets/{SettingsSection-f0f75a42.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-e1e9c953.js → SettingsSection-cd341bb6.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-7ded385d.js → SettingsSection-f2002a6d.js} +3 -3
- flowfile/web/static/assets/{SingleSelect-6c777aac.js → SingleSelect-460cc0ea.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-33e3ff9b.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
- flowfile/web/static/assets/{SliderInput-7cb93e62.js → SliderInput-5d926864.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-6cbde21a.js → Sort-3cdc971b.js} +9 -7
- flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
- flowfile/web/static/assets/{TextInput-d9a40c11.js → TextInput-a2d0bfbd.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-5896c375.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-c4fcbf4d.js → TextToRows-918945f7.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-4ef91d19.js → ToggleSwitch-f0ef5196.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-38478c20.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
- flowfile/web/static/assets/{UnavailableFields-a03f512c.js → UnavailableFields-bdad6144.js} +4 -4
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/{Union-bfe9b996.js → Union-e8ab8c86.js} +8 -6
- flowfile/web/static/assets/{Unique-5d023a27.js → Unique-8cd4f976.js} +13 -10
- 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-91cc5354.js → Unpivot-8da14095.js} +10 -8
- flowfile/web/static/assets/{UnpivotValidation-7ee2de44.js → UnpivotValidation-6f7d89ff.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-e51b9924.js → VueGraphicWalker-3fb312e1.js} +4 -4
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{api-cf1221f0.js → api-24483f0d.js} +1 -1
- flowfile/web/static/assets/{api-c1bad5ca.js → api-8b81fa73.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
- flowfile/web/static/assets/{dropDown-614b998d.js → dropDown-ac0fda9d.js} +3 -3
- flowfile/web/static/assets/{fullEditor-f7971590.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-4fe5f36b.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-5429bbf8.js → index-fb6493ae.js} +41626 -40867
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
- flowfile/web/static/assets/{outputCsv-076b85ab.js → outputCsv-8f8ba42d.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-0fd17dbe.js → outputExcel-393f4fef.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/{outputParquet-b61e0847.js → outputParquet-07c81f65.js} +4 -4
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{readCsv-a8bb8b61.js → readCsv-07f6d9ad.js} +3 -3
- flowfile/web/static/assets/{readCsv-c767cb37.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readExcel-806d2826.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-67b4aee0.js → readExcel-ed69bc8f.js} +5 -5
- flowfile/web/static/assets/{readParquet-48c81530.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/{readParquet-92ce1dbc.js → readParquet-e3ed4528.js} +3 -3
- flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
- flowfile/web/static/assets/{selectDynamic-92e25ee3.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-41b0e0d7.js → vue-codemirror.esm-0965f39f.js} +31 -640
- flowfile/web/static/assets/{vue-content-loader.es-2c8e608f.js → vue-content-loader.es-c506ad97.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +2 -3
- flowfile-0.5.3.dist-info/RECORD +402 -0
- flowfile_core/__init__.py +13 -6
- flowfile_core/auth/jwt.py +51 -16
- flowfile_core/auth/models.py +32 -7
- flowfile_core/auth/password.py +89 -0
- flowfile_core/auth/secrets.py +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 +26 -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 +358 -244
- flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
- flowfile_core/flowfile/connection_manager/models.py +1 -1
- flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
- flowfile_core/flowfile/database_connection_manager/models.py +1 -1
- flowfile_core/flowfile/extensions.py +17 -12
- flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
- flowfile_core/flowfile/flow_data_engine/create/funcs.py +115 -83
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +481 -423
- flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
- flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +31 -20
- flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
- flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +14 -15
- flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
- flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
- flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
- flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +190 -127
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
- flowfile_core/flowfile/flow_data_engine/utils.py +99 -67
- flowfile_core/flowfile/flow_graph.py +918 -571
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +330 -233
- flowfile_core/flowfile/flow_node/models.py +53 -41
- flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
- flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
- flowfile_core/flowfile/handler.py +80 -30
- flowfile_core/flowfile/manage/compatibility_enhancements.py +209 -126
- flowfile_core/flowfile/manage/io_flowfile.py +54 -57
- flowfile_core/flowfile/node_designer/__init__.py +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 +135 -34
- flowfile_core/flowfile/schema_callbacks.py +71 -51
- flowfile_core/flowfile/setting_generator/__init__.py +0 -1
- flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
- flowfile_core/flowfile/setting_generator/settings.py +64 -53
- flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
- flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
- flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
- flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
- flowfile_core/flowfile/util/calculate_layout.py +9 -13
- flowfile_core/flowfile/util/execution_orderer.py +25 -17
- flowfile_core/flowfile/util/node_skipper.py +4 -4
- flowfile_core/flowfile/utils.py +19 -21
- flowfile_core/main.py +26 -19
- flowfile_core/routes/auth.py +284 -11
- flowfile_core/routes/cloud_connections.py +25 -25
- flowfile_core/routes/logs.py +21 -29
- flowfile_core/routes/public.py +3 -3
- flowfile_core/routes/routes.py +70 -34
- flowfile_core/routes/secrets.py +25 -27
- flowfile_core/routes/user_defined_components.py +483 -4
- flowfile_core/run_lock.py +0 -1
- flowfile_core/schemas/__init__.py +4 -6
- flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
- flowfile_core/schemas/cloud_storage_schemas.py +59 -53
- flowfile_core/schemas/input_schema.py +231 -144
- flowfile_core/schemas/output_model.py +49 -34
- flowfile_core/schemas/schemas.py +116 -89
- flowfile_core/schemas/transform_schema.py +518 -263
- flowfile_core/schemas/yaml_types.py +21 -7
- flowfile_core/secret_manager/secret_manager.py +17 -13
- flowfile_core/types.py +29 -9
- flowfile_core/utils/arrow_reader.py +7 -6
- flowfile_core/utils/excel_file_manager.py +3 -3
- flowfile_core/utils/fileManager.py +7 -7
- flowfile_core/utils/fl_executor.py +8 -10
- flowfile_core/utils/utils.py +4 -4
- flowfile_core/utils/validate_setup.py +5 -4
- flowfile_frame/__init__.py +106 -51
- flowfile_frame/adapters.py +2 -9
- flowfile_frame/adding_expr.py +73 -32
- flowfile_frame/cloud_storage/frame_helpers.py +27 -23
- flowfile_frame/cloud_storage/secret_manager.py +12 -26
- flowfile_frame/config.py +2 -5
- flowfile_frame/expr.py +311 -218
- flowfile_frame/expr.pyi +160 -159
- flowfile_frame/expr_name.py +23 -23
- flowfile_frame/flow_frame.py +571 -476
- flowfile_frame/flow_frame.pyi +123 -104
- flowfile_frame/flow_frame_methods.py +227 -246
- flowfile_frame/group_frame.py +50 -20
- flowfile_frame/join.py +2 -2
- flowfile_frame/lazy.py +129 -87
- flowfile_frame/lazy_methods.py +83 -30
- flowfile_frame/list_name_space.py +55 -50
- flowfile_frame/selectors.py +148 -68
- flowfile_frame/series.py +9 -7
- flowfile_frame/utils.py +19 -21
- flowfile_worker/__init__.py +12 -7
- flowfile_worker/configs.py +11 -19
- flowfile_worker/create/__init__.py +14 -9
- flowfile_worker/create/funcs.py +114 -77
- flowfile_worker/create/models.py +46 -43
- flowfile_worker/create/pl_types.py +14 -15
- flowfile_worker/create/read_excel_tables.py +34 -41
- flowfile_worker/create/utils.py +22 -19
- flowfile_worker/external_sources/s3_source/main.py +18 -51
- flowfile_worker/external_sources/s3_source/models.py +34 -27
- flowfile_worker/external_sources/sql_source/main.py +8 -5
- flowfile_worker/external_sources/sql_source/models.py +13 -9
- flowfile_worker/flow_logger.py +10 -8
- flowfile_worker/funcs.py +214 -155
- flowfile_worker/main.py +11 -17
- flowfile_worker/models.py +35 -28
- flowfile_worker/process_manager.py +2 -3
- flowfile_worker/routes.py +121 -90
- flowfile_worker/secrets.py +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/__init__.py +1 -1
- tools/migrate/__main__.py +16 -29
- tools/migrate/legacy_schemas.py +251 -190
- tools/migrate/migrate.py +193 -181
- tools/migrate/tests/conftest.py +1 -3
- tools/migrate/tests/test_migrate.py +36 -41
- tools/migrate/tests/test_migration_e2e.py +28 -29
- tools/migrate/tests/test_node_migrations.py +50 -20
- flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
- flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
- flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
- flowfile/web/static/assets/Filter-9b6d08db.js +0 -164
- flowfile/web/static/assets/Filter-f62091b3.css +0 -20
- flowfile/web/static/assets/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/nodeInput-5d0d6b79.js +0 -41
- flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
- flowfile/web/static/assets/secretApi-68435402.js +0 -46
- flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
- flowfile-0.5.1.dist-info/RECORD +0 -388
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,34 +1,42 @@
|
|
|
1
|
-
from typing import Literal, Optional, TYPE_CHECKING
|
|
2
|
-
from pydantic import BaseModel, SecretStr
|
|
3
|
-
from flowfile_core.schemas.input_schema import (DatabaseConnection,
|
|
4
|
-
NodeDatabaseReader,
|
|
5
|
-
FullDatabaseConnection,
|
|
6
|
-
NodeDatabaseWriter)
|
|
7
1
|
import base64
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
8
4
|
import polars as pl
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from flowfile_core.schemas.input_schema import (
|
|
8
|
+
DatabaseConnection,
|
|
9
|
+
FullDatabaseConnection,
|
|
10
|
+
NodeDatabaseReader,
|
|
11
|
+
NodeDatabaseWriter,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class ExtDatabaseConnection(DatabaseConnection):
|
|
12
16
|
"""Database connection configuration with password handling."""
|
|
17
|
+
|
|
13
18
|
password: str = None
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class DatabaseExternalWriteSettings(BaseModel):
|
|
17
22
|
"""Settings for SQL sink."""
|
|
23
|
+
|
|
18
24
|
connection: ExtDatabaseConnection
|
|
19
25
|
table_name: str
|
|
20
|
-
if_exists:
|
|
26
|
+
if_exists: Literal["append", "replace", "fail"] | None = "append"
|
|
21
27
|
flowfile_flow_id: int = 1
|
|
22
28
|
flowfile_node_id: int | str = -1
|
|
23
29
|
operation: str
|
|
24
30
|
|
|
25
31
|
@classmethod
|
|
26
|
-
def create_from_from_node_database_writer(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
def create_from_from_node_database_writer(
|
|
33
|
+
cls,
|
|
34
|
+
node_database_writer: NodeDatabaseWriter,
|
|
35
|
+
password: str,
|
|
36
|
+
table_name: str,
|
|
37
|
+
lf: pl.LazyFrame,
|
|
38
|
+
database_reference_settings: FullDatabaseConnection = None,
|
|
39
|
+
) -> "DatabaseExternalWriteSettings":
|
|
32
40
|
"""
|
|
33
41
|
Create DatabaseExternalWriteSettings from NodeDatabaseWriter.
|
|
34
42
|
Args:
|
|
@@ -45,28 +53,33 @@ class DatabaseExternalWriteSettings(BaseModel):
|
|
|
45
53
|
else:
|
|
46
54
|
database_connection = {k: v for k, v in database_reference_settings.model_dump().items() if k != "password"}
|
|
47
55
|
|
|
48
|
-
ext_database_connection = ExtDatabaseConnection(**database_connection,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
ext_database_connection = ExtDatabaseConnection(**database_connection, password=password)
|
|
57
|
+
return cls(
|
|
58
|
+
connection=ext_database_connection,
|
|
59
|
+
table_name=table_name,
|
|
60
|
+
if_exists=node_database_writer.database_write_settings.if_exists,
|
|
61
|
+
flowfile_flow_id=node_database_writer.flow_id,
|
|
62
|
+
flowfile_node_id=node_database_writer.node_id,
|
|
63
|
+
operation=base64.b64encode(lf.serialize()).decode(),
|
|
64
|
+
)
|
|
56
65
|
|
|
57
66
|
|
|
58
67
|
class DatabaseExternalReadSettings(BaseModel):
|
|
59
68
|
"""Settings for SQL source."""
|
|
69
|
+
|
|
60
70
|
connection: ExtDatabaseConnection
|
|
61
71
|
query: str
|
|
62
72
|
flowfile_flow_id: int = 1
|
|
63
73
|
flowfile_node_id: int | str = -1
|
|
64
74
|
|
|
65
75
|
@classmethod
|
|
66
|
-
def create_from_from_node_database_reader(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
def create_from_from_node_database_reader(
|
|
77
|
+
cls,
|
|
78
|
+
node_database_reader: NodeDatabaseReader,
|
|
79
|
+
password: str,
|
|
80
|
+
query: str,
|
|
81
|
+
database_reference_settings: FullDatabaseConnection = None,
|
|
82
|
+
) -> "DatabaseExternalReadSettings":
|
|
70
83
|
"""
|
|
71
84
|
Create DatabaseExternalReadSettings from NodeDatabaseReader.
|
|
72
85
|
Args:
|
|
@@ -82,9 +95,10 @@ class DatabaseExternalReadSettings(BaseModel):
|
|
|
82
95
|
else:
|
|
83
96
|
database_connection = {k: v for k, v in database_reference_settings.model_dump().items() if k != "password"}
|
|
84
97
|
|
|
85
|
-
ext_database_connection = ExtDatabaseConnection(**database_connection,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
ext_database_connection = ExtDatabaseConnection(**database_connection, password=password)
|
|
99
|
+
return cls(
|
|
100
|
+
connection=ext_database_connection,
|
|
101
|
+
query=query,
|
|
102
|
+
flowfile_flow_id=node_database_reader.flow_id,
|
|
103
|
+
flowfile_node_id=node_database_reader.node_id,
|
|
104
|
+
)
|
|
@@ -1,16 +1,129 @@
|
|
|
1
|
-
|
|
1
|
+
import re
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
2
5
|
import polars as pl
|
|
6
|
+
from sqlalchemy import Engine, create_engine, inspect, text
|
|
7
|
+
|
|
3
8
|
from flowfile_core.configs import logger
|
|
9
|
+
from flowfile_core.flowfile.database_connection_manager.db_connections import get_local_database_connection
|
|
4
10
|
from flowfile_core.flowfile.flow_data_engine.flow_file_column.main import FlowfileColumn
|
|
5
|
-
from flowfile_core.schemas.input_schema import MinimalFieldInfo, DatabaseSettings
|
|
6
|
-
from sqlalchemy import Engine, inspect, create_engine, text
|
|
7
|
-
from flowfile_core.secret_manager.secret_manager import get_encrypted_secret, decrypt_secret
|
|
8
|
-
|
|
9
11
|
from flowfile_core.flowfile.sources.external_sources.base_class import ExternalDataSource
|
|
10
|
-
from flowfile_core.flowfile.sources.external_sources.sql_source.utils import
|
|
11
|
-
from flowfile_core.
|
|
12
|
+
from flowfile_core.flowfile.sources.external_sources.sql_source.utils import construct_sql_uri, get_polars_type
|
|
13
|
+
from flowfile_core.schemas.input_schema import DatabaseSettings, MinimalFieldInfo
|
|
14
|
+
from flowfile_core.secret_manager.secret_manager import decrypt_secret, get_encrypted_secret
|
|
15
|
+
|
|
16
|
+
QueryMode = Literal["table", "query"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UnsafeSQLError(ValueError):
|
|
20
|
+
"""Raised when a SQL query contains unsafe operations."""
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_sql_query(query: str) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Validate that a SQL query is safe for execution (read-only SELECT statements only).
|
|
28
|
+
|
|
29
|
+
This function checks that the query:
|
|
30
|
+
1. Is a SELECT statement (not INSERT, UPDATE, DELETE, etc.)
|
|
31
|
+
2. Does not contain DDL statements (DROP, CREATE, ALTER, TRUNCATE)
|
|
32
|
+
3. Does not contain other dangerous operations
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
query: The SQL query string to validate
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
UnsafeSQLError: If the query contains unsafe operations
|
|
39
|
+
"""
|
|
40
|
+
if not query or not query.strip():
|
|
41
|
+
raise UnsafeSQLError("SQL query cannot be empty")
|
|
42
|
+
|
|
43
|
+
# Normalize the query: remove comments and extra whitespace
|
|
44
|
+
normalized = _remove_sql_comments(query)
|
|
45
|
+
normalized = " ".join(normalized.split()).upper()
|
|
46
|
+
|
|
47
|
+
# Check if query starts with SELECT (allowing for WITH clauses / CTEs)
|
|
48
|
+
if not _is_select_query(normalized):
|
|
49
|
+
raise UnsafeSQLError(
|
|
50
|
+
"Only SELECT queries are allowed. "
|
|
51
|
+
"The query must start with SELECT or WITH (for common table expressions)."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Check for dangerous DDL statements
|
|
55
|
+
ddl_patterns = [
|
|
56
|
+
(r"\bDROP\s+", "DROP statements are not allowed"),
|
|
57
|
+
(r"\bCREATE\s+", "CREATE statements are not allowed"),
|
|
58
|
+
(r"\bALTER\s+", "ALTER statements are not allowed"),
|
|
59
|
+
(r"\bTRUNCATE\s+", "TRUNCATE statements are not allowed"),
|
|
60
|
+
(r"\bRENAME\s+", "RENAME statements are not allowed"),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
for pattern, error_msg in ddl_patterns:
|
|
64
|
+
if re.search(pattern, normalized):
|
|
65
|
+
raise UnsafeSQLError(error_msg)
|
|
66
|
+
|
|
67
|
+
# Check for dangerous DML statements (these shouldn't appear in a SELECT)
|
|
68
|
+
dml_patterns = [
|
|
69
|
+
(r"\bINSERT\s+INTO\b", "INSERT statements are not allowed"),
|
|
70
|
+
(r"\bUPDATE\s+\w+\s+SET\b", "UPDATE statements are not allowed"),
|
|
71
|
+
(r"\bDELETE\s+FROM\b", "DELETE statements are not allowed"),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
for pattern, error_msg in dml_patterns:
|
|
75
|
+
if re.search(pattern, normalized):
|
|
76
|
+
raise UnsafeSQLError(error_msg)
|
|
77
|
+
|
|
78
|
+
# Check for dangerous operations that could be used maliciously
|
|
79
|
+
dangerous_patterns = [
|
|
80
|
+
(r"\bEXEC(UTE)?\s*\(", "EXECUTE statements are not allowed"),
|
|
81
|
+
(r"\bCALL\s+", "CALL statements (stored procedures) are not allowed"),
|
|
82
|
+
(r"\bGRANT\s+", "GRANT statements are not allowed"),
|
|
83
|
+
(r"\bREVOKE\s+", "REVOKE statements are not allowed"),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
for pattern, error_msg in dangerous_patterns:
|
|
87
|
+
if re.search(pattern, normalized):
|
|
88
|
+
raise UnsafeSQLError(error_msg)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _remove_sql_comments(query: str) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Remove SQL comments from a query string.
|
|
94
|
+
|
|
95
|
+
Handles:
|
|
96
|
+
- Single line comments (-- comment)
|
|
97
|
+
- Multi-line comments (/* comment */)
|
|
98
|
+
"""
|
|
99
|
+
# Remove multi-line comments using a non-backtracking pattern
|
|
100
|
+
# Matches /* followed by (non-* chars OR * not followed by /) then */
|
|
101
|
+
result = re.sub(r"/\*(?:[^*]|\*(?!/))*\*/", " ", query)
|
|
102
|
+
# Remove single-line comments - explicitly match non-newline chars to avoid backtracking
|
|
103
|
+
result = re.sub(r"--[^\r\n]*", " ", result)
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _is_select_query(normalized_query: str) -> bool:
|
|
108
|
+
"""
|
|
109
|
+
Check if a normalized (uppercase, whitespace-cleaned) query is a SELECT statement.
|
|
110
|
+
|
|
111
|
+
Allows:
|
|
112
|
+
- SELECT ...
|
|
113
|
+
- WITH ... SELECT ... (CTEs)
|
|
114
|
+
"""
|
|
115
|
+
# Check for direct SELECT
|
|
116
|
+
if normalized_query.startswith("SELECT ") or normalized_query.startswith("SELECT\t"):
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
# Check for WITH clause (CTE) that leads to SELECT
|
|
120
|
+
if normalized_query.startswith("WITH ") or normalized_query.startswith("WITH\t"):
|
|
121
|
+
# CTEs should eventually have a SELECT
|
|
122
|
+
# Make sure there's a SELECT after the WITH clause and no dangerous statements
|
|
123
|
+
if " SELECT " in normalized_query or "\tSELECT " in normalized_query:
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
return False
|
|
14
127
|
|
|
15
128
|
|
|
16
129
|
def get_query_columns(engine: Engine, query_text: str):
|
|
@@ -36,7 +149,7 @@ def get_query_columns(engine: Engine, query_text: str):
|
|
|
36
149
|
return list(column_names)
|
|
37
150
|
|
|
38
151
|
|
|
39
|
-
def get_table_column_types(engine: Engine, table_name: str, schema: str = None) ->
|
|
152
|
+
def get_table_column_types(engine: Engine, table_name: str, schema: str = None) -> list[tuple[str, Any]]:
|
|
40
153
|
"""
|
|
41
154
|
Get column types from a database table using a SQLAlchemy engine
|
|
42
155
|
|
|
@@ -51,7 +164,7 @@ def get_table_column_types(engine: Engine, table_name: str, schema: str = None)
|
|
|
51
164
|
inspector = inspect(engine)
|
|
52
165
|
columns = inspector.get_columns(table_name, schema=schema)
|
|
53
166
|
|
|
54
|
-
return [(column[
|
|
167
|
+
return [(column["name"], column["type"]) for column in columns]
|
|
55
168
|
|
|
56
169
|
|
|
57
170
|
class BaseSqlSource:
|
|
@@ -59,17 +172,20 @@ class BaseSqlSource:
|
|
|
59
172
|
A simplified base class for SQL sources that handles query generation
|
|
60
173
|
without requiring database connection details.
|
|
61
174
|
"""
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
175
|
+
|
|
176
|
+
table_name: str | None = None
|
|
177
|
+
query: str | None = None
|
|
178
|
+
schema_name: str | None = None
|
|
179
|
+
query_mode: QueryMode = "table"
|
|
180
|
+
schema: list[FlowfileColumn] | None = None
|
|
181
|
+
|
|
182
|
+
def __init__(
|
|
183
|
+
self,
|
|
184
|
+
query: str = None,
|
|
185
|
+
table_name: str = None,
|
|
186
|
+
schema_name: str = None,
|
|
187
|
+
fields: list[MinimalFieldInfo] | None = None,
|
|
188
|
+
):
|
|
73
189
|
"""
|
|
74
190
|
Initialize a BaseSqlSource object.
|
|
75
191
|
|
|
@@ -79,7 +195,7 @@ class BaseSqlSource:
|
|
|
79
195
|
schema_name: Optional database schema name
|
|
80
196
|
fields: Optional list of field information
|
|
81
197
|
"""
|
|
82
|
-
if schema_name ==
|
|
198
|
+
if schema_name == "":
|
|
83
199
|
schema_name = None
|
|
84
200
|
|
|
85
201
|
# Validate inputs
|
|
@@ -90,15 +206,17 @@ class BaseSqlSource:
|
|
|
90
206
|
|
|
91
207
|
# Set query mode and build query if needed
|
|
92
208
|
if query is not None:
|
|
93
|
-
|
|
209
|
+
# Validate user-provided queries for safety (read-only SELECT only)
|
|
210
|
+
validate_sql_query(query)
|
|
211
|
+
self.query_mode = "query"
|
|
94
212
|
self.query = query
|
|
95
213
|
elif table_name is not None:
|
|
96
|
-
self.query_mode =
|
|
214
|
+
self.query_mode = "table"
|
|
97
215
|
self.table_name = table_name
|
|
98
216
|
self.schema_name = schema_name
|
|
99
217
|
|
|
100
218
|
# Generate the basic query
|
|
101
|
-
if schema_name is not None and schema_name !=
|
|
219
|
+
if schema_name is not None and schema_name != "":
|
|
102
220
|
self.query = f"SELECT * FROM {schema_name}.{table_name}"
|
|
103
221
|
else:
|
|
104
222
|
self.query = f"SELECT * FROM {table_name}"
|
|
@@ -111,13 +229,13 @@ class BaseSqlSource:
|
|
|
111
229
|
"""
|
|
112
230
|
Get a sample query that returns a limited number of rows.
|
|
113
231
|
"""
|
|
114
|
-
if self.query_mode ==
|
|
232
|
+
if self.query_mode == "query":
|
|
115
233
|
return f"select * from ({self.query}) as main_query LIMIT 1"
|
|
116
234
|
else:
|
|
117
235
|
return f"{self.query} LIMIT 1"
|
|
118
236
|
|
|
119
237
|
@staticmethod
|
|
120
|
-
def _parse_table_name(table_name: str) -> tuple[
|
|
238
|
+
def _parse_table_name(table_name: str) -> tuple[str | None, str]:
|
|
121
239
|
"""
|
|
122
240
|
Parse a table name that may include a schema.
|
|
123
241
|
|
|
@@ -127,10 +245,10 @@ class BaseSqlSource:
|
|
|
127
245
|
Returns:
|
|
128
246
|
Tuple of (schema, table_name)
|
|
129
247
|
"""
|
|
130
|
-
table_parts = table_name.split(
|
|
248
|
+
table_parts = table_name.split(".")
|
|
131
249
|
if len(table_parts) > 1:
|
|
132
250
|
# Handle schema.table_name format
|
|
133
|
-
schema =
|
|
251
|
+
schema = ".".join(table_parts[:-1])
|
|
134
252
|
table = table_parts[-1]
|
|
135
253
|
return schema, table
|
|
136
254
|
else:
|
|
@@ -138,16 +256,17 @@ class BaseSqlSource:
|
|
|
138
256
|
|
|
139
257
|
|
|
140
258
|
class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
141
|
-
connection_string:
|
|
142
|
-
read_result:
|
|
143
|
-
|
|
144
|
-
def __init__(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
259
|
+
connection_string: str | None
|
|
260
|
+
read_result: pl.DataFrame | None = None
|
|
261
|
+
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
connection_string: str,
|
|
265
|
+
query: str = None,
|
|
266
|
+
table_name: str = None,
|
|
267
|
+
schema_name: str = None,
|
|
268
|
+
fields: list[MinimalFieldInfo] | None = None,
|
|
269
|
+
):
|
|
151
270
|
# Initialize the base class first
|
|
152
271
|
BaseSqlSource.__init__(self, query=query, table_name=table_name, schema_name=schema_name, fields=fields)
|
|
153
272
|
|
|
@@ -155,13 +274,13 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
155
274
|
self.connection_string = connection_string
|
|
156
275
|
self.read_result = None
|
|
157
276
|
|
|
158
|
-
def get_initial_data(self) ->
|
|
277
|
+
def get_initial_data(self) -> list[dict[str, Any]]:
|
|
159
278
|
return []
|
|
160
279
|
|
|
161
280
|
def validate(self) -> None:
|
|
162
281
|
try:
|
|
163
282
|
engine = create_engine(self.connection_string)
|
|
164
|
-
if self.query_mode ==
|
|
283
|
+
if self.query_mode == "table":
|
|
165
284
|
try:
|
|
166
285
|
if self.schema_name is not None:
|
|
167
286
|
self._get_columns_from_table_and_schema(engine, self.table_name, self.schema_name)
|
|
@@ -180,8 +299,8 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
180
299
|
logger.error(f"Error validating SQL source: {e}")
|
|
181
300
|
raise e
|
|
182
301
|
|
|
183
|
-
def get_iter(self) -> Generator[
|
|
184
|
-
logger.warning(
|
|
302
|
+
def get_iter(self) -> Generator[dict[str, Any], None, None]:
|
|
303
|
+
logger.warning("Getting data in iteration, this is suboptimal")
|
|
185
304
|
data = self.data_getter()
|
|
186
305
|
for row in data:
|
|
187
306
|
yield row
|
|
@@ -190,8 +309,8 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
190
309
|
df = self.get_pl_df()
|
|
191
310
|
return df.to_pandas()
|
|
192
311
|
|
|
193
|
-
def get_sample(self, n: int = 10000) -> Generator[
|
|
194
|
-
if self.query_mode ==
|
|
312
|
+
def get_sample(self, n: int = 10000) -> Generator[dict[str, Any], None, None]:
|
|
313
|
+
if self.query_mode == "table":
|
|
195
314
|
query = f"{self.query} LIMIT {n}"
|
|
196
315
|
try:
|
|
197
316
|
df = pl.read_database_uri(query, self.connection_string)
|
|
@@ -204,7 +323,7 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
204
323
|
rows = df.head(n).to_dicts()
|
|
205
324
|
return (r for r in rows)
|
|
206
325
|
|
|
207
|
-
def data_getter(self) -> Generator[
|
|
326
|
+
def data_getter(self) -> Generator[dict[str, Any], None, None]:
|
|
208
327
|
df = self.get_pl_df()
|
|
209
328
|
rows = df.to_dicts()
|
|
210
329
|
return (r for r in rows)
|
|
@@ -214,7 +333,7 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
214
333
|
self.read_result = pl.read_database_uri(self.query, self.connection_string)
|
|
215
334
|
return self.read_result
|
|
216
335
|
|
|
217
|
-
def get_flow_file_columns(self) ->
|
|
336
|
+
def get_flow_file_columns(self) -> list[FlowfileColumn]:
|
|
218
337
|
"""
|
|
219
338
|
Get column information from the SQL source and convert to FlowfileColumn objects
|
|
220
339
|
|
|
@@ -223,7 +342,7 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
223
342
|
"""
|
|
224
343
|
engine = create_engine(self.connection_string)
|
|
225
344
|
|
|
226
|
-
if self.query_mode ==
|
|
345
|
+
if self.query_mode == "table":
|
|
227
346
|
try:
|
|
228
347
|
if self.schema_name is not None:
|
|
229
348
|
return self._get_columns_from_table_and_schema(engine, self.table_name, self.schema_name)
|
|
@@ -235,7 +354,7 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
235
354
|
return self._get_columns_from_query(engine, self.get_sample_query())
|
|
236
355
|
|
|
237
356
|
@staticmethod
|
|
238
|
-
def _get_columns_from_table(engine: Engine, table_name: str) ->
|
|
357
|
+
def _get_columns_from_table(engine: Engine, table_name: str) -> list[FlowfileColumn]:
|
|
239
358
|
"""
|
|
240
359
|
Get FlowfileColumn objects from a database table
|
|
241
360
|
|
|
@@ -248,8 +367,10 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
248
367
|
"""
|
|
249
368
|
schema_name, table = BaseSqlSource._parse_table_name(table_name)
|
|
250
369
|
column_types = get_table_column_types(engine, table, schema=schema_name)
|
|
251
|
-
columns = [
|
|
252
|
-
|
|
370
|
+
columns = [
|
|
371
|
+
FlowfileColumn.create_from_polars_dtype(column_name, get_polars_type(column_type))
|
|
372
|
+
for column_name, column_type in column_types
|
|
373
|
+
]
|
|
253
374
|
|
|
254
375
|
return columns
|
|
255
376
|
|
|
@@ -266,12 +387,14 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
266
387
|
List of FlowfileColumn objects
|
|
267
388
|
"""
|
|
268
389
|
column_types = get_table_column_types(engine, table_name, schema=schema_name)
|
|
269
|
-
columns = [
|
|
270
|
-
|
|
390
|
+
columns = [
|
|
391
|
+
FlowfileColumn.create_from_polars_dtype(column_name, get_polars_type(column_type))
|
|
392
|
+
for column_name, column_type in column_types
|
|
393
|
+
]
|
|
271
394
|
return columns
|
|
272
395
|
|
|
273
396
|
@staticmethod
|
|
274
|
-
def _get_columns_from_query(engine: Engine, query: str) ->
|
|
397
|
+
def _get_columns_from_query(engine: Engine, query: str) -> list[FlowfileColumn]:
|
|
275
398
|
"""
|
|
276
399
|
Get FlowfileColumn objects from a SQL query
|
|
277
400
|
|
|
@@ -285,17 +408,18 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
285
408
|
try:
|
|
286
409
|
column_names = get_query_columns(engine, query)
|
|
287
410
|
|
|
288
|
-
columns = [
|
|
289
|
-
|
|
411
|
+
columns = [
|
|
412
|
+
FlowfileColumn.create_from_polars_dtype(column_name, pl.String()) for column_name in column_names
|
|
413
|
+
]
|
|
290
414
|
return columns
|
|
291
415
|
except Exception as e:
|
|
292
416
|
logger.error(f"Error getting column info for query: {e}")
|
|
293
417
|
raise e
|
|
294
418
|
|
|
295
|
-
def parse_schema(self) ->
|
|
419
|
+
def parse_schema(self) -> list[FlowfileColumn]:
|
|
296
420
|
return self.get_schema()
|
|
297
421
|
|
|
298
|
-
def get_schema(self) ->
|
|
422
|
+
def get_schema(self) -> list[FlowfileColumn]:
|
|
299
423
|
if self.schema is None:
|
|
300
424
|
self.schema = self.get_flow_file_columns()
|
|
301
425
|
return self.schema
|
|
@@ -303,26 +427,27 @@ class SqlSource(BaseSqlSource, ExternalDataSource):
|
|
|
303
427
|
|
|
304
428
|
def create_sql_source_from_db_settings(database_settings: DatabaseSettings, user_id: int) -> SqlSource:
|
|
305
429
|
database_connection = database_settings.database_connection
|
|
306
|
-
if database_settings.connection_mode ==
|
|
430
|
+
if database_settings.connection_mode == "inline":
|
|
307
431
|
if database_connection is None:
|
|
308
432
|
raise ValueError("Database connection is required in inline mode")
|
|
309
|
-
encrypted_secret = get_encrypted_secret(current_user_id=user_id,
|
|
310
|
-
secret_name=database_connection.password_ref)
|
|
433
|
+
encrypted_secret = get_encrypted_secret(current_user_id=user_id, secret_name=database_connection.password_ref)
|
|
311
434
|
else:
|
|
312
435
|
database_connection = get_local_database_connection(database_settings.database_connection_name, user_id)
|
|
313
436
|
encrypted_secret = database_connection.password.get_secret_value()
|
|
314
437
|
if encrypted_secret is None:
|
|
315
438
|
raise ValueError(f"Secret with name {database_connection.password_ref} not found for user {user_id}")
|
|
316
439
|
|
|
317
|
-
sql_source = SqlSource(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
440
|
+
sql_source = SqlSource(
|
|
441
|
+
connection_string=construct_sql_uri(
|
|
442
|
+
database_type=database_connection.database_type,
|
|
443
|
+
host=database_connection.host,
|
|
444
|
+
port=database_connection.port,
|
|
445
|
+
database=database_connection.database,
|
|
446
|
+
username=database_connection.username,
|
|
447
|
+
password=decrypt_secret(encrypted_secret),
|
|
448
|
+
),
|
|
449
|
+
query=None if database_settings.query_mode == "table" else database_settings.query,
|
|
450
|
+
table_name=database_settings.table_name,
|
|
451
|
+
schema_name=database_settings.schema_name,
|
|
452
|
+
)
|
|
453
|
+
return sql_source
|