Flowfile 0.4.1__py3-none-any.whl → 0.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- build_backends/main.py +25 -22
- build_backends/main_prd.py +10 -19
- flowfile/__init__.py +179 -73
- flowfile/__main__.py +10 -7
- flowfile/api.py +52 -59
- flowfile/web/__init__.py +14 -9
- flowfile/web/static/assets/AdminView-49392a9a.js +713 -0
- flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
- flowfile/web/static/assets/CloudConnectionView-36bcd6df.css +72 -0
- flowfile/web/static/assets/{CloudConnectionManager-d3248f8d.js → CloudConnectionView-f13f202b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-d65bf041.js → CloudStorageReader-0023d4a5.js} +10 -8
- flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
- flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
- flowfile/web/static/assets/{CloudStorageWriter-e83be3ed.js → CloudStorageWriter-8e781e11.js} +10 -8
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-cce661cf.js → ColumnSelector-8ad68ea9.js} +3 -5
- flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-11a4652a.js → ContextMenu-31ee57f0.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-160afb08.js → ContextMenu-69a74055.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-cf18d2cc.js → ContextMenu-8e2051c6.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-4c74eef1.css → ContextMenu-8ec1729e.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-63cfa99b.css → ContextMenu-9b310c60.css} +6 -6
- flowfile/web/static/assets/{CrossJoin-d395d38c.js → CrossJoin-03df6938.js} +12 -10
- flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
- flowfile/web/static/assets/CustomNode-59e99a86.css +32 -0
- flowfile/web/static/assets/{CustomNode-b812dc0b.js → CustomNode-8479239b.js} +36 -24
- flowfile/web/static/assets/{DatabaseConnectionSettings-7000bf2c.js → DatabaseConnectionSettings-869e3efd.js} +5 -4
- flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-e91df89a.css} +13 -13
- flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-36898a00.css} +24 -24
- flowfile/web/static/assets/{DatabaseReader-4f035d0c.js → DatabaseReader-c58b9552.js} +25 -15
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseManager-9662ec5b.js → DatabaseView-d26a9140.js} +11 -11
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
- flowfile/web/static/assets/{DatabaseWriter-f65dcd54.js → DatabaseWriter-4d05ddc7.js} +17 -10
- flowfile/web/static/assets/{designer-e3c150ec.css → DesignerView-a6d0ee84.css} +629 -538
- flowfile/web/static/assets/{designer-f3656d8c.js → DesignerView-e6f5c0e8.js} +1214 -3209
- flowfile/web/static/assets/{documentation-52b241e7.js → DocumentationView-2e78ef1b.js} +5 -5
- flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-fd46c656.css} +7 -7
- flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
- flowfile/web/static/assets/{ExploreData-94c43dfc.js → ExploreData-7b54caca.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-ac04b3cc.js → ExternalSource-3fa399b2.js} +9 -7
- flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-47ab05a3.css} +17 -17
- flowfile/web/static/assets/Filter-7494ea97.css +48 -0
- flowfile/web/static/assets/Filter-8cbbdbf3.js +287 -0
- flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
- flowfile/web/static/assets/{Formula-71472193.js → Formula-aac42b1e.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-b317f631.js → FuzzyMatch-cd9bbfca.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-754a234f.js → GraphSolver-c7e6780e.js} +13 -11
- flowfile/web/static/assets/{GroupBy-6c6f9802.js → GroupBy-93c5d22b.js} +9 -7
- flowfile/web/static/assets/{GroupBy-b9505323.css → GroupBy-be7ac0bf.css} +10 -10
- flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
- flowfile/web/static/assets/{Join-a1b800be.js → Join-a19b2de2.js} +13 -11
- flowfile/web/static/assets/LoginView-0df4ed0a.js +134 -0
- flowfile/web/static/assets/LoginView-d325d632.css +172 -0
- flowfile/web/static/assets/ManualInput-3702e677.css +293 -0
- flowfile/web/static/assets/{ManualInput-a9640276.js → ManualInput-8d3374b2.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-97213888.js → MultiSelect-ad1b6243.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-6ffe088a.js → MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js} +1 -1
- flowfile/web/static/assets/NodeDesigner-40b647c9.js +2610 -0
- flowfile/web/static/assets/NodeDesigner-5f53be3f.css +1429 -0
- flowfile/web/static/assets/{NumericInput-e638088a.js → NumericInput-7100234c.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-90eb2cba.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
- flowfile/web/static/assets/{Output-ddc9079f.css → Output-35e97000.css} +6 -6
- flowfile/web/static/assets/{Output-76750610.js → Output-f5efd2aa.js} +60 -38
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-7814803f.js → Pivot-d981d23c.js} +11 -9
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f92137d2.js → PivotValidation-39386e95.js} +3 -3
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-76dd431a.js → PivotValidation-63de1f73.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-889c3008.js → PolarsCode-f9d69217.js} +18 -9
- flowfile/web/static/assets/PopOver-b22f049e.js +939 -0
- flowfile/web/static/assets/PopOver-d96599db.css +33 -0
- flowfile/web/static/assets/{Read-6b17491f.css → Read-36e7bd51.css} +12 -12
- flowfile/web/static/assets/{Read-637b72a7.js → Read-aec2e377.js} +83 -105
- flowfile/web/static/assets/{RecordCount-2b050c41.js → RecordCount-78ed6845.js} +6 -4
- flowfile/web/static/assets/{RecordId-81df7784.js → RecordId-2156e890.js} +8 -6
- flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
- flowfile/web/static/assets/{SQLQueryComponent-88dcfe53.js → SQLQueryComponent-48c72f5b.js} +3 -3
- flowfile/web/static/assets/{Sample-258ad2a9.js → Sample-1352ca74.js} +6 -4
- flowfile/web/static/assets/SecretSelector-22b5ff89.js +113 -0
- flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
- flowfile/web/static/assets/{SecretManager-2a2cb7e2.js → SecretsView-17df66ee.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-850215fd.js → Select-0aee4c54.js} +9 -7
- flowfile/web/static/assets/{SettingsSection-55bae608.js → SettingsSection-0784e157.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-71e6b7e3.css → SettingsSection-07fbbc39.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-5c696bee.css → SettingsSection-26fe48d4.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-0e8d9123.js → SettingsSection-cd341bb6.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-29b4fa6b.js → SettingsSection-f2002a6d.js} +3 -3
- flowfile/web/static/assets/{SingleSelect-bebd408b.js → SingleSelect-460cc0ea.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-6093741c.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
- flowfile/web/static/assets/{SliderInput-6a05ab61.js → SliderInput-5d926864.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-10ab48ed.js → Sort-3cdc971b.js} +9 -7
- flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
- flowfile/web/static/assets/{TextInput-df9d6259.js → TextInput-a2d0bfbd.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-000e1178.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-6c2d93d8.js → TextToRows-918945f7.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-0ff7ac52.js → ToggleSwitch-f0ef5196.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-c6dc3029.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
- flowfile/web/static/assets/{UnavailableFields-1bab97cb.js → UnavailableFields-bdad6144.js} +4 -4
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/{Union-b563478a.js → Union-e8ab8c86.js} +8 -6
- flowfile/web/static/assets/{Unique-f90db5db.js → Unique-8cd4f976.js} +13 -22
- flowfile/web/static/assets/{Sort-3643d625.css → Unique-9fb2f567.css} +10 -10
- flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-710a2948.css} +7 -7
- flowfile/web/static/assets/{Unpivot-bcb0025f.js → Unpivot-8da14095.js} +10 -8
- flowfile/web/static/assets/{UnpivotValidation-c4e73b04.js → UnpivotValidation-6f7d89ff.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-bb8535e2.js → VueGraphicWalker-3fb312e1.js} +4 -4
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{api-4c8e3822.js → api-24483f0d.js} +1 -1
- flowfile/web/static/assets/{api-2d6adc4f.js → api-8b81fa73.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
- flowfile/web/static/assets/{dropDown-1bca8a74.js → dropDown-ac0fda9d.js} +3 -3
- flowfile/web/static/assets/{fullEditor-2985687e.js → fullEditor-5497a84a.js} +11 -10
- flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-a0be62b3.css} +74 -62
- flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
- flowfile/web/static/assets/{genericNodeSettings-0476ba4e.js → genericNodeSettings-99014e1d.js} +5 -5
- flowfile/web/static/assets/index-07dda503.js +38 -0
- flowfile/web/static/assets/index-3ba44389.js +2696 -0
- flowfile/web/static/assets/{index-50508d4d.css → index-e6289dd0.css} +1945 -569
- flowfile/web/static/assets/{index-246f201c.js → index-fb6493ae.js} +41626 -40869
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
- flowfile/web/static/assets/{outputCsv-d686eeaf.js → outputCsv-8f8ba42d.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-8809ea2f.js → outputExcel-393f4fef.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/{outputParquet-53ba645a.js → outputParquet-07c81f65.js} +4 -4
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{readCsv-053bf97b.js → readCsv-07f6d9ad.js} +21 -20
- flowfile/web/static/assets/{readCsv-bca3ed53.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readExcel-e1b381ea.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-ad531eab.js → readExcel-ed69bc8f.js} +10 -12
- flowfile/web/static/assets/{readParquet-cee068e2.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/{readParquet-58e899a1.js → readParquet-e3ed4528.js} +4 -7
- flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
- flowfile/web/static/assets/{selectDynamic-b38de2ba.js → selectDynamic-80b92899.js} +5 -5
- flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
- flowfile/web/static/assets/{vue-codemirror.esm-db9b8936.js → vue-codemirror.esm-0965f39f.js} +31 -637
- flowfile/web/static/assets/{vue-content-loader.es-b5f3ac30.js → vue-content-loader.es-c506ad97.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +4 -4
- flowfile-0.5.3.dist-info/RECORD +402 -0
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +1 -1
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +1 -0
- flowfile_core/__init__.py +13 -3
- flowfile_core/auth/jwt.py +51 -16
- flowfile_core/auth/models.py +32 -7
- flowfile_core/auth/password.py +89 -0
- flowfile_core/auth/secrets.py +8 -6
- flowfile_core/configs/__init__.py +9 -7
- flowfile_core/configs/flow_logger.py +15 -14
- flowfile_core/configs/node_store/__init__.py +72 -4
- flowfile_core/configs/node_store/nodes.py +155 -172
- flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
- flowfile_core/configs/settings.py +28 -15
- flowfile_core/database/connection.py +7 -6
- flowfile_core/database/init_db.py +96 -2
- flowfile_core/database/models.py +3 -1
- flowfile_core/fileExplorer/__init__.py +17 -0
- flowfile_core/fileExplorer/funcs.py +123 -57
- flowfile_core/fileExplorer/utils.py +10 -11
- flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
- flowfile_core/flowfile/analytics/analytics_processor.py +27 -24
- flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
- flowfile_core/flowfile/analytics/utils.py +1 -1
- flowfile_core/flowfile/code_generator/code_generator.py +391 -279
- flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
- flowfile_core/flowfile/connection_manager/models.py +1 -1
- flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
- flowfile_core/flowfile/database_connection_manager/models.py +1 -1
- flowfile_core/flowfile/extensions.py +17 -12
- flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
- flowfile_core/flowfile/flow_data_engine/create/funcs.py +152 -103
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +526 -477
- flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
- flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +43 -32
- flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
- flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +15 -11
- flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
- flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
- flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
- flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +360 -191
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
- flowfile_core/flowfile/flow_data_engine/utils.py +101 -67
- flowfile_core/flowfile/flow_graph.py +1011 -561
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +332 -232
- flowfile_core/flowfile/flow_node/models.py +54 -41
- flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
- flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
- flowfile_core/flowfile/handler.py +82 -32
- flowfile_core/flowfile/manage/compatibility_enhancements.py +493 -47
- flowfile_core/flowfile/manage/io_flowfile.py +391 -0
- flowfile_core/flowfile/node_designer/__init__.py +15 -13
- flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
- flowfile_core/flowfile/node_designer/custom_node.py +162 -36
- flowfile_core/flowfile/node_designer/ui_components.py +136 -35
- flowfile_core/flowfile/schema_callbacks.py +77 -54
- flowfile_core/flowfile/setting_generator/__init__.py +0 -1
- flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
- flowfile_core/flowfile/setting_generator/settings.py +72 -55
- flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
- flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
- flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
- flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
- flowfile_core/flowfile/util/calculate_layout.py +9 -13
- flowfile_core/flowfile/util/execution_orderer.py +25 -17
- flowfile_core/flowfile/util/node_skipper.py +4 -4
- flowfile_core/flowfile/utils.py +19 -21
- flowfile_core/main.py +26 -19
- flowfile_core/routes/auth.py +284 -11
- flowfile_core/routes/cloud_connections.py +25 -25
- flowfile_core/routes/logs.py +21 -29
- flowfile_core/routes/public.py +3 -3
- flowfile_core/routes/routes.py +77 -43
- flowfile_core/routes/secrets.py +25 -27
- flowfile_core/routes/user_defined_components.py +483 -4
- flowfile_core/run_lock.py +0 -1
- flowfile_core/schemas/__init__.py +4 -6
- flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
- flowfile_core/schemas/cloud_storage_schemas.py +59 -55
- flowfile_core/schemas/input_schema.py +398 -154
- flowfile_core/schemas/output_model.py +50 -35
- flowfile_core/schemas/schemas.py +207 -67
- flowfile_core/schemas/transform_schema.py +1360 -435
- flowfile_core/schemas/yaml_types.py +117 -0
- flowfile_core/secret_manager/secret_manager.py +17 -13
- flowfile_core/{flowfile/node_designer/data_types.py → types.py} +33 -3
- flowfile_core/utils/arrow_reader.py +7 -6
- flowfile_core/utils/excel_file_manager.py +3 -3
- flowfile_core/utils/fileManager.py +7 -7
- flowfile_core/utils/fl_executor.py +8 -10
- flowfile_core/utils/utils.py +4 -4
- flowfile_core/utils/validate_setup.py +5 -4
- flowfile_frame/__init__.py +107 -50
- flowfile_frame/adapters.py +2 -9
- flowfile_frame/adding_expr.py +73 -32
- flowfile_frame/cloud_storage/frame_helpers.py +27 -23
- flowfile_frame/cloud_storage/secret_manager.py +12 -26
- flowfile_frame/config.py +2 -5
- flowfile_frame/expr.py +311 -218
- flowfile_frame/expr.pyi +160 -159
- flowfile_frame/expr_name.py +23 -23
- flowfile_frame/flow_frame.py +581 -489
- flowfile_frame/flow_frame.pyi +123 -104
- flowfile_frame/flow_frame_methods.py +236 -252
- flowfile_frame/group_frame.py +50 -20
- flowfile_frame/join.py +2 -2
- flowfile_frame/lazy.py +129 -87
- flowfile_frame/lazy_methods.py +83 -30
- flowfile_frame/list_name_space.py +55 -50
- flowfile_frame/selectors.py +148 -68
- flowfile_frame/series.py +9 -7
- flowfile_frame/utils.py +19 -21
- flowfile_worker/__init__.py +12 -4
- flowfile_worker/configs.py +11 -19
- flowfile_worker/create/__init__.py +14 -27
- flowfile_worker/create/funcs.py +143 -94
- flowfile_worker/create/models.py +139 -68
- flowfile_worker/create/pl_types.py +14 -15
- flowfile_worker/create/read_excel_tables.py +34 -41
- flowfile_worker/create/utils.py +22 -19
- flowfile_worker/external_sources/s3_source/main.py +18 -51
- flowfile_worker/external_sources/s3_source/models.py +34 -27
- flowfile_worker/external_sources/sql_source/main.py +8 -5
- flowfile_worker/external_sources/sql_source/models.py +13 -9
- flowfile_worker/flow_logger.py +10 -8
- flowfile_worker/funcs.py +214 -155
- flowfile_worker/main.py +11 -17
- flowfile_worker/models.py +35 -28
- flowfile_worker/process_manager.py +2 -3
- flowfile_worker/routes.py +121 -93
- flowfile_worker/secrets.py +9 -6
- flowfile_worker/spawner.py +80 -49
- flowfile_worker/utils.py +3 -2
- shared/__init__.py +2 -7
- shared/storage_config.py +25 -13
- test_utils/postgres/commands.py +3 -2
- test_utils/postgres/fixtures.py +9 -9
- test_utils/s3/commands.py +1 -1
- test_utils/s3/data_generator.py +3 -4
- test_utils/s3/demo_data_generator.py +4 -7
- test_utils/s3/fixtures.py +7 -5
- tools/migrate/README.md +56 -0
- tools/migrate/__init__.py +12 -0
- tools/migrate/__main__.py +118 -0
- tools/migrate/legacy_schemas.py +682 -0
- tools/migrate/migrate.py +610 -0
- tools/migrate/tests/__init__.py +0 -0
- tools/migrate/tests/conftest.py +21 -0
- tools/migrate/tests/test_migrate.py +622 -0
- tools/migrate/tests/test_migration_e2e.py +1009 -0
- tools/migrate/tests/test_node_migrations.py +843 -0
- flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
- flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
- flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
- flowfile/web/static/assets/Filter-812dcbca.js +0 -164
- flowfile/web/static/assets/Filter-f62091b3.css +0 -20
- flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
- flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
- flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
- flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
- flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
- flowfile/web/static/assets/secretApi-538058f3.js +0 -46
- flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
- flowfile-0.4.1.dist-info/RECORD +0 -376
- flowfile_core/flowfile/manage/open_flowfile.py +0 -143
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
- /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
2
3
|
from flowfile_core.flowfile.connection_manager.models import Connection
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class ConnectionManager:
|
|
6
|
-
connections:
|
|
7
|
+
connections: dict[str, dict[str, Connection]]
|
|
7
8
|
|
|
8
9
|
def add_connection(self, connection_group: str, connection_name: str, connection: Connection):
|
|
9
10
|
existing_connections_in_group = self.connections.get(connection_group)
|
|
@@ -34,15 +35,15 @@ class ConnectionManager:
|
|
|
34
35
|
self.raise_if_connection_does_not_exist(connection_group, connection_name)
|
|
35
36
|
self.connections[connection_group][connection_name] = connection
|
|
36
37
|
|
|
37
|
-
def insert_settings_raw(self, connection_group: str, connection_name: str, settings:
|
|
38
|
+
def insert_settings_raw(self, connection_group: str, connection_name: str, settings: dict[str, Any]):
|
|
38
39
|
connection = Connection(group=connection_group, name=connection_name, config_setting=settings)
|
|
39
40
|
self.add_connection(connection_group, connection_name, connection)
|
|
40
41
|
|
|
41
|
-
def connection_groups(self) ->
|
|
42
|
+
def connection_groups(self) -> list[str]:
|
|
42
43
|
return list(self.connections.keys())
|
|
43
44
|
|
|
44
45
|
def get_available_connections_in_group(self, group_name: str):
|
|
45
46
|
connection_group = self.connections.get(group_name)
|
|
46
47
|
if connection_group is None:
|
|
47
48
|
return []
|
|
48
|
-
return list(connection_group.keys())
|
|
49
|
+
return list(connection_group.keys())
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
from flowfile_core.schemas.input_schema import FullDatabaseConnection, FullDatabaseConnectionInterface
|
|
2
|
-
from flowfile_core.schemas.cloud_storage_schemas import FullCloudStorageConnection, FullCloudStorageConnectionInterface
|
|
3
1
|
from sqlalchemy.orm import Session
|
|
4
|
-
|
|
5
|
-
CloudStorageConnection as DBCloudStorageConnection)
|
|
6
|
-
from flowfile_core.secret_manager.secret_manager import store_secret, SecretInput, decrypt_secret
|
|
2
|
+
|
|
7
3
|
from flowfile_core.database.connection import get_db_context
|
|
4
|
+
from flowfile_core.database.models import CloudStorageConnection as DBCloudStorageConnection
|
|
5
|
+
from flowfile_core.database.models import DatabaseConnection as DBConnectionModel
|
|
6
|
+
from flowfile_core.database.models import Secret
|
|
7
|
+
from flowfile_core.schemas.cloud_storage_schemas import FullCloudStorageConnection, FullCloudStorageConnectionInterface
|
|
8
|
+
from flowfile_core.schemas.input_schema import FullDatabaseConnection, FullDatabaseConnectionInterface
|
|
9
|
+
from flowfile_core.secret_manager.secret_manager import SecretInput, decrypt_secret, store_secret
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def store_database_connection(db: Session, connection: FullDatabaseConnection, user_id: int) -> DBConnectionModel:
|
|
@@ -32,7 +34,7 @@ def store_database_connection(db: Session, connection: FullDatabaseConnection, u
|
|
|
32
34
|
username=connection.username,
|
|
33
35
|
password_id=password_id,
|
|
34
36
|
ssl_enabled=connection.ssl_enabled,
|
|
35
|
-
user_id=user_id
|
|
37
|
+
user_id=user_id,
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
# Add and commit the new connection to the database
|
|
@@ -47,10 +49,11 @@ def get_database_connection(db: Session, connection_name: str, user_id: int) ->
|
|
|
47
49
|
"""
|
|
48
50
|
Get a database connection by its name and user ID.
|
|
49
51
|
"""
|
|
50
|
-
db_connection =
|
|
51
|
-
DBConnectionModel
|
|
52
|
-
DBConnectionModel.user_id == user_id
|
|
53
|
-
|
|
52
|
+
db_connection = (
|
|
53
|
+
db.query(DBConnectionModel)
|
|
54
|
+
.filter(DBConnectionModel.connection_name == connection_name, DBConnectionModel.user_id == user_id)
|
|
55
|
+
.first()
|
|
56
|
+
)
|
|
54
57
|
|
|
55
58
|
return db_connection
|
|
56
59
|
|
|
@@ -59,10 +62,13 @@ def get_cloud_connection(db: Session, connection_name: str, user_id: int) -> DBC
|
|
|
59
62
|
"""
|
|
60
63
|
Get a cloud storage connection by its name and user ID.
|
|
61
64
|
"""
|
|
62
|
-
db_connection =
|
|
63
|
-
DBCloudStorageConnection
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
db_connection = (
|
|
66
|
+
db.query(DBCloudStorageConnection)
|
|
67
|
+
.filter(
|
|
68
|
+
DBCloudStorageConnection.connection_name == connection_name, DBCloudStorageConnection.user_id == user_id
|
|
69
|
+
)
|
|
70
|
+
.first()
|
|
71
|
+
)
|
|
66
72
|
|
|
67
73
|
return db_connection
|
|
68
74
|
|
|
@@ -87,7 +93,7 @@ def get_database_connection_schema(db: Session, connection_name: str, user_id: i
|
|
|
87
93
|
database_type=db_connection.database_type,
|
|
88
94
|
username=db_connection.username,
|
|
89
95
|
password=password_secret.encrypted_value,
|
|
90
|
-
ssl_enabled=db_connection.ssl_enabled
|
|
96
|
+
ssl_enabled=db_connection.ssl_enabled,
|
|
91
97
|
)
|
|
92
98
|
|
|
93
99
|
return None
|
|
@@ -116,10 +122,11 @@ def delete_database_connection(db: Session, connection_name: str, user_id: int)
|
|
|
116
122
|
"""
|
|
117
123
|
Delete a database connection by its name and user ID.
|
|
118
124
|
"""
|
|
119
|
-
db_connection =
|
|
120
|
-
DBConnectionModel
|
|
121
|
-
DBConnectionModel.user_id == user_id
|
|
122
|
-
|
|
125
|
+
db_connection = (
|
|
126
|
+
db.query(DBConnectionModel)
|
|
127
|
+
.filter(DBConnectionModel.connection_name == connection_name, DBConnectionModel.user_id == user_id)
|
|
128
|
+
.first()
|
|
129
|
+
)
|
|
123
130
|
|
|
124
131
|
if db_connection:
|
|
125
132
|
db.delete(db_connection)
|
|
@@ -131,7 +138,8 @@ def delete_database_connection(db: Session, connection_name: str, user_id: int)
|
|
|
131
138
|
|
|
132
139
|
|
|
133
140
|
def database_connection_interface_from_db_connection(
|
|
134
|
-
|
|
141
|
+
db_connection: DBConnectionModel,
|
|
142
|
+
) -> FullDatabaseConnectionInterface:
|
|
135
143
|
"""
|
|
136
144
|
Convert a database connection from the database model to the interface model.
|
|
137
145
|
"""
|
|
@@ -142,7 +150,7 @@ def database_connection_interface_from_db_connection(
|
|
|
142
150
|
host=db_connection.host,
|
|
143
151
|
port=db_connection.port,
|
|
144
152
|
database=db_connection.database,
|
|
145
|
-
ssl_enabled=db_connection.ssl_enabled
|
|
153
|
+
ssl_enabled=db_connection.ssl_enabled,
|
|
146
154
|
)
|
|
147
155
|
|
|
148
156
|
|
|
@@ -151,9 +159,7 @@ def get_all_database_connections_interface(db: Session, user_id: int) -> list[Fu
|
|
|
151
159
|
Get all database connections for a user.
|
|
152
160
|
"""
|
|
153
161
|
# Get the raw query results
|
|
154
|
-
query_results = db.query(DBConnectionModel).filter(
|
|
155
|
-
DBConnectionModel.user_id == user_id
|
|
156
|
-
).all()
|
|
162
|
+
query_results = db.query(DBConnectionModel).filter(DBConnectionModel.user_id == user_id).all()
|
|
157
163
|
|
|
158
164
|
# Convert with explicit type assertion
|
|
159
165
|
result = []
|
|
@@ -168,7 +174,9 @@ def get_all_database_connections_interface(db: Session, user_id: int) -> list[Fu
|
|
|
168
174
|
return result
|
|
169
175
|
|
|
170
176
|
|
|
171
|
-
def store_cloud_connection(
|
|
177
|
+
def store_cloud_connection(
|
|
178
|
+
db: Session, connection: FullCloudStorageConnection, user_id: int
|
|
179
|
+
) -> DBCloudStorageConnection:
|
|
172
180
|
"""
|
|
173
181
|
Placeholder function to store a cloud database connection.
|
|
174
182
|
This function should be implemented based on specific cloud provider requirements.
|
|
@@ -180,20 +188,29 @@ def store_cloud_connection(db: Session, connection: FullCloudStorageConnection,
|
|
|
180
188
|
f" Please use a unique connection name or delete the existing connection first."
|
|
181
189
|
)
|
|
182
190
|
if connection.aws_secret_access_key is not None:
|
|
183
|
-
aws_secret_access_key_ref_id = store_secret(
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
aws_secret_access_key_ref_id = store_secret(
|
|
192
|
+
db,
|
|
193
|
+
SecretInput(
|
|
194
|
+
name=connection.connection_name + "_aws_secret_access_key", value=connection.aws_secret_access_key
|
|
195
|
+
),
|
|
196
|
+
user_id,
|
|
197
|
+
).id
|
|
186
198
|
else:
|
|
187
199
|
aws_secret_access_key_ref_id = None
|
|
188
200
|
if connection.azure_client_secret is not None:
|
|
189
|
-
azure_client_secret_ref_id = store_secret(
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
azure_client_secret_ref_id = store_secret(
|
|
202
|
+
db,
|
|
203
|
+
SecretInput(name=connection.connection_name + "azure_client_secret", value=connection.azure_client_secret),
|
|
204
|
+
user_id,
|
|
205
|
+
).id
|
|
192
206
|
else:
|
|
193
207
|
azure_client_secret_ref_id = None
|
|
194
208
|
if connection.azure_account_key is not None:
|
|
195
|
-
azure_account_key_ref_id = store_secret(
|
|
196
|
-
|
|
209
|
+
azure_account_key_ref_id = store_secret(
|
|
210
|
+
db,
|
|
211
|
+
SecretInput(name=connection.connection_name + "azure_account_key", value=connection.azure_account_key),
|
|
212
|
+
user_id,
|
|
213
|
+
).id
|
|
197
214
|
else:
|
|
198
215
|
azure_account_key_ref_id = None
|
|
199
216
|
|
|
@@ -202,24 +219,21 @@ def store_cloud_connection(db: Session, connection: FullCloudStorageConnection,
|
|
|
202
219
|
storage_type=connection.storage_type,
|
|
203
220
|
auth_method=connection.auth_method,
|
|
204
221
|
user_id=user_id,
|
|
205
|
-
|
|
206
|
-
# AWS S3 fields
|
|
222
|
+
# AWS S3 fields
|
|
207
223
|
aws_region=connection.aws_region,
|
|
208
224
|
aws_access_key_id=connection.aws_access_key_id,
|
|
209
225
|
aws_role_arn=connection.aws_role_arn,
|
|
210
226
|
aws_secret_access_key_id=aws_secret_access_key_ref_id,
|
|
211
227
|
aws_allow_unsafe_html=connection.aws_allow_unsafe_html,
|
|
212
|
-
|
|
213
228
|
# Azure ADLS fields
|
|
214
229
|
azure_account_name=connection.azure_account_name,
|
|
215
230
|
azure_tenant_id=connection.azure_tenant_id,
|
|
216
231
|
azure_client_id=connection.azure_client_id,
|
|
217
232
|
azure_account_key_id=azure_account_key_ref_id,
|
|
218
233
|
azure_client_secret_id=azure_client_secret_ref_id,
|
|
219
|
-
|
|
220
234
|
# Common fields
|
|
221
235
|
endpoint_url=connection.endpoint_url,
|
|
222
|
-
verify_ssl=connection.verify_ssl
|
|
236
|
+
verify_ssl=connection.verify_ssl,
|
|
223
237
|
)
|
|
224
238
|
db.add(db_cloud_connection)
|
|
225
239
|
db.commit()
|
|
@@ -228,7 +242,8 @@ def store_cloud_connection(db: Session, connection: FullCloudStorageConnection,
|
|
|
228
242
|
|
|
229
243
|
|
|
230
244
|
def get_full_cloud_storage_interface_from_db(
|
|
231
|
-
|
|
245
|
+
db_cloud_connection: DBCloudStorageConnection,
|
|
246
|
+
) -> FullCloudStorageConnectionInterface:
|
|
232
247
|
"""
|
|
233
248
|
Convert a cloud storage connection from the database model to the interface model.
|
|
234
249
|
"""
|
|
@@ -244,7 +259,7 @@ def get_full_cloud_storage_interface_from_db(
|
|
|
244
259
|
azure_tenant_id=db_cloud_connection.azure_tenant_id,
|
|
245
260
|
azure_client_id=db_cloud_connection.azure_client_id,
|
|
246
261
|
endpoint_url=db_cloud_connection.endpoint_url,
|
|
247
|
-
verify_ssl=db_cloud_connection.verify_ssl
|
|
262
|
+
verify_ssl=db_cloud_connection.verify_ssl,
|
|
248
263
|
)
|
|
249
264
|
|
|
250
265
|
|
|
@@ -291,12 +306,13 @@ def get_cloud_connection_schema(db: Session, connection_name: str, user_id: int)
|
|
|
291
306
|
azure_client_id=db_connection.azure_client_id,
|
|
292
307
|
azure_client_secret=azure_client_secret,
|
|
293
308
|
endpoint_url=db_connection.endpoint_url,
|
|
294
|
-
verify_ssl=db_connection.verify_ssl
|
|
309
|
+
verify_ssl=db_connection.verify_ssl,
|
|
295
310
|
)
|
|
296
311
|
|
|
297
312
|
|
|
298
313
|
def cloud_connection_interface_from_db_connection(
|
|
299
|
-
|
|
314
|
+
db_connection: DBCloudStorageConnection,
|
|
315
|
+
) -> FullCloudStorageConnectionInterface:
|
|
300
316
|
"""
|
|
301
317
|
Converts a DBCloudStorageConnection model to a FullCloudStorageConnectionInterface model,
|
|
302
318
|
which safely exposes non-sensitive data.
|
|
@@ -313,7 +329,7 @@ def cloud_connection_interface_from_db_connection(
|
|
|
313
329
|
azure_tenant_id=db_connection.azure_tenant_id,
|
|
314
330
|
azure_client_id=db_connection.azure_client_id,
|
|
315
331
|
endpoint_url=db_connection.endpoint_url,
|
|
316
|
-
verify_ssl=db_connection.verify_ssl
|
|
332
|
+
verify_ssl=db_connection.verify_ssl,
|
|
317
333
|
)
|
|
318
334
|
|
|
319
335
|
|
|
@@ -339,7 +355,7 @@ def delete_cloud_connection(db: Session, connection_name: str, user_id: int) ->
|
|
|
339
355
|
db_connection.aws_session_token_id,
|
|
340
356
|
db_connection.azure_account_key_id,
|
|
341
357
|
db_connection.azure_client_secret_id,
|
|
342
|
-
db_connection.azure_sas_token_id
|
|
358
|
+
db_connection.azure_sas_token_id,
|
|
343
359
|
]
|
|
344
360
|
# Filter out None values
|
|
345
361
|
secret_ids_to_delete = [id for id in secret_ids_to_delete if id is not None]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
1
5
|
from flowfile_core.flowfile._extensions.real_time_interface import get_realtime_func_results
|
|
2
6
|
from flowfile_core.flowfile.flow_node.flow_node import FlowNode
|
|
3
7
|
from flowfile_core.schemas.output_model import InstantFuncResult
|
|
4
8
|
from flowfile_core.utils.arrow_reader import read_top_n
|
|
5
|
-
import pyarrow as pa
|
|
6
|
-
import polars as pl
|
|
7
|
-
from functools import lru_cache
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@lru_cache(maxsize=16)
|
|
@@ -14,23 +15,27 @@ def get_first_row(arrow_path: str) -> pl.DataFrame:
|
|
|
14
15
|
|
|
15
16
|
def get_instant_func_results(node_step: FlowNode, func_string: str) -> InstantFuncResult:
|
|
16
17
|
if len(node_step.main_input) == 0:
|
|
17
|
-
return InstantFuncResult(result=
|
|
18
|
+
return InstantFuncResult(result="No input data connected, so cannot evaluate the result", success=None)
|
|
18
19
|
node_input = node_step.main_input[0]
|
|
19
20
|
try:
|
|
20
|
-
if
|
|
21
|
+
if (
|
|
22
|
+
node_input.node_stats.has_run_with_current_setup
|
|
23
|
+
and node_input.is_setup
|
|
24
|
+
and node_input.results.example_data_path
|
|
25
|
+
):
|
|
21
26
|
df = get_first_row(node_input.results.example_data_path)
|
|
22
27
|
else:
|
|
23
28
|
df = node_input.get_predicted_resulting_data().data_frame.collect()
|
|
24
29
|
except:
|
|
25
|
-
return InstantFuncResult(result=
|
|
30
|
+
return InstantFuncResult(result="Could not get data from previous step", success=None)
|
|
26
31
|
try:
|
|
27
|
-
real_time_result = get_realtime_func_results(df=df,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
real_time_result = get_realtime_func_results(df=df, func_string=func_string)
|
|
33
|
+
if node_step.name == "filter" and not real_time_result.is_filterable_result():
|
|
34
|
+
return InstantFuncResult(
|
|
35
|
+
result="Result is not filterable," " make sure the function results in a true or false output",
|
|
36
|
+
success=False,
|
|
37
|
+
)
|
|
32
38
|
r = InstantFuncResult(result=real_time_result.readable_result, success=real_time_result.success)
|
|
33
39
|
except Exception as e:
|
|
34
40
|
r = InstantFuncResult(result=str(e), success=False)
|
|
35
41
|
return r
|
|
36
|
-
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
1
4
|
import boto3
|
|
2
5
|
from botocore.exceptions import ClientError
|
|
3
|
-
from typing import Optional, Dict, Any, Callable, Literal
|
|
4
6
|
|
|
5
7
|
from flowfile_core.schemas.cloud_storage_schemas import FullCloudStorageConnection
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
def create_storage_options_from_boto_credentials(
|
|
9
|
-
|
|
10
|
+
def create_storage_options_from_boto_credentials(
|
|
11
|
+
profile_name: str | None, region_name: str | None = None
|
|
12
|
+
) -> dict[str, Any]:
|
|
10
13
|
"""
|
|
11
14
|
Create a storage options dictionary from AWS credentials using a boto3 profile.
|
|
12
15
|
This is the most robust way to handle profile-based authentication as it
|
|
@@ -45,7 +48,7 @@ class CloudStorageReader:
|
|
|
45
48
|
"""Helper class to handle different cloud storage authentication methods and read operations."""
|
|
46
49
|
|
|
47
50
|
@staticmethod
|
|
48
|
-
def get_storage_options(connection: FullCloudStorageConnection) ->
|
|
51
|
+
def get_storage_options(connection: FullCloudStorageConnection) -> dict[str, Any]:
|
|
49
52
|
"""
|
|
50
53
|
Build storage options dict based on the connection type and auth method.
|
|
51
54
|
|
|
@@ -65,13 +68,12 @@ class CloudStorageReader:
|
|
|
65
68
|
raise ValueError(f"Unsupported storage type: {connection.storage_type}")
|
|
66
69
|
|
|
67
70
|
@staticmethod
|
|
68
|
-
def _get_s3_storage_options(connection:
|
|
71
|
+
def _get_s3_storage_options(connection: "FullCloudStorageConnection") -> dict[str, Any]:
|
|
69
72
|
"""Build S3-specific storage options."""
|
|
70
73
|
auth_method = connection.auth_method
|
|
71
74
|
if auth_method == "aws-cli":
|
|
72
75
|
return create_storage_options_from_boto_credentials(
|
|
73
|
-
profile_name=connection.connection_name,
|
|
74
|
-
region_name=connection.aws_region
|
|
76
|
+
profile_name=connection.connection_name, region_name=connection.aws_region
|
|
75
77
|
)
|
|
76
78
|
|
|
77
79
|
storage_options = {}
|
|
@@ -81,7 +83,7 @@ class CloudStorageReader:
|
|
|
81
83
|
storage_options["endpoint_url"] = connection.endpoint_url
|
|
82
84
|
if not connection.verify_ssl:
|
|
83
85
|
storage_options["verify"] = "False"
|
|
84
|
-
if connection.aws_allow_unsafe_html:
|
|
86
|
+
if connection.aws_allow_unsafe_html: # Note: Polars uses aws_allow_http
|
|
85
87
|
storage_options["aws_allow_http"] = "true"
|
|
86
88
|
|
|
87
89
|
if auth_method == "access_key":
|
|
@@ -92,20 +94,20 @@ class CloudStorageReader:
|
|
|
92
94
|
|
|
93
95
|
elif auth_method == "iam_role":
|
|
94
96
|
# Correctly implement IAM role assumption using boto3 STS client.
|
|
95
|
-
sts_client = boto3.client(
|
|
97
|
+
sts_client = boto3.client("sts", region_name=connection.aws_region)
|
|
96
98
|
assumed_role_object = sts_client.assume_role(
|
|
97
99
|
RoleArn=connection.aws_role_arn,
|
|
98
|
-
RoleSessionName="PolarsCloudStorageReaderSession"
|
|
100
|
+
RoleSessionName="PolarsCloudStorageReaderSession", # A descriptive session name
|
|
99
101
|
)
|
|
100
|
-
credentials = assumed_role_object[
|
|
101
|
-
storage_options["aws_access_key_id"] = credentials[
|
|
102
|
-
storage_options["aws_secret_access_key"] = credentials[
|
|
103
|
-
storage_options["aws_session_token"] = credentials[
|
|
102
|
+
credentials = assumed_role_object["Credentials"]
|
|
103
|
+
storage_options["aws_access_key_id"] = credentials["AccessKeyId"]
|
|
104
|
+
storage_options["aws_secret_access_key"] = credentials["SecretAccessKey"]
|
|
105
|
+
storage_options["aws_session_token"] = credentials["SessionToken"]
|
|
104
106
|
|
|
105
107
|
return storage_options
|
|
106
108
|
|
|
107
109
|
@staticmethod
|
|
108
|
-
def _get_adls_storage_options(connection:
|
|
110
|
+
def _get_adls_storage_options(connection: "FullCloudStorageConnection") -> dict[str, Any]:
|
|
109
111
|
"""Build Azure ADLS-specific storage options."""
|
|
110
112
|
storage_options = {}
|
|
111
113
|
|
|
@@ -133,14 +135,14 @@ class CloudStorageReader:
|
|
|
133
135
|
return storage_options
|
|
134
136
|
|
|
135
137
|
@staticmethod
|
|
136
|
-
def _get_gcs_storage_options(connection:
|
|
138
|
+
def _get_gcs_storage_options(connection: "FullCloudStorageConnection") -> dict[str, Any]:
|
|
137
139
|
"""Build GCS-specific storage options."""
|
|
138
140
|
# GCS typically uses service account authentication
|
|
139
141
|
# Implementation would depend on how credentials are stored
|
|
140
142
|
return {}
|
|
141
143
|
|
|
142
144
|
@staticmethod
|
|
143
|
-
def get_credential_provider(connection:
|
|
145
|
+
def get_credential_provider(connection: "FullCloudStorageConnection") -> Callable | None:
|
|
144
146
|
"""
|
|
145
147
|
Get a credential provider function if needed for the authentication method.
|
|
146
148
|
|
|
@@ -165,7 +167,7 @@ class CloudStorageReader:
|
|
|
165
167
|
return None
|
|
166
168
|
|
|
167
169
|
|
|
168
|
-
def get_first_file_from_s3_dir(source: str, storage_options:
|
|
170
|
+
def get_first_file_from_s3_dir(source: str, storage_options: dict[str, Any] = None) -> str:
|
|
169
171
|
"""
|
|
170
172
|
Get the first parquet file from an S3 directory path.
|
|
171
173
|
|
|
@@ -188,7 +190,7 @@ def get_first_file_from_s3_dir(source: str, storage_options: Dict[str, Any] = No
|
|
|
188
190
|
ClientError
|
|
189
191
|
If S3 access fails
|
|
190
192
|
"""
|
|
191
|
-
if not source.startswith(
|
|
193
|
+
if not source.startswith("s3://"):
|
|
192
194
|
raise ValueError("Source must be a valid S3 URI starting with 's3://'")
|
|
193
195
|
bucket_name, prefix = _parse_s3_path(source)
|
|
194
196
|
file_extension = _get_file_extension(source)
|
|
@@ -211,39 +213,39 @@ def _get_file_extension(source: str) -> str:
|
|
|
211
213
|
|
|
212
214
|
def _parse_s3_path(source: str) -> tuple[str, str]:
|
|
213
215
|
"""Parse S3 URI into bucket name and prefix."""
|
|
214
|
-
path_parts = source[5:].split(
|
|
216
|
+
path_parts = source[5:].split("/", 1) # Remove 's3://'
|
|
215
217
|
bucket_name = path_parts[0]
|
|
216
|
-
prefix = path_parts[1] if len(path_parts) > 1 else
|
|
218
|
+
prefix = path_parts[1] if len(path_parts) > 1 else ""
|
|
217
219
|
return bucket_name, prefix
|
|
218
220
|
|
|
219
221
|
|
|
220
222
|
def _remove_wildcards_from_prefix(prefix: str) -> str:
|
|
221
223
|
"""Remove wildcard patterns from S3 prefix."""
|
|
222
|
-
return prefix.split(
|
|
224
|
+
return prefix.split("*")[0]
|
|
223
225
|
|
|
224
226
|
|
|
225
|
-
def _create_s3_client(storage_options:
|
|
227
|
+
def _create_s3_client(storage_options: dict[str, Any] | None):
|
|
226
228
|
"""Create boto3 S3 client with optional credentials."""
|
|
227
229
|
if storage_options is None:
|
|
228
|
-
return boto3.client(
|
|
230
|
+
return boto3.client("s3")
|
|
229
231
|
|
|
230
232
|
# Handle both 'aws_region' and 'region_name' keys
|
|
231
233
|
client_options = storage_options.copy()
|
|
232
|
-
if
|
|
233
|
-
client_options[
|
|
234
|
+
if "aws_region" in client_options:
|
|
235
|
+
client_options["region_name"] = client_options.pop("aws_region")
|
|
234
236
|
|
|
235
|
-
return boto3.client(
|
|
237
|
+
return boto3.client("s3", **{k: v for k, v in client_options.items() if k != "aws_allow_http"})
|
|
236
238
|
|
|
237
239
|
|
|
238
|
-
def _get_first_file(s3_client, bucket_name: str, base_prefix: str, file_extension: str) ->
|
|
240
|
+
def _get_first_file(s3_client, bucket_name: str, base_prefix: str, file_extension: str) -> dict[Any, Any]:
|
|
239
241
|
"""List all parquet files in S3 bucket with given prefix."""
|
|
240
242
|
try:
|
|
241
|
-
paginator = s3_client.get_paginator(
|
|
243
|
+
paginator = s3_client.get_paginator("list_objects_v2")
|
|
242
244
|
pages = paginator.paginate(Bucket=bucket_name, Prefix=base_prefix)
|
|
243
245
|
for page in pages:
|
|
244
|
-
if
|
|
245
|
-
for obj in page[
|
|
246
|
-
if obj[
|
|
246
|
+
if "Contents" in page:
|
|
247
|
+
for obj in page["Contents"]:
|
|
248
|
+
if obj["Key"].endswith(f".{file_extension}"):
|
|
247
249
|
return obj
|
|
248
250
|
else:
|
|
249
251
|
raise ValueError(f"No objects found in s3://{bucket_name}/{base_prefix}")
|