Flowfile 0.5.1__py3-none-any.whl → 0.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- build_backends/main.py +25 -22
- build_backends/main_prd.py +10 -19
- flowfile/__init__.py +178 -74
- flowfile/__main__.py +10 -7
- flowfile/api.py +51 -57
- flowfile/web/__init__.py +14 -9
- flowfile/web/static/assets/AdminView-49392a9a.js +713 -0
- flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
- flowfile/web/static/assets/CloudConnectionView-36bcd6df.css +72 -0
- flowfile/web/static/assets/{CloudConnectionManager-0dfba9f2.js → CloudConnectionView-f13f202b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-d5b1b6c9.js → CloudStorageReader-0023d4a5.js} +10 -8
- flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
- flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
- flowfile/web/static/assets/{CloudStorageWriter-00d87aad.js → CloudStorageWriter-8e781e11.js} +10 -8
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-4685e75d.js → ColumnSelector-8ad68ea9.js} +3 -5
- flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-23e909da.js → ContextMenu-31ee57f0.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-70ae0c79.js → ContextMenu-69a74055.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-f149cf7c.js → ContextMenu-8e2051c6.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-4c74eef1.css → ContextMenu-8ec1729e.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-63cfa99b.css → ContextMenu-9b310c60.css} +6 -6
- flowfile/web/static/assets/{CrossJoin-702a3edd.js → CrossJoin-03df6938.js} +12 -10
- flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
- flowfile/web/static/assets/CustomNode-59e99a86.css +32 -0
- flowfile/web/static/assets/{CustomNode-b1519993.js → CustomNode-8479239b.js} +36 -24
- flowfile/web/static/assets/{DatabaseConnectionSettings-6f3e4ea5.js → DatabaseConnectionSettings-869e3efd.js} +5 -4
- flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-e91df89a.css} +13 -13
- flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-36898a00.css} +24 -24
- flowfile/web/static/assets/{DatabaseReader-d38c7295.js → DatabaseReader-c58b9552.js} +25 -15
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseManager-cf5ef661.js → DatabaseView-d26a9140.js} +11 -11
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
- flowfile/web/static/assets/{DatabaseWriter-b04ef46a.js → DatabaseWriter-4d05ddc7.js} +17 -10
- flowfile/web/static/assets/{designer-8da3ba3a.css → DesignerView-a6d0ee84.css} +614 -546
- flowfile/web/static/assets/{designer-9633482a.js → DesignerView-e6f5c0e8.js} +1107 -3170
- flowfile/web/static/assets/{documentation-ca400224.js → DocumentationView-2e78ef1b.js} +5 -5
- flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-fd46c656.css} +7 -7
- flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
- flowfile/web/static/assets/{ExploreData-5fa10ed8.js → ExploreData-7b54caca.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-d39af878.js → ExternalSource-3fa399b2.js} +9 -7
- flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-47ab05a3.css} +17 -17
- flowfile/web/static/assets/Filter-7494ea97.css +48 -0
- flowfile/web/static/assets/Filter-8cbbdbf3.js +287 -0
- flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
- flowfile/web/static/assets/{Formula-6b04fb1d.js → Formula-aac42b1e.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-999521f4.js → FuzzyMatch-cd9bbfca.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-17dd2198.js → GraphSolver-c7e6780e.js} +13 -11
- flowfile/web/static/assets/{GroupBy-6b039e18.js → GroupBy-93c5d22b.js} +9 -7
- flowfile/web/static/assets/{GroupBy-b9505323.css → GroupBy-be7ac0bf.css} +10 -10
- flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
- flowfile/web/static/assets/{Join-24d0f113.js → Join-a19b2de2.js} +13 -11
- flowfile/web/static/assets/LoginView-0df4ed0a.js +134 -0
- flowfile/web/static/assets/LoginView-d325d632.css +172 -0
- flowfile/web/static/assets/ManualInput-3702e677.css +293 -0
- flowfile/web/static/assets/{ManualInput-34639209.js → ManualInput-8d3374b2.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-0e8724a3.js → MultiSelect-ad1b6243.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-b0e538c2.js → MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js} +1 -1
- flowfile/web/static/assets/NodeDesigner-40b647c9.js +2610 -0
- flowfile/web/static/assets/NodeDesigner-5f53be3f.css +1429 -0
- flowfile/web/static/assets/{NumericInput-3d63a470.js → NumericInput-7100234c.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-e0edeccc.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
- flowfile/web/static/assets/{Output-283fe388.css → Output-35e97000.css} +6 -6
- flowfile/web/static/assets/{Output-edea9802.js → Output-f5efd2aa.js} +12 -9
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-61d19301.js → Pivot-d981d23c.js} +11 -9
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f97fec5b.js → PivotValidation-39386e95.js} +3 -3
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-de9f43fe.js → PivotValidation-63de1f73.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-bc3c9984.js → PolarsCode-f9d69217.js} +18 -9
- flowfile/web/static/assets/PopOver-b22f049e.js +939 -0
- flowfile/web/static/assets/PopOver-d96599db.css +33 -0
- flowfile/web/static/assets/{Read-e808b239.css → Read-36e7bd51.css} +12 -12
- flowfile/web/static/assets/{Read-64a3f259.js → Read-aec2e377.js} +14 -11
- flowfile/web/static/assets/{RecordCount-3d5039be.js → RecordCount-78ed6845.js} +6 -4
- flowfile/web/static/assets/{RecordId-597510e0.js → RecordId-2156e890.js} +8 -6
- flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
- flowfile/web/static/assets/{SQLQueryComponent-df51adbe.js → SQLQueryComponent-48c72f5b.js} +3 -3
- flowfile/web/static/assets/{Sample-4be0a507.js → Sample-1352ca74.js} +6 -4
- flowfile/web/static/assets/SecretSelector-22b5ff89.js +113 -0
- flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
- flowfile/web/static/assets/{SecretManager-4839be57.js → SecretsView-17df66ee.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-9b72f201.js → Select-0aee4c54.js} +9 -7
- flowfile/web/static/assets/{SettingsSection-f0f75a42.js → SettingsSection-0784e157.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-71e6b7e3.css → SettingsSection-07fbbc39.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-5c696bee.css → SettingsSection-26fe48d4.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-e1e9c953.js → SettingsSection-cd341bb6.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-7ded385d.js → SettingsSection-f2002a6d.js} +3 -3
- flowfile/web/static/assets/{SingleSelect-6c777aac.js → SingleSelect-460cc0ea.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-33e3ff9b.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
- flowfile/web/static/assets/{SliderInput-7cb93e62.js → SliderInput-5d926864.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-6cbde21a.js → Sort-3cdc971b.js} +9 -7
- flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
- flowfile/web/static/assets/{TextInput-d9a40c11.js → TextInput-a2d0bfbd.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-5896c375.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-c4fcbf4d.js → TextToRows-918945f7.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-4ef91d19.js → ToggleSwitch-f0ef5196.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-38478c20.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
- flowfile/web/static/assets/{UnavailableFields-a03f512c.js → UnavailableFields-bdad6144.js} +4 -4
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/{Union-bfe9b996.js → Union-e8ab8c86.js} +8 -6
- flowfile/web/static/assets/{Unique-5d023a27.js → Unique-8cd4f976.js} +13 -10
- flowfile/web/static/assets/{Sort-3643d625.css → Unique-9fb2f567.css} +10 -10
- flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-710a2948.css} +7 -7
- flowfile/web/static/assets/{Unpivot-91cc5354.js → Unpivot-8da14095.js} +10 -8
- flowfile/web/static/assets/{UnpivotValidation-7ee2de44.js → UnpivotValidation-6f7d89ff.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-e51b9924.js → VueGraphicWalker-3fb312e1.js} +4 -4
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{api-cf1221f0.js → api-24483f0d.js} +1 -1
- flowfile/web/static/assets/{api-c1bad5ca.js → api-8b81fa73.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
- flowfile/web/static/assets/{dropDown-614b998d.js → dropDown-ac0fda9d.js} +3 -3
- flowfile/web/static/assets/{fullEditor-f7971590.js → fullEditor-5497a84a.js} +11 -10
- flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-a0be62b3.css} +74 -62
- flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
- flowfile/web/static/assets/{genericNodeSettings-4fe5f36b.js → genericNodeSettings-99014e1d.js} +5 -5
- flowfile/web/static/assets/index-07dda503.js +38 -0
- flowfile/web/static/assets/index-3ba44389.js +2696 -0
- flowfile/web/static/assets/{index-50508d4d.css → index-e6289dd0.css} +1945 -569
- flowfile/web/static/assets/{index-5429bbf8.js → index-fb6493ae.js} +41626 -40867
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
- flowfile/web/static/assets/{outputCsv-076b85ab.js → outputCsv-8f8ba42d.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-0fd17dbe.js → outputExcel-393f4fef.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/{outputParquet-b61e0847.js → outputParquet-07c81f65.js} +4 -4
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{readCsv-a8bb8b61.js → readCsv-07f6d9ad.js} +3 -3
- flowfile/web/static/assets/{readCsv-c767cb37.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readExcel-806d2826.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-67b4aee0.js → readExcel-ed69bc8f.js} +5 -5
- flowfile/web/static/assets/{readParquet-48c81530.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/{readParquet-92ce1dbc.js → readParquet-e3ed4528.js} +3 -3
- flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
- flowfile/web/static/assets/{selectDynamic-92e25ee3.js → selectDynamic-80b92899.js} +5 -5
- flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
- flowfile/web/static/assets/{vue-codemirror.esm-41b0e0d7.js → vue-codemirror.esm-0965f39f.js} +31 -640
- flowfile/web/static/assets/{vue-content-loader.es-2c8e608f.js → vue-content-loader.es-c506ad97.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +2 -3
- flowfile-0.5.3.dist-info/RECORD +402 -0
- flowfile_core/__init__.py +13 -6
- flowfile_core/auth/jwt.py +51 -16
- flowfile_core/auth/models.py +32 -7
- flowfile_core/auth/password.py +89 -0
- flowfile_core/auth/secrets.py +8 -6
- flowfile_core/configs/__init__.py +9 -7
- flowfile_core/configs/flow_logger.py +15 -14
- flowfile_core/configs/node_store/__init__.py +72 -4
- flowfile_core/configs/node_store/nodes.py +155 -172
- flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
- flowfile_core/configs/settings.py +28 -15
- flowfile_core/database/connection.py +7 -6
- flowfile_core/database/init_db.py +96 -2
- flowfile_core/database/models.py +3 -1
- flowfile_core/fileExplorer/__init__.py +17 -0
- flowfile_core/fileExplorer/funcs.py +123 -57
- flowfile_core/fileExplorer/utils.py +10 -11
- flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
- flowfile_core/flowfile/analytics/analytics_processor.py +26 -24
- flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
- flowfile_core/flowfile/analytics/utils.py +1 -1
- flowfile_core/flowfile/code_generator/code_generator.py +358 -244
- flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
- flowfile_core/flowfile/connection_manager/models.py +1 -1
- flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
- flowfile_core/flowfile/database_connection_manager/models.py +1 -1
- flowfile_core/flowfile/extensions.py +17 -12
- flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
- flowfile_core/flowfile/flow_data_engine/create/funcs.py +115 -83
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +481 -423
- flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
- flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +31 -20
- flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
- flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +14 -15
- flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
- flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
- flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
- flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +190 -127
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
- flowfile_core/flowfile/flow_data_engine/utils.py +99 -67
- flowfile_core/flowfile/flow_graph.py +918 -571
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +330 -233
- flowfile_core/flowfile/flow_node/models.py +53 -41
- flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
- flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
- flowfile_core/flowfile/handler.py +80 -30
- flowfile_core/flowfile/manage/compatibility_enhancements.py +209 -126
- flowfile_core/flowfile/manage/io_flowfile.py +54 -57
- flowfile_core/flowfile/node_designer/__init__.py +15 -13
- flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
- flowfile_core/flowfile/node_designer/custom_node.py +162 -36
- flowfile_core/flowfile/node_designer/ui_components.py +135 -34
- flowfile_core/flowfile/schema_callbacks.py +71 -51
- flowfile_core/flowfile/setting_generator/__init__.py +0 -1
- flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
- flowfile_core/flowfile/setting_generator/settings.py +64 -53
- flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
- flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
- flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
- flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
- flowfile_core/flowfile/util/calculate_layout.py +9 -13
- flowfile_core/flowfile/util/execution_orderer.py +25 -17
- flowfile_core/flowfile/util/node_skipper.py +4 -4
- flowfile_core/flowfile/utils.py +19 -21
- flowfile_core/main.py +26 -19
- flowfile_core/routes/auth.py +284 -11
- flowfile_core/routes/cloud_connections.py +25 -25
- flowfile_core/routes/logs.py +21 -29
- flowfile_core/routes/public.py +3 -3
- flowfile_core/routes/routes.py +70 -34
- flowfile_core/routes/secrets.py +25 -27
- flowfile_core/routes/user_defined_components.py +483 -4
- flowfile_core/run_lock.py +0 -1
- flowfile_core/schemas/__init__.py +4 -6
- flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
- flowfile_core/schemas/cloud_storage_schemas.py +59 -53
- flowfile_core/schemas/input_schema.py +231 -144
- flowfile_core/schemas/output_model.py +49 -34
- flowfile_core/schemas/schemas.py +116 -89
- flowfile_core/schemas/transform_schema.py +518 -263
- flowfile_core/schemas/yaml_types.py +21 -7
- flowfile_core/secret_manager/secret_manager.py +17 -13
- flowfile_core/types.py +29 -9
- flowfile_core/utils/arrow_reader.py +7 -6
- flowfile_core/utils/excel_file_manager.py +3 -3
- flowfile_core/utils/fileManager.py +7 -7
- flowfile_core/utils/fl_executor.py +8 -10
- flowfile_core/utils/utils.py +4 -4
- flowfile_core/utils/validate_setup.py +5 -4
- flowfile_frame/__init__.py +106 -51
- flowfile_frame/adapters.py +2 -9
- flowfile_frame/adding_expr.py +73 -32
- flowfile_frame/cloud_storage/frame_helpers.py +27 -23
- flowfile_frame/cloud_storage/secret_manager.py +12 -26
- flowfile_frame/config.py +2 -5
- flowfile_frame/expr.py +311 -218
- flowfile_frame/expr.pyi +160 -159
- flowfile_frame/expr_name.py +23 -23
- flowfile_frame/flow_frame.py +571 -476
- flowfile_frame/flow_frame.pyi +123 -104
- flowfile_frame/flow_frame_methods.py +227 -246
- flowfile_frame/group_frame.py +50 -20
- flowfile_frame/join.py +2 -2
- flowfile_frame/lazy.py +129 -87
- flowfile_frame/lazy_methods.py +83 -30
- flowfile_frame/list_name_space.py +55 -50
- flowfile_frame/selectors.py +148 -68
- flowfile_frame/series.py +9 -7
- flowfile_frame/utils.py +19 -21
- flowfile_worker/__init__.py +12 -7
- flowfile_worker/configs.py +11 -19
- flowfile_worker/create/__init__.py +14 -9
- flowfile_worker/create/funcs.py +114 -77
- flowfile_worker/create/models.py +46 -43
- flowfile_worker/create/pl_types.py +14 -15
- flowfile_worker/create/read_excel_tables.py +34 -41
- flowfile_worker/create/utils.py +22 -19
- flowfile_worker/external_sources/s3_source/main.py +18 -51
- flowfile_worker/external_sources/s3_source/models.py +34 -27
- flowfile_worker/external_sources/sql_source/main.py +8 -5
- flowfile_worker/external_sources/sql_source/models.py +13 -9
- flowfile_worker/flow_logger.py +10 -8
- flowfile_worker/funcs.py +214 -155
- flowfile_worker/main.py +11 -17
- flowfile_worker/models.py +35 -28
- flowfile_worker/process_manager.py +2 -3
- flowfile_worker/routes.py +121 -90
- flowfile_worker/secrets.py +9 -6
- flowfile_worker/spawner.py +80 -49
- flowfile_worker/utils.py +3 -2
- shared/__init__.py +2 -7
- shared/storage_config.py +25 -13
- test_utils/postgres/commands.py +3 -2
- test_utils/postgres/fixtures.py +9 -9
- test_utils/s3/commands.py +1 -1
- test_utils/s3/data_generator.py +3 -4
- test_utils/s3/demo_data_generator.py +4 -7
- test_utils/s3/fixtures.py +7 -5
- tools/migrate/__init__.py +1 -1
- tools/migrate/__main__.py +16 -29
- tools/migrate/legacy_schemas.py +251 -190
- tools/migrate/migrate.py +193 -181
- tools/migrate/tests/conftest.py +1 -3
- tools/migrate/tests/test_migrate.py +36 -41
- tools/migrate/tests/test_migration_e2e.py +28 -29
- tools/migrate/tests/test_node_migrations.py +50 -20
- flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
- flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
- flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
- flowfile/web/static/assets/Filter-9b6d08db.js +0 -164
- flowfile/web/static/assets/Filter-f62091b3.css +0 -20
- flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
- flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
- flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
- flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
- flowfile/web/static/assets/nodeInput-5d0d6b79.js +0 -41
- flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
- flowfile/web/static/assets/secretApi-68435402.js +0 -46
- flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
- flowfile-0.5.1.dist-info/RECORD +0 -388
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import List, Optional, Set, Union
|
|
1
|
+
import os
|
|
3
2
|
from datetime import datetime
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal, Optional
|
|
5
|
+
|
|
6
|
+
from fastapi import HTTPException
|
|
4
7
|
from pydantic import BaseModel
|
|
5
|
-
|
|
6
|
-
import
|
|
8
|
+
|
|
9
|
+
from shared.storage_config import storage
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class FileInfo(BaseModel):
|
|
10
13
|
"""Comprehensive information about a file or directory."""
|
|
14
|
+
|
|
11
15
|
name: str
|
|
12
16
|
path: str
|
|
13
17
|
is_directory: bool
|
|
@@ -19,8 +23,7 @@ class FileInfo(BaseModel):
|
|
|
19
23
|
exists: bool = True
|
|
20
24
|
|
|
21
25
|
@classmethod
|
|
22
|
-
def from_path(cls, path: Path, sandbox_root:
|
|
23
|
-
use_relative_paths: bool = False) -> 'FileInfo':
|
|
26
|
+
def from_path(cls, path: Path, sandbox_root: Path | None = None, use_relative_paths: bool = False) -> "FileInfo":
|
|
24
27
|
"""Create FileInfo instance from a path.
|
|
25
28
|
|
|
26
29
|
Args:
|
|
@@ -48,10 +51,8 @@ class FileInfo(BaseModel):
|
|
|
48
51
|
file_type=path.suffix[1:] if path.suffix else "",
|
|
49
52
|
last_modified=datetime.fromtimestamp(stats.st_mtime),
|
|
50
53
|
created_date=datetime.fromtimestamp(stats.st_ctime),
|
|
51
|
-
is_hidden=path.name.startswith(
|
|
52
|
-
|
|
53
|
-
),
|
|
54
|
-
exists=True
|
|
54
|
+
is_hidden=path.name.startswith(".") or (os.name == "nt" and bool(stats.st_file_attributes & 0x2)),
|
|
55
|
+
exists=True,
|
|
55
56
|
)
|
|
56
57
|
except (PermissionError, OSError):
|
|
57
58
|
# Handle error case
|
|
@@ -72,16 +73,16 @@ class FileInfo(BaseModel):
|
|
|
72
73
|
last_modified=datetime.fromtimestamp(0),
|
|
73
74
|
created_date=datetime.fromtimestamp(0),
|
|
74
75
|
is_hidden=False,
|
|
75
|
-
exists=False
|
|
76
|
+
exists=False,
|
|
76
77
|
)
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
class SecureFileExplorer:
|
|
80
81
|
"""File explorer with sandbox enforcement to prevent directory traversal."""
|
|
81
82
|
|
|
82
|
-
def __init__(
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
def __init__(
|
|
84
|
+
self, start_path: str | Path, sandbox_root: str | Path | None = None, use_relative_paths: bool = False
|
|
85
|
+
):
|
|
85
86
|
"""Initialize SecureFileExplorer with sandboxing.
|
|
86
87
|
|
|
87
88
|
Args:
|
|
@@ -126,16 +127,17 @@ class SecureFileExplorer:
|
|
|
126
127
|
except (ValueError, RuntimeError):
|
|
127
128
|
return False
|
|
128
129
|
|
|
129
|
-
def _sanitize_path(self, path:
|
|
130
|
+
def _sanitize_path(self, path: str | Path) -> Path | None:
|
|
130
131
|
"""Sanitize and validate a path, ensuring it stays within sandbox.
|
|
131
132
|
|
|
132
133
|
Returns None if path would escape sandbox.
|
|
133
134
|
"""
|
|
134
135
|
try:
|
|
135
|
-
# Handle relative paths from current
|
|
136
|
+
# Handle relative paths from current directoryb
|
|
136
137
|
if isinstance(path, str):
|
|
138
|
+
|
|
137
139
|
# Remove any suspicious patterns
|
|
138
|
-
if
|
|
140
|
+
if path.startswith("/"):
|
|
139
141
|
# For absolute paths or parent references, resolve from sandbox root
|
|
140
142
|
test_path = Path(path).expanduser()
|
|
141
143
|
else:
|
|
@@ -168,7 +170,7 @@ class SecureFileExplorer:
|
|
|
168
170
|
return "/"
|
|
169
171
|
|
|
170
172
|
@property
|
|
171
|
-
def parent_directory(self) ->
|
|
173
|
+
def parent_directory(self) -> str | None:
|
|
172
174
|
"""Get the parent directory path if it exists and is within sandbox."""
|
|
173
175
|
parent = self.current_path.parent
|
|
174
176
|
if self._is_path_safe(parent) and parent != self.current_path:
|
|
@@ -180,26 +182,26 @@ class SecureFileExplorer:
|
|
|
180
182
|
return None
|
|
181
183
|
|
|
182
184
|
def list_contents(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
) ->
|
|
185
|
+
self,
|
|
186
|
+
*,
|
|
187
|
+
show_hidden: bool = False,
|
|
188
|
+
file_types: list[str] | None = None,
|
|
189
|
+
recursive: bool = False,
|
|
190
|
+
min_size: int | None = None,
|
|
191
|
+
max_size: int | None = None,
|
|
192
|
+
sort_by: Literal["name", "date", "size", "type"] = "name",
|
|
193
|
+
reverse: bool = False,
|
|
194
|
+
exclude_patterns: list[str] | None = None,
|
|
195
|
+
max_depth: int = 5, # Add depth limit for recursive operations
|
|
196
|
+
) -> list[FileInfo]:
|
|
195
197
|
"""List contents with security-conscious filtering."""
|
|
196
|
-
contents:
|
|
197
|
-
excluded_paths:
|
|
198
|
+
contents: list[FileInfo] = []
|
|
199
|
+
excluded_paths: set[str] = set()
|
|
198
200
|
|
|
199
201
|
if exclude_patterns:
|
|
200
202
|
for pattern in exclude_patterns:
|
|
201
203
|
# Ensure patterns don't escape sandbox
|
|
202
|
-
safe_pattern = pattern.replace(
|
|
204
|
+
safe_pattern = pattern.replace("../", "").replace("..\\", "")
|
|
203
205
|
excluded_paths.update(str(p) for p in self.current_path.glob(safe_pattern))
|
|
204
206
|
|
|
205
207
|
def should_include(info: FileInfo) -> bool:
|
|
@@ -239,8 +241,7 @@ class SecureFileExplorer:
|
|
|
239
241
|
continue
|
|
240
242
|
|
|
241
243
|
try:
|
|
242
|
-
file_info = FileInfo.from_path(item, self.sandbox_root,
|
|
243
|
-
self.use_relative_paths)
|
|
244
|
+
file_info = FileInfo.from_path(item, self.sandbox_root, self.use_relative_paths)
|
|
244
245
|
if should_include(file_info):
|
|
245
246
|
contents.append(file_info)
|
|
246
247
|
|
|
@@ -258,8 +259,7 @@ class SecureFileExplorer:
|
|
|
258
259
|
continue
|
|
259
260
|
|
|
260
261
|
try:
|
|
261
|
-
file_info = FileInfo.from_path(item, self.sandbox_root,
|
|
262
|
-
self.use_relative_paths)
|
|
262
|
+
file_info = FileInfo.from_path(item, self.sandbox_root, self.use_relative_paths)
|
|
263
263
|
if should_include(file_info):
|
|
264
264
|
contents.append(file_info)
|
|
265
265
|
except (PermissionError, OSError):
|
|
@@ -270,10 +270,10 @@ class SecureFileExplorer:
|
|
|
270
270
|
|
|
271
271
|
# Sort results
|
|
272
272
|
sort_key = {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
"name": lambda x: (not x.is_directory, x.name.lower()),
|
|
274
|
+
"date": lambda x: (not x.is_directory, x.last_modified),
|
|
275
|
+
"size": lambda x: (not x.is_directory, x.size),
|
|
276
|
+
"type": lambda x: (not x.is_directory, x.file_type.lower(), x.name.lower()),
|
|
277
277
|
}[sort_by]
|
|
278
278
|
|
|
279
279
|
return sorted(contents, key=sort_key, reverse=reverse)
|
|
@@ -316,13 +316,13 @@ class SecureFileExplorer:
|
|
|
316
316
|
def navigate_into(self, directory_name: str) -> bool:
|
|
317
317
|
"""Navigate into a subdirectory, with path sanitization."""
|
|
318
318
|
# Sanitize directory name
|
|
319
|
-
if
|
|
319
|
+
if "/" in directory_name or "\\" in directory_name or ".." in directory_name:
|
|
320
320
|
return False
|
|
321
321
|
|
|
322
322
|
new_path = self.current_path / directory_name
|
|
323
323
|
return self.navigate_to(str(new_path))
|
|
324
324
|
|
|
325
|
-
def get_absolute_path(self, relative_path: str) ->
|
|
325
|
+
def get_absolute_path(self, relative_path: str) -> Path | None:
|
|
326
326
|
"""Get absolute path for a file within sandbox.
|
|
327
327
|
|
|
328
328
|
Returns None if the path would escape sandbox.
|
|
@@ -332,13 +332,13 @@ class SecureFileExplorer:
|
|
|
332
332
|
|
|
333
333
|
|
|
334
334
|
def get_files_from_directory(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
) ->
|
|
335
|
+
dir_name: str | Path,
|
|
336
|
+
types: list[str] | None = None,
|
|
337
|
+
*,
|
|
338
|
+
include_hidden: bool = False,
|
|
339
|
+
recursive: bool = False,
|
|
340
|
+
sandbox_root: str | Path | None = None,
|
|
341
|
+
) -> list[FileInfo] | None:
|
|
342
342
|
"""
|
|
343
343
|
Get list of files from a directory with sandbox enforcement.
|
|
344
344
|
|
|
@@ -360,18 +360,84 @@ def get_files_from_directory(
|
|
|
360
360
|
explorer = SecureFileExplorer(start_path=dir_name)
|
|
361
361
|
|
|
362
362
|
# Use the explorer's list_contents method
|
|
363
|
-
return explorer.list_contents(
|
|
364
|
-
show_hidden=include_hidden,
|
|
365
|
-
file_types=types,
|
|
366
|
-
recursive=recursive
|
|
367
|
-
)
|
|
363
|
+
return explorer.list_contents(show_hidden=include_hidden, file_types=types, recursive=recursive)
|
|
368
364
|
|
|
369
|
-
except (ValueError, PermissionError)
|
|
365
|
+
except (ValueError, PermissionError):
|
|
370
366
|
# Return None for invalid/inaccessible directories
|
|
371
367
|
return None
|
|
372
368
|
except Exception as e:
|
|
373
369
|
raise type(e)(f"Error scanning directory {dir_name}: {str(e)}") from e
|
|
374
370
|
|
|
375
371
|
|
|
372
|
+
def validate_file_path(user_path: str, allowed_base: Path) -> Optional[Path]:
|
|
373
|
+
"""Validate a file path is safe and within allowed_base.
|
|
374
|
+
|
|
375
|
+
Uses os.path.realpath + startswith pattern recognized by CodeQL as safe.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
user_path: The user-provided path string
|
|
379
|
+
allowed_base: The base directory that the path must be within
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
The validated Path object, or None if validation fails
|
|
383
|
+
"""
|
|
384
|
+
try:
|
|
385
|
+
# Block obvious path traversal patterns early
|
|
386
|
+
if '..' in user_path:
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
# Get the base path as a normalized, real path string
|
|
390
|
+
base_path = os.path.realpath(str(allowed_base.resolve()))
|
|
391
|
+
|
|
392
|
+
# Always resolve the user path relative to the allowed base directory
|
|
393
|
+
candidate_path = os.path.join(base_path, user_path)
|
|
394
|
+
fullpath = os.path.realpath(candidate_path)
|
|
395
|
+
|
|
396
|
+
# Ensure the resolved path stays within the allowed base directory
|
|
397
|
+
if not fullpath.startswith(base_path + os.sep) and fullpath != base_path:
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
return Path(fullpath)
|
|
401
|
+
|
|
402
|
+
except (ValueError, RuntimeError, OSError):
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def validate_path_under_cwd(user_path: str) -> str:
|
|
407
|
+
"""Validate that a user-provided path resolves to within allowed directories.
|
|
408
|
+
|
|
409
|
+
Uses the exact pattern from CodeQL documentation for py/path-injection:
|
|
410
|
+
- os.path.normpath for path normalization
|
|
411
|
+
- os.path.join to combine base with user input
|
|
412
|
+
- startswith check to ensure path stays within base
|
|
413
|
+
|
|
414
|
+
Allowed directories:
|
|
415
|
+
- Current working directory (for development/testing)
|
|
416
|
+
- Flowfile storage directory (~/.flowfile)
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
user_path: The user-provided path string
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
The validated, normalized full path as a string
|
|
423
|
+
|
|
424
|
+
Raises:
|
|
425
|
+
HTTPException: 403 if path escapes the allowed directories
|
|
426
|
+
"""
|
|
427
|
+
# Try current working directory first
|
|
428
|
+
base_path = os.path.normpath(os.getcwd())
|
|
429
|
+
fullpath = os.path.normpath(os.path.join(base_path, user_path))
|
|
430
|
+
if fullpath.startswith(base_path):
|
|
431
|
+
return fullpath
|
|
432
|
+
|
|
433
|
+
# Try flowfile storage directory (~/.flowfile)
|
|
434
|
+
base_path = os.path.normpath(str(storage.base_directory))
|
|
435
|
+
fullpath = os.path.normpath(os.path.join(base_path, user_path))
|
|
436
|
+
if fullpath.startswith(base_path):
|
|
437
|
+
return fullpath
|
|
438
|
+
|
|
439
|
+
raise HTTPException(403, 'Access denied')
|
|
440
|
+
|
|
441
|
+
|
|
376
442
|
# Alias for backward compatibility
|
|
377
|
-
FileExplorer = SecureFileExplorer
|
|
443
|
+
FileExplorer = SecureFileExplorer
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
3
|
|
|
4
|
-
video_types = [
|
|
5
|
-
audio_types = [
|
|
4
|
+
video_types = ["mp4", "webm", "opgg"]
|
|
5
|
+
audio_types = ["mp3", "wav", "ogg", "mpeg", "aac", "3gpp", "3gpp2", "aiff", "x-aiff", "amr", "mpga"]
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def get_chunk(start_byte=None, end_byte=None, full_path=None):
|
|
@@ -11,7 +11,7 @@ def get_chunk(start_byte=None, end_byte=None, full_path=None):
|
|
|
11
11
|
length = end_byte + 1 - start_byte
|
|
12
12
|
else:
|
|
13
13
|
length = file_size - start_byte
|
|
14
|
-
with open(full_path,
|
|
14
|
+
with open(full_path, "rb") as f:
|
|
15
15
|
f.seek(start_byte)
|
|
16
16
|
chunk = f.read(length)
|
|
17
17
|
return chunk, start_byte, length, file_size
|
|
@@ -19,10 +19,10 @@ def get_chunk(start_byte=None, end_byte=None, full_path=None):
|
|
|
19
19
|
|
|
20
20
|
def get_file(file_path, mimetype):
|
|
21
21
|
f = ""
|
|
22
|
-
range_header = f.headers.get(
|
|
22
|
+
range_header = f.headers.get("Range", None)
|
|
23
23
|
start_byte, end_byte = 0, None
|
|
24
24
|
if range_header:
|
|
25
|
-
match = re.search(r
|
|
25
|
+
match = re.search(r"(\d+)-(\d*)", range_header)
|
|
26
26
|
groups = match.groups()
|
|
27
27
|
if groups[0]:
|
|
28
28
|
start_byte = int(groups[0])
|
|
@@ -30,15 +30,14 @@ def get_file(file_path, mimetype):
|
|
|
30
30
|
end_byte = int(groups[1])
|
|
31
31
|
|
|
32
32
|
chunk, start, length, file_size = get_chunk(start_byte, end_byte, file_path)
|
|
33
|
-
resp = Response(chunk, 206, mimetype=f
|
|
34
|
-
content_type=mimetype, direct_passthrough=True)
|
|
33
|
+
resp = Response(chunk, 206, mimetype=f"video/{mimetype}", content_type=mimetype, direct_passthrough=True)
|
|
35
34
|
print(length)
|
|
36
|
-
resp.headers.add(
|
|
35
|
+
resp.headers.add("Content-Range", f"bytes {start}-{start + length - 1}/{file_size}")
|
|
37
36
|
return resp
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
def is_media(filepath):
|
|
41
|
-
found_media = re.search("\.mp4$|\.mp3$", filepath, re.IGNORECASE)
|
|
40
|
+
found_media = re.search(r"\.mp4$|\.mp3$", filepath, re.IGNORECASE)
|
|
42
41
|
if found_media:
|
|
43
42
|
extension = found_media[0].lower()[1:]
|
|
44
43
|
if found_media in video_types:
|
|
@@ -48,6 +47,6 @@ def is_media(filepath):
|
|
|
48
47
|
|
|
49
48
|
|
|
50
49
|
def get_file_extension(fname):
|
|
51
|
-
found_extension = re.search("\.[A-Za-z0-9]*$", fname, re.IGNORECASE)
|
|
50
|
+
found_extension = re.search(r"\.[A-Za-z0-9]*$", fname, re.IGNORECASE)
|
|
52
51
|
if found_extension:
|
|
53
|
-
return found_extension[0][1:].lower()
|
|
52
|
+
return found_extension[0][1:].lower()
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
1
3
|
import polars as pl
|
|
2
4
|
from polars_expr_transformer import simple_function_to_expr
|
|
5
|
+
|
|
3
6
|
from flowfile_core.configs import logger
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from typing import Optional
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@dataclass
|
|
@@ -10,16 +11,16 @@ class RealTimeResult:
|
|
|
10
11
|
result_df: pl.DataFrame
|
|
11
12
|
data_type: pl.DataType
|
|
12
13
|
readable_result: str
|
|
13
|
-
success:
|
|
14
|
+
success: bool | None = None
|
|
14
15
|
|
|
15
16
|
def __init__(self, result_value: pl.DataFrame, data_type: pl.DataType):
|
|
16
17
|
self.result_df = result_value
|
|
17
18
|
self.data_type = data_type
|
|
18
|
-
if len(result_value)>0:
|
|
19
|
-
self.readable_result = str(result_value.item(0,0))
|
|
19
|
+
if len(result_value) > 0:
|
|
20
|
+
self.readable_result = str(result_value.item(0, 0))
|
|
20
21
|
self.success = True
|
|
21
22
|
else:
|
|
22
|
-
self.readable_result =
|
|
23
|
+
self.readable_result = ""
|
|
23
24
|
self.success = None
|
|
24
25
|
|
|
25
26
|
def is_filterable_result(self):
|
|
@@ -44,8 +45,9 @@ def get_realtime_func_results(df: pl.DataFrame | pl.LazyFrame, func_string: str,
|
|
|
44
45
|
print(get_first_result_of_function('year(today())', df))
|
|
45
46
|
"""
|
|
46
47
|
if isinstance(df, pl.LazyFrame):
|
|
47
|
-
logger.warning(
|
|
48
|
-
|
|
48
|
+
logger.warning(
|
|
49
|
+
"Performance in this case can be " "improved by using polars.DataFrame to ensure it returns instantly"
|
|
50
|
+
)
|
|
49
51
|
df = df.head(sample).collect()
|
|
50
52
|
result = df.head(1).select(simple_function_to_expr(func_string))
|
|
51
53
|
return RealTimeResult(result, result.dtypes[0])
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from flowfile_core.flowfile.analytics.graphic_walker import (get_initial_gf_data_from_ff,
|
|
4
|
-
convert_ff_columns_to_gw_fields)
|
|
1
|
+
from flowfile_core.configs import logger
|
|
2
|
+
from flowfile_core.flowfile.analytics.graphic_walker import convert_ff_columns_to_gw_fields, get_initial_gf_data_from_ff
|
|
5
3
|
from flowfile_core.flowfile.flow_node.flow_node import FlowNode
|
|
4
|
+
from flowfile_core.schemas.analysis_schemas.graphic_walker_schemas import (
|
|
5
|
+
DataModel,
|
|
6
|
+
GraphicWalkerInput,
|
|
7
|
+
MutField,
|
|
8
|
+
ViewField,
|
|
9
|
+
)
|
|
6
10
|
from flowfile_core.schemas.input_schema import NodeExploreData
|
|
7
|
-
from flowfile_core.schemas.analysis_schemas.graphic_walker_schemas import GraphicWalkerInput, DataModel, MutField, ViewField
|
|
8
|
-
from flowfile_core.configs import logger
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class AnalyticsProcessor:
|
|
12
|
-
|
|
13
14
|
@staticmethod
|
|
14
15
|
def process_graphic_walker_input(node_step: FlowNode) -> NodeExploreData:
|
|
15
16
|
node_explore_data: NodeExploreData = node_step.setting_input
|
|
16
|
-
if hasattr(node_explore_data,
|
|
17
|
+
if hasattr(node_explore_data, "graphic_walker_input"):
|
|
17
18
|
graphic_walker_input = node_explore_data.graphic_walker_input
|
|
18
19
|
else:
|
|
19
20
|
logger.error("NodeExploreData is not an instance of GraphicWalkerInput.")
|
|
@@ -24,8 +25,9 @@ class AnalyticsProcessor:
|
|
|
24
25
|
return node_explore_data
|
|
25
26
|
|
|
26
27
|
@staticmethod
|
|
27
|
-
def create_graphic_walker_input(
|
|
28
|
-
|
|
28
|
+
def create_graphic_walker_input(
|
|
29
|
+
node_step: FlowNode, graphic_walker_input: GraphicWalkerInput = None
|
|
30
|
+
) -> GraphicWalkerInput:
|
|
29
31
|
if not node_step.results.analysis_data_generator:
|
|
30
32
|
node_step.get_predicted_schema()
|
|
31
33
|
fields = convert_ff_columns_to_gw_fields(node_step.get_predicted_schema())
|
|
@@ -42,17 +44,17 @@ class AnalyticsProcessor:
|
|
|
42
44
|
return graphic_walker_input
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def check_if_field_in_spec_list_encodings(spec_list:
|
|
46
|
-
for encoding in spec_list[
|
|
47
|
-
if field_name in encoding[
|
|
47
|
+
def check_if_field_in_spec_list_encodings(spec_list: dict, field_name: str) -> bool:
|
|
48
|
+
for encoding in spec_list["encodings"]["dimensions"].values():
|
|
49
|
+
if field_name in encoding["fid"]:
|
|
48
50
|
return True
|
|
49
|
-
for encoding in spec_list[
|
|
50
|
-
if field_name in encoding[
|
|
51
|
+
for encoding in spec_list["encodings"]["measures"].values():
|
|
52
|
+
if field_name in encoding["fid"]:
|
|
51
53
|
return True
|
|
52
54
|
return False
|
|
53
55
|
|
|
54
56
|
|
|
55
|
-
def get_existing_encoding_fields(spec_list:
|
|
57
|
+
def get_existing_encoding_fields(spec_list: dict) -> set[str]:
|
|
56
58
|
"""
|
|
57
59
|
Get the existing encoding fields from the spec_list.
|
|
58
60
|
|
|
@@ -62,8 +64,8 @@ def get_existing_encoding_fields(spec_list: Dict) -> Set[str]:
|
|
|
62
64
|
Returns:
|
|
63
65
|
Set[str]: A set of existing encoding fields.
|
|
64
66
|
"""
|
|
65
|
-
dimensions = {encoding[
|
|
66
|
-
measures = {encoding[
|
|
67
|
+
dimensions = {encoding["fid"] for encoding in spec_list["encodings"]["dimensions"]}
|
|
68
|
+
measures = {encoding["fid"] for encoding in spec_list["encodings"]["measures"]}
|
|
67
69
|
return dimensions.union(measures)
|
|
68
70
|
|
|
69
71
|
|
|
@@ -72,7 +74,7 @@ def transform_mut_field_to_view_field(mut_field: MutField) -> ViewField:
|
|
|
72
74
|
return view_field
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def add_field_to_spec_list(spec_list:
|
|
77
|
+
def add_field_to_spec_list(spec_list: dict, mut_field: MutField) -> None:
|
|
76
78
|
"""
|
|
77
79
|
Add a field to the spec_list.
|
|
78
80
|
|
|
@@ -84,13 +86,13 @@ def add_field_to_spec_list(spec_list: Dict, mut_field: MutField) -> None:
|
|
|
84
86
|
None
|
|
85
87
|
"""
|
|
86
88
|
view_field = transform_mut_field_to_view_field(mut_field)
|
|
87
|
-
if mut_field.analyticType ==
|
|
88
|
-
spec_list[
|
|
89
|
+
if mut_field.analyticType == "measure":
|
|
90
|
+
spec_list["encodings"]["measures"].append(view_field.model_dump_dict())
|
|
89
91
|
else:
|
|
90
|
-
spec_list[
|
|
92
|
+
spec_list["encodings"]["dimensions"].append(view_field.model_dump_dict())
|
|
91
93
|
|
|
92
94
|
|
|
93
|
-
def validate_spec_list_with_data_model_types(spec_list:
|
|
95
|
+
def validate_spec_list_with_data_model_types(spec_list: dict, data_model: DataModel) -> None:
|
|
94
96
|
"""
|
|
95
97
|
Validate the spec_list with the data model types.
|
|
96
98
|
|
|
@@ -109,7 +111,7 @@ def validate_spec_list_with_data_model_types(spec_list: Dict, data_model: DataMo
|
|
|
109
111
|
add_field_to_spec_list(spec_list, field)
|
|
110
112
|
|
|
111
113
|
|
|
112
|
-
def validate_spec_lists_with_data_model(spec_lists:
|
|
114
|
+
def validate_spec_lists_with_data_model(spec_lists: list[dict], data_model: DataModel) -> None:
|
|
113
115
|
"""
|
|
114
116
|
Validate the spec lists with the data model.
|
|
115
117
|
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
from flowfile_core.flowfile.flow_data_engine.flow_data_engine import FlowDataEngine, FlowfileColumn
|
|
2
2
|
from flowfile_core.schemas.analysis_schemas import graphic_walker_schemas as gw_schema
|
|
3
|
-
from typing import List
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
def get_semantic_type(data_type: str) -> str:
|
|
7
6
|
"""Determine the semanticType based on the data_type."""
|
|
8
|
-
if data_type in [
|
|
9
|
-
return
|
|
10
|
-
elif data_type in [
|
|
11
|
-
return
|
|
12
|
-
elif data_type in [
|
|
13
|
-
return
|
|
7
|
+
if data_type in ["Utf8", "VARCHAR", "CHAR", "NVARCHAR", "String"]:
|
|
8
|
+
return "nominal"
|
|
9
|
+
elif data_type in ["Int64", "Float64", "Int32", "Float32", "Int16", "Float16", "Decimal"]:
|
|
10
|
+
return "quantitative"
|
|
11
|
+
elif data_type in ["Datetime", "Date"]:
|
|
12
|
+
return "temporal"
|
|
14
13
|
else:
|
|
15
|
-
return
|
|
14
|
+
return "nominal" # Default case; adjust as necessary
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
def get_analytic_type(semantic_type: str) -> gw_schema.AnalyticTypeLit:
|
|
19
18
|
"""Determine the analyticType based on the semanticType."""
|
|
20
|
-
return
|
|
19
|
+
return "measure" if semantic_type == "quantitative" else "dimension"
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
def convert_ff_column_to_gw_field(flow_file_column: FlowfileColumn) -> gw_schema.MutField:
|
|
@@ -42,11 +41,11 @@ def convert_ff_column_to_gw_field(flow_file_column: FlowfileColumn) -> gw_schema
|
|
|
42
41
|
basename=flow_file_column.name,
|
|
43
42
|
key=flow_file_column.name,
|
|
44
43
|
semanticType=semantic_type,
|
|
45
|
-
analyticType=analytic_type
|
|
44
|
+
analyticType=analytic_type,
|
|
46
45
|
)
|
|
47
46
|
|
|
48
47
|
|
|
49
|
-
def convert_ff_columns_to_gw_fields(ff_columns:
|
|
48
|
+
def convert_ff_columns_to_gw_fields(ff_columns: list[FlowfileColumn]) -> [gw_schema.MutField]:
|
|
50
49
|
return [convert_ff_column_to_gw_field(ff_column) for ff_column in ff_columns]
|
|
51
50
|
|
|
52
51
|
|
|
@@ -55,6 +54,6 @@ def get_initial_gf_data_from_ff(flow_file: FlowDataEngine) -> gw_schema.DataMode
|
|
|
55
54
|
return gw_schema.DataModel(fields=fields, data=[])
|
|
56
55
|
|
|
57
56
|
|
|
58
|
-
def get_gf_data_from_ff(flow_file: FlowDataEngine, fields:
|
|
57
|
+
def get_gf_data_from_ff(flow_file: FlowDataEngine, fields: list[gw_schema.MutField]) -> gw_schema.DataModel:
|
|
59
58
|
data = flow_file.to_pylist()
|
|
60
59
|
return gw_schema.DataModel(fields=fields, data=data)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from flowfile_core.schemas.input_schema import NodeExploreData, NodePromise
|
|
2
1
|
from flowfile_core.schemas.analysis_schemas.graphic_walker_schemas import GraphicWalkerInput
|
|
2
|
+
from flowfile_core.schemas.input_schema import NodeExploreData, NodePromise
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def create_graphic_walker_node_from_node_promise(node_promise: NodePromise) -> NodeExploreData:
|