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,59 +1,55 @@
|
|
|
1
|
-
from typing import Generator, List
|
|
2
|
-
from openpyxl import Workbook, load_workbook
|
|
3
|
-
from openpyxl.worksheet.worksheet import Worksheet
|
|
4
1
|
import gc
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
|
|
5
4
|
import polars as pl
|
|
5
|
+
from openpyxl import Workbook, load_workbook
|
|
6
|
+
from openpyxl.worksheet.worksheet import Worksheet
|
|
7
|
+
|
|
6
8
|
from flowfile_worker.create.utils import create_pl_df_type_save
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
def raw_data_openpyxl(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
def raw_data_openpyxl(
|
|
12
|
+
file_path: str,
|
|
13
|
+
sheet_name: str = None,
|
|
14
|
+
min_row: int = None,
|
|
15
|
+
max_row: int = None,
|
|
16
|
+
min_col: int = None,
|
|
17
|
+
max_col: int = None,
|
|
18
|
+
) -> Generator[list, None, None]:
|
|
16
19
|
workbook: Workbook = load_workbook(file_path, data_only=True, read_only=True)
|
|
17
20
|
sheet_name = workbook.sheetnames[0] if sheet_name is None else sheet_name
|
|
18
21
|
sheet: Worksheet = workbook[sheet_name]
|
|
19
|
-
|
|
20
|
-
max_row=max_row,
|
|
21
|
-
min_col=min_col,
|
|
22
|
-
max_col=max_col,
|
|
23
|
-
values_only=True):
|
|
24
|
-
yield row
|
|
22
|
+
yield from sheet.iter_rows(min_row=min_row, max_row=max_row, min_col=min_col, max_col=max_col, values_only=True)
|
|
25
23
|
workbook.close()
|
|
26
24
|
del workbook
|
|
27
25
|
gc.collect()
|
|
28
26
|
|
|
29
27
|
|
|
30
|
-
def df_from_openpyxl(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
min_col=min_col,
|
|
43
|
-
max_col=max_col)
|
|
28
|
+
def df_from_openpyxl(
|
|
29
|
+
file_path: str,
|
|
30
|
+
sheet_name: str = None,
|
|
31
|
+
min_row: int = None,
|
|
32
|
+
max_row: int = None,
|
|
33
|
+
min_col: int = None,
|
|
34
|
+
max_col: int = None,
|
|
35
|
+
has_headers: bool = True,
|
|
36
|
+
) -> pl.DataFrame:
|
|
37
|
+
data_iterator = raw_data_openpyxl(
|
|
38
|
+
file_path=file_path, sheet_name=sheet_name, min_row=min_row, max_row=max_row, min_col=min_col, max_col=max_col
|
|
39
|
+
)
|
|
44
40
|
raw_data = list(data_iterator)
|
|
45
41
|
if len(raw_data) > 0:
|
|
46
42
|
if has_headers:
|
|
47
43
|
columns = []
|
|
48
44
|
for i, col in enumerate(raw_data[0]):
|
|
49
45
|
if col is None:
|
|
50
|
-
col = f
|
|
46
|
+
col = f"_unnamed_column_{i}"
|
|
51
47
|
elif not isinstance(col, str):
|
|
52
48
|
col = str(col)
|
|
53
49
|
columns.append(col)
|
|
54
50
|
columns = ensure_unique(columns)
|
|
55
51
|
df = create_pl_df_type_save(raw_data[1:])
|
|
56
|
-
renames = {o: n for o, n in zip(df.columns, columns)}
|
|
52
|
+
renames = {o: n for o, n in zip(df.columns, columns, strict=False)}
|
|
57
53
|
df = df.rename(renames)
|
|
58
54
|
|
|
59
55
|
else:
|
|
@@ -63,7 +59,7 @@ def df_from_openpyxl(file_path: str,
|
|
|
63
59
|
return pl.DataFrame()
|
|
64
60
|
|
|
65
61
|
|
|
66
|
-
def ensure_unique(lst:
|
|
62
|
+
def ensure_unique(lst: list[str]) -> list[str]:
|
|
67
63
|
"""
|
|
68
64
|
Ensures that all elements in the input list are unique by appending
|
|
69
65
|
a version number (e.g., '_v1') to duplicates. It continues adding
|
|
@@ -99,12 +95,9 @@ def ensure_unique(lst: List[str]) -> List[str]:
|
|
|
99
95
|
def df_from_calamine_xlsx(file_path: str, sheet_name: str, start_row: int = 0, end_row: int = 0) -> pl.DataFrame:
|
|
100
96
|
read_options = {}
|
|
101
97
|
if start_row > 0:
|
|
102
|
-
read_options[
|
|
98
|
+
read_options["header_row"] = start_row
|
|
103
99
|
if end_row > 0:
|
|
104
|
-
read_options[
|
|
105
|
-
return pl.read_excel(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
read_options=read_options,
|
|
109
|
-
raise_if_empty=False
|
|
110
|
-
)
|
|
100
|
+
read_options["n_rows"] = end_row - start_row
|
|
101
|
+
return pl.read_excel(
|
|
102
|
+
source=file_path, engine="calamine", sheet_name=sheet_name, read_options=read_options, raise_if_empty=False
|
|
103
|
+
)
|
flowfile_worker/create/utils.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Iterable
|
|
2
2
|
from functools import partial
|
|
3
3
|
from random import randint
|
|
4
|
+
|
|
4
5
|
import polars as pl
|
|
5
|
-
from
|
|
6
|
+
from faker import Faker
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def create_fake_data(n_records: int = 1000) -> pl.DataFrame:
|
|
9
10
|
fake = Faker()
|
|
10
|
-
selector = partial(randint,0)
|
|
11
|
+
selector = partial(randint, 0)
|
|
11
12
|
min_range = partial(min, n_records)
|
|
12
13
|
# Pre-generation of static data
|
|
13
14
|
cities = [fake.city() for _ in range(min_range(7000))]
|
|
@@ -35,18 +36,20 @@ def create_fake_data(n_records: int = 1000) -> pl.DataFrame:
|
|
|
35
36
|
data = []
|
|
36
37
|
for i in range(n_records):
|
|
37
38
|
name = generate_name()
|
|
38
|
-
data.append(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
data.append(
|
|
40
|
+
dict(
|
|
41
|
+
ID=randint(1, 1000000),
|
|
42
|
+
Name=name,
|
|
43
|
+
Address=generate_address(),
|
|
44
|
+
City=cities[selector(min_range(7000)) - 1],
|
|
45
|
+
Email=generate_email(name),
|
|
46
|
+
Phone=generate_phone_number(),
|
|
47
|
+
DOB=dob[selector(min_range(100_000)) - 1],
|
|
48
|
+
Work=companies[selector(min_range(100_000)) - 1],
|
|
49
|
+
Zipcode=zipcodes[selector(min_range(200_000)) - 1],
|
|
50
|
+
Country=countries[selector(min_range(50)) - 1],
|
|
51
|
+
)
|
|
52
|
+
)
|
|
50
53
|
|
|
51
54
|
return pl.DataFrame(data)
|
|
52
55
|
|
|
@@ -68,7 +71,7 @@ def standardize_col_dtype(vals):
|
|
|
68
71
|
return [convert_to_string(v) for v in vals]
|
|
69
72
|
|
|
70
73
|
|
|
71
|
-
def create_pl_df_type_save(raw_data: Iterable[Iterable], orient: str =
|
|
74
|
+
def create_pl_df_type_save(raw_data: Iterable[Iterable], orient: str = "row") -> pl.DataFrame:
|
|
72
75
|
"""
|
|
73
76
|
orient : {'col', 'row'}, default None
|
|
74
77
|
Whether to interpret two-dimensional data as columns or as rows. If None,
|
|
@@ -78,7 +81,7 @@ def create_pl_df_type_save(raw_data: Iterable[Iterable], orient: str = 'row') ->
|
|
|
78
81
|
:param orient:
|
|
79
82
|
:return: polars dataframe
|
|
80
83
|
"""
|
|
81
|
-
if orient ==
|
|
82
|
-
raw_data = zip(*raw_data)
|
|
84
|
+
if orient == "row":
|
|
85
|
+
raw_data = zip(*raw_data, strict=False)
|
|
83
86
|
raw_data = [standardize_col_dtype(values) for values in raw_data]
|
|
84
|
-
return pl.DataFrame(raw_data, orient=
|
|
87
|
+
return pl.DataFrame(raw_data, orient="col")
|
|
@@ -4,23 +4,17 @@ This module provides functionality to write Polars LazyFrames to various cloud s
|
|
|
4
4
|
services (S3, Azure ADLS, Google Cloud Storage) in different file formats.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import polars as pl
|
|
8
|
-
from typing import Dict, Any
|
|
9
7
|
from logging import Logger
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import polars as pl
|
|
10
11
|
|
|
11
|
-
from flowfile_worker.external_sources.s3_source.models import
|
|
12
|
-
CloudStorageWriteSettings,
|
|
13
|
-
WriteSettings
|
|
14
|
-
)
|
|
12
|
+
from flowfile_worker.external_sources.s3_source.models import CloudStorageWriteSettings, WriteSettings
|
|
15
13
|
from flowfile_worker.utils import collect_lazy_frame
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
def _write_parquet_to_cloud(
|
|
19
|
-
df: pl.LazyFrame,
|
|
20
|
-
resource_path: str,
|
|
21
|
-
storage_options: Dict[str, Any],
|
|
22
|
-
write_settings: WriteSettings,
|
|
23
|
-
logger: Logger
|
|
17
|
+
df: pl.LazyFrame, resource_path: str, storage_options: dict[str, Any], write_settings: WriteSettings, logger: Logger
|
|
24
18
|
) -> None:
|
|
25
19
|
"""Write LazyFrame to a Parquet file in cloud storage.
|
|
26
20
|
|
|
@@ -49,7 +43,7 @@ def _write_parquet_to_cloud(
|
|
|
49
43
|
# Fall back to collecting and writing if sink fails
|
|
50
44
|
logger.warning(f"Failed to use sink_parquet, falling back to collect and write: {str(e)}")
|
|
51
45
|
pl_df = collect_lazy_frame(df)
|
|
52
|
-
sink_kwargs[
|
|
46
|
+
sink_kwargs["file"] = sink_kwargs.pop("path")
|
|
53
47
|
pl_df.write_parquet(**sink_kwargs)
|
|
54
48
|
|
|
55
49
|
except Exception as e:
|
|
@@ -58,11 +52,7 @@ def _write_parquet_to_cloud(
|
|
|
58
52
|
|
|
59
53
|
|
|
60
54
|
def _write_delta_to_cloud(
|
|
61
|
-
df: pl.LazyFrame,
|
|
62
|
-
resource_path: str,
|
|
63
|
-
storage_options: Dict[str, Any],
|
|
64
|
-
write_settings: WriteSettings,
|
|
65
|
-
logger: Logger
|
|
55
|
+
df: pl.LazyFrame, resource_path: str, storage_options: dict[str, Any], write_settings: WriteSettings, logger: Logger
|
|
66
56
|
) -> None:
|
|
67
57
|
"""Write LazyFrame to Delta Lake format in cloud storage.
|
|
68
58
|
|
|
@@ -85,11 +75,7 @@ def _write_delta_to_cloud(
|
|
|
85
75
|
|
|
86
76
|
|
|
87
77
|
def _write_csv_to_cloud(
|
|
88
|
-
df: pl.LazyFrame,
|
|
89
|
-
resource_path: str,
|
|
90
|
-
storage_options: Dict[str, Any],
|
|
91
|
-
write_settings: WriteSettings,
|
|
92
|
-
logger: Logger
|
|
78
|
+
df: pl.LazyFrame, resource_path: str, storage_options: dict[str, Any], write_settings: WriteSettings, logger: Logger
|
|
93
79
|
) -> None:
|
|
94
80
|
"""Write LazyFrame to a CSV file in cloud storage.
|
|
95
81
|
|
|
@@ -120,11 +106,7 @@ def _write_csv_to_cloud(
|
|
|
120
106
|
|
|
121
107
|
|
|
122
108
|
def _write_json_to_cloud(
|
|
123
|
-
df: pl.LazyFrame,
|
|
124
|
-
resource_path: str,
|
|
125
|
-
storage_options: Dict[str, Any],
|
|
126
|
-
write_settings: WriteSettings,
|
|
127
|
-
logger: Logger
|
|
109
|
+
df: pl.LazyFrame, resource_path: str, storage_options: dict[str, Any], write_settings: WriteSettings, logger: Logger
|
|
128
110
|
) -> None:
|
|
129
111
|
"""Write LazyFrame to a line-delimited JSON (NDJSON) file in cloud storage.
|
|
130
112
|
|
|
@@ -149,7 +131,7 @@ def _write_json_to_cloud(
|
|
|
149
131
|
except Exception as e:
|
|
150
132
|
# Fall back to collecting and writing if sink fails
|
|
151
133
|
pl_df = collect_lazy_frame(df)
|
|
152
|
-
sink_kwargs[
|
|
134
|
+
sink_kwargs["file"] = sink_kwargs.pop("path")
|
|
153
135
|
pl_df.write_ndjson(**sink_kwargs)
|
|
154
136
|
logger.error(f"Failed to use sink_ndjson, falling back to collect and write: {str(e)}")
|
|
155
137
|
|
|
@@ -157,6 +139,7 @@ def _write_json_to_cloud(
|
|
|
157
139
|
logger.error(f"Failed to write JSON to {resource_path}: {str(e)}")
|
|
158
140
|
raise Exception(f"Failed to write JSON to cloud storage: {str(e)}")
|
|
159
141
|
|
|
142
|
+
|
|
160
143
|
writers = {
|
|
161
144
|
"parquet": _write_parquet_to_cloud,
|
|
162
145
|
"delta": _write_delta_to_cloud,
|
|
@@ -165,11 +148,7 @@ writers = {
|
|
|
165
148
|
}
|
|
166
149
|
|
|
167
150
|
|
|
168
|
-
def write_df_to_cloud(
|
|
169
|
-
df: pl.LazyFrame,
|
|
170
|
-
settings: CloudStorageWriteSettings,
|
|
171
|
-
logger: Logger
|
|
172
|
-
) -> None:
|
|
151
|
+
def write_df_to_cloud(df: pl.LazyFrame, settings: CloudStorageWriteSettings, logger: Logger) -> None:
|
|
173
152
|
"""Write a Polars LazyFrame to an object in cloud storage.
|
|
174
153
|
|
|
175
154
|
Supports writing to S3, Azure ADLS, and Google Cloud Storage. Currently supports
|
|
@@ -187,30 +166,18 @@ def write_df_to_cloud(
|
|
|
187
166
|
"""
|
|
188
167
|
connection = settings.connection
|
|
189
168
|
write_settings = settings.write_settings
|
|
190
|
-
logger.info(
|
|
191
|
-
f"Writing to {connection.storage_type} storage: {write_settings.resource_path}"
|
|
192
|
-
)
|
|
169
|
+
logger.info(f"Writing to {connection.storage_type} storage: {write_settings.resource_path}")
|
|
193
170
|
# Validate write mode
|
|
194
|
-
if write_settings.write_mode ==
|
|
195
|
-
raise NotImplementedError(
|
|
196
|
-
"The 'append' write mode is not yet supported for this destination."
|
|
197
|
-
)
|
|
171
|
+
if write_settings.write_mode == "append" and write_settings.file_format != "delta":
|
|
172
|
+
raise NotImplementedError("The 'append' write mode is not yet supported for this destination.")
|
|
198
173
|
|
|
199
174
|
storage_options = connection.get_storage_options()
|
|
200
175
|
|
|
201
176
|
# Dispatch to the appropriate writer
|
|
202
177
|
writer_func = writers.get(write_settings.file_format)
|
|
203
178
|
if not writer_func:
|
|
204
|
-
raise ValueError(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
writer_func(
|
|
209
|
-
df,
|
|
210
|
-
write_settings.resource_path,
|
|
211
|
-
storage_options,
|
|
212
|
-
write_settings,
|
|
213
|
-
logger
|
|
214
|
-
)
|
|
179
|
+
raise ValueError(f"Unsupported file format for writing: {write_settings.file_format}")
|
|
180
|
+
|
|
181
|
+
writer_func(df, write_settings.resource_path, storage_options, write_settings, logger)
|
|
215
182
|
|
|
216
183
|
logger.info(f"Successfully wrote data to {write_settings.resource_path}")
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
"""Cloud storage connection schemas for S3, ADLS, and other cloud providers."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
4
5
|
import boto3
|
|
5
6
|
from pydantic import BaseModel, SecretStr
|
|
7
|
+
|
|
6
8
|
from flowfile_worker.secrets import decrypt_secret
|
|
7
9
|
|
|
8
10
|
CloudStorageType = Literal["s3", "adls", "gcs"]
|
|
9
|
-
AuthMethod = Literal[
|
|
11
|
+
AuthMethod = Literal[
|
|
12
|
+
"access_key", "iam_role", "service_principal", "managed_identity", "sas_token", "aws-cli", "env_vars"
|
|
13
|
+
]
|
|
10
14
|
|
|
11
15
|
|
|
12
|
-
def create_storage_options_from_boto_credentials(
|
|
13
|
-
|
|
16
|
+
def create_storage_options_from_boto_credentials(
|
|
17
|
+
profile_name: str | None, region_name: str | None = None
|
|
18
|
+
) -> dict[str, Any]:
|
|
14
19
|
"""
|
|
15
20
|
Create a storage options dictionary from AWS credentials using a boto3 profile.
|
|
16
21
|
This is the most robust way to handle profile-based authentication as it
|
|
@@ -47,29 +52,30 @@ def create_storage_options_from_boto_credentials(profile_name: Optional[str],
|
|
|
47
52
|
|
|
48
53
|
class FullCloudStorageConnection(BaseModel):
|
|
49
54
|
"""Internal model with decrypted secrets"""
|
|
55
|
+
|
|
50
56
|
storage_type: CloudStorageType
|
|
51
57
|
auth_method: AuthMethod
|
|
52
|
-
connection_name:
|
|
58
|
+
connection_name: str | None = "None" # This is the reference to the item we will fetch that contains the data
|
|
53
59
|
|
|
54
60
|
# AWS S3
|
|
55
|
-
aws_region:
|
|
56
|
-
aws_access_key_id:
|
|
57
|
-
aws_secret_access_key:
|
|
58
|
-
aws_role_arn:
|
|
59
|
-
aws_allow_unsafe_html:
|
|
61
|
+
aws_region: str | None = None
|
|
62
|
+
aws_access_key_id: str | None = None
|
|
63
|
+
aws_secret_access_key: SecretStr | None = None
|
|
64
|
+
aws_role_arn: str | None = None
|
|
65
|
+
aws_allow_unsafe_html: bool | None = None
|
|
60
66
|
|
|
61
67
|
# Azure ADLS
|
|
62
|
-
azure_account_name:
|
|
63
|
-
azure_account_key:
|
|
64
|
-
azure_tenant_id:
|
|
65
|
-
azure_client_id:
|
|
66
|
-
azure_client_secret:
|
|
68
|
+
azure_account_name: str | None = None
|
|
69
|
+
azure_account_key: SecretStr | None = None
|
|
70
|
+
azure_tenant_id: str | None = None
|
|
71
|
+
azure_client_id: str | None = None
|
|
72
|
+
azure_client_secret: SecretStr | None = None
|
|
67
73
|
|
|
68
74
|
# Common
|
|
69
|
-
endpoint_url:
|
|
75
|
+
endpoint_url: str | None = None
|
|
70
76
|
verify_ssl: bool = True
|
|
71
77
|
|
|
72
|
-
def get_storage_options(self) ->
|
|
78
|
+
def get_storage_options(self) -> dict[str, Any]:
|
|
73
79
|
"""
|
|
74
80
|
Build storage options dict based on the connection type and auth method.
|
|
75
81
|
|
|
@@ -79,15 +85,14 @@ class FullCloudStorageConnection(BaseModel):
|
|
|
79
85
|
if self.storage_type == "s3":
|
|
80
86
|
return self._get_s3_storage_options()
|
|
81
87
|
|
|
82
|
-
def _get_s3_storage_options(self) ->
|
|
88
|
+
def _get_s3_storage_options(self) -> dict[str, Any]:
|
|
83
89
|
"""Build S3-specific storage options."""
|
|
84
90
|
auth_method = self.auth_method
|
|
85
91
|
print(f"Building S3 storage options for auth_method: '{auth_method}'")
|
|
86
92
|
|
|
87
93
|
if auth_method == "aws-cli":
|
|
88
94
|
return create_storage_options_from_boto_credentials(
|
|
89
|
-
profile_name=self.connection_name,
|
|
90
|
-
region_name=self.aws_region
|
|
95
|
+
profile_name=self.connection_name, region_name=self.aws_region
|
|
91
96
|
)
|
|
92
97
|
|
|
93
98
|
storage_options = {}
|
|
@@ -103,27 +108,29 @@ class FullCloudStorageConnection(BaseModel):
|
|
|
103
108
|
if auth_method == "access_key":
|
|
104
109
|
storage_options["aws_access_key_id"] = self.aws_access_key_id
|
|
105
110
|
storage_options["aws_secret_access_key"] = decrypt_secret(
|
|
106
|
-
self.aws_secret_access_key.get_secret_value()
|
|
111
|
+
self.aws_secret_access_key.get_secret_value()
|
|
112
|
+
).get_secret_value()
|
|
107
113
|
# Explicitly clear any session token from the environment
|
|
108
114
|
storage_options["aws_session_token"] = ""
|
|
109
115
|
|
|
110
116
|
elif auth_method == "iam_role":
|
|
111
117
|
# Correctly implement IAM role assumption using boto3 STS client.
|
|
112
|
-
sts_client = boto3.client(
|
|
118
|
+
sts_client = boto3.client("sts", region_name=self.aws_region)
|
|
113
119
|
assumed_role_object = sts_client.assume_role(
|
|
114
120
|
RoleArn=self.aws_role_arn,
|
|
115
|
-
RoleSessionName="PolarsCloudStorageReaderSession" # A descriptive session name
|
|
121
|
+
RoleSessionName="PolarsCloudStorageReaderSession", # A descriptive session name
|
|
116
122
|
)
|
|
117
|
-
credentials = assumed_role_object[
|
|
118
|
-
storage_options["aws_access_key_id"] = credentials[
|
|
119
|
-
storage_options["aws_secret_access_key"] = decrypt_secret(credentials[
|
|
120
|
-
storage_options["aws_session_token"] = decrypt_secret(credentials[
|
|
123
|
+
credentials = assumed_role_object["Credentials"]
|
|
124
|
+
storage_options["aws_access_key_id"] = credentials["AccessKeyId"]
|
|
125
|
+
storage_options["aws_secret_access_key"] = decrypt_secret(credentials["SecretAccessKey"]).get_secret_value()
|
|
126
|
+
storage_options["aws_session_token"] = decrypt_secret(credentials["SessionToken"]).get_secret_value()
|
|
121
127
|
|
|
122
128
|
return storage_options
|
|
123
129
|
|
|
124
130
|
|
|
125
131
|
class WriteSettings(BaseModel):
|
|
126
132
|
"""Settings for writing to cloud storage"""
|
|
133
|
+
|
|
127
134
|
resource_path: str # s3://bucket/path/to/file.csv
|
|
128
135
|
|
|
129
136
|
write_mode: Literal["overwrite", "append"] = "overwrite"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
|
|
1
3
|
import polars as pl
|
|
4
|
+
|
|
2
5
|
from flowfile_worker.external_sources.sql_source.models import DatabaseReadSettings, DatabaseWriteSettings
|
|
3
|
-
from io import BytesIO
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
def write_df_to_database(df: pl.DataFrame, database_write_settings: DatabaseWriteSettings):
|
|
@@ -11,9 +13,11 @@ def write_df_to_database(df: pl.DataFrame, database_write_settings: DatabaseWrit
|
|
|
11
13
|
database_write_settings (DatabaseWriteSettings): The settings for the database connection and table.
|
|
12
14
|
"""
|
|
13
15
|
# Write the DataFrame to the database
|
|
14
|
-
df.write_database(
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
df.write_database(
|
|
17
|
+
table_name=database_write_settings.table_name,
|
|
18
|
+
connection=database_write_settings.connection.create_uri(),
|
|
19
|
+
if_table_exists=database_write_settings.if_exists,
|
|
20
|
+
)
|
|
17
21
|
return True
|
|
18
22
|
|
|
19
23
|
|
|
@@ -53,4 +57,3 @@ def read_sql_source(database_read_settings: DatabaseReadSettings):
|
|
|
53
57
|
# Read the query into a DataFrame
|
|
54
58
|
df = read_query_as_pd_df(database_read_settings.query, database_read_settings.connection.create_uri())
|
|
55
59
|
return df
|
|
56
|
-
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
2
3
|
from pydantic import BaseModel, SecretStr
|
|
4
|
+
|
|
3
5
|
from flowfile_worker.secrets import decrypt_secret
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class DataBaseConnection(BaseModel):
|
|
7
9
|
"""Database connection configuration with secure password handling."""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
|
|
11
|
+
username: str | None = None
|
|
12
|
+
password: SecretStr | None = None # Encrypted password
|
|
13
|
+
host: str | None = None
|
|
14
|
+
port: int | None = None
|
|
15
|
+
database: str | None = None # The database name
|
|
13
16
|
database_type: str = "postgresql" # Database type (postgresql, mysql, etc.)
|
|
14
|
-
url:
|
|
17
|
+
url: str | None = None
|
|
15
18
|
|
|
16
19
|
def get_decrypted_secret(self) -> SecretStr:
|
|
17
20
|
return decrypt_secret(self.password.get_secret_value())
|
|
@@ -48,7 +51,6 @@ class DataBaseConnection(BaseModel):
|
|
|
48
51
|
if self.port:
|
|
49
52
|
port_section = f":{self.port}"
|
|
50
53
|
if self.database:
|
|
51
|
-
|
|
52
54
|
base_uri = f"{self.database_type}://{credentials}{self.host}{port_section}/{self.database}"
|
|
53
55
|
else:
|
|
54
56
|
base_uri = f"{self.database_type}://{credentials}{self.host}{port_section}"
|
|
@@ -57,6 +59,7 @@ class DataBaseConnection(BaseModel):
|
|
|
57
59
|
|
|
58
60
|
class DatabaseReadSettings(BaseModel):
|
|
59
61
|
"""Settings for SQL source."""
|
|
62
|
+
|
|
60
63
|
connection: DataBaseConnection
|
|
61
64
|
query: str
|
|
62
65
|
flowfile_flow_id: int = 1
|
|
@@ -65,8 +68,9 @@ class DatabaseReadSettings(BaseModel):
|
|
|
65
68
|
|
|
66
69
|
class DatabaseWriteSettings(BaseModel):
|
|
67
70
|
"""Settings for SQL sink."""
|
|
71
|
+
|
|
68
72
|
connection: DataBaseConnection
|
|
69
73
|
table_name: str
|
|
70
|
-
if_exists: Literal[
|
|
74
|
+
if_exists: Literal["append", "replace", "fail"] = "append"
|
|
71
75
|
flowfile_flow_id: int = 1
|
|
72
76
|
flowfile_node_id: int | str = -1
|
flowfile_worker/flow_logger.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
import requests
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
from flowfile_worker.configs import FLOWFILE_CORE_URI
|
|
6
|
+
from flowfile_worker.models import RawLogInput
|
|
5
7
|
|
|
6
8
|
LOGGING_URL = FLOWFILE_CORE_URI + "/raw_logs"
|
|
7
9
|
|
|
@@ -23,12 +25,12 @@ class FlowfileLogHandler(logging.Handler):
|
|
|
23
25
|
flowfile_flow_id=self.flowfile_flow_id,
|
|
24
26
|
log_message=log_message,
|
|
25
27
|
log_type=record.levelname.upper(),
|
|
26
|
-
extra={
|
|
27
|
-
}
|
|
28
|
+
extra={},
|
|
28
29
|
)
|
|
29
30
|
if self.flowfile_flow_id != -1 and self.flowfile_node_id != -1:
|
|
30
|
-
response = requests.post(
|
|
31
|
-
|
|
31
|
+
response = requests.post(
|
|
32
|
+
LOGGING_URL, json=raw_log_input.__dict__, headers={"Content-Type": "application/json"}
|
|
33
|
+
)
|
|
32
34
|
if response.status_code != 200:
|
|
33
35
|
raise Exception(f"Failed to send log: {response.text}")
|
|
34
36
|
except Exception as e:
|
|
@@ -45,13 +47,13 @@ def get_worker_logger(flowfile_flow_id: int, flowfile_node_id: int | str) -> log
|
|
|
45
47
|
if not logger.handlers:
|
|
46
48
|
stream_handler = logging.StreamHandler()
|
|
47
49
|
stream_handler.setLevel(logging.DEBUG)
|
|
48
|
-
stream_formatter = logging.Formatter(
|
|
50
|
+
stream_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
49
51
|
stream_handler.setFormatter(stream_formatter)
|
|
50
52
|
logger.addHandler(stream_handler)
|
|
51
53
|
|
|
52
|
-
http_handler = FlowfileLogHandler(flowfile_flow_id=flowfile_flow_id, flowfile_node_id
|
|
54
|
+
http_handler = FlowfileLogHandler(flowfile_flow_id=flowfile_flow_id, flowfile_node_id=flowfile_node_id)
|
|
53
55
|
http_handler.setLevel(logging.INFO)
|
|
54
|
-
http_formatter = logging.Formatter(
|
|
56
|
+
http_formatter = logging.Formatter("%(message)s")
|
|
55
57
|
http_handler.setFormatter(http_formatter)
|
|
56
58
|
logger.addHandler(http_handler)
|
|
57
59
|
|