Flowfile 0.3.9__py3-none-any.whl → 0.5.1__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.
- flowfile/__init__.py +8 -1
- flowfile/api.py +1 -3
- flowfile/web/static/assets/{CloudConnectionManager-c97c25f8.js → CloudConnectionManager-0dfba9f2.js} +2 -2
- flowfile/web/static/assets/{CloudStorageReader-f1ff509e.js → CloudStorageReader-d5b1b6c9.js} +11 -78
- flowfile/web/static/assets/{CloudStorageWriter-034f8b78.js → CloudStorageWriter-00d87aad.js} +12 -79
- flowfile/web/static/assets/{CloudStorageWriter-49c9a4b2.css → CloudStorageWriter-b0ee067f.css} +24 -24
- flowfile/web/static/assets/ColumnSelector-4685e75d.js +83 -0
- flowfile/web/static/assets/ColumnSelector-47996a16.css +10 -0
- flowfile/web/static/assets/ContextMenu-23e909da.js +41 -0
- flowfile/web/static/assets/{SettingsSection-9c836ecc.css → ContextMenu-4c74eef1.css} +0 -21
- flowfile/web/static/assets/ContextMenu-63cfa99b.css +26 -0
- flowfile/web/static/assets/ContextMenu-70ae0c79.js +41 -0
- flowfile/web/static/assets/ContextMenu-c13f91d0.css +26 -0
- flowfile/web/static/assets/ContextMenu-f149cf7c.js +41 -0
- flowfile/web/static/assets/{CrossJoin-41efa4cb.css → CrossJoin-1119d18e.css} +18 -18
- flowfile/web/static/assets/{CrossJoin-9e156ebe.js → CrossJoin-702a3edd.js} +14 -84
- flowfile/web/static/assets/CustomNode-74a37f74.css +32 -0
- flowfile/web/static/assets/CustomNode-b1519993.js +211 -0
- flowfile/web/static/assets/{DatabaseConnectionSettings-d5c625b3.js → DatabaseConnectionSettings-6f3e4ea5.js} +3 -3
- flowfile/web/static/assets/{DatabaseManager-265adc5e.js → DatabaseManager-cf5ef661.js} +2 -2
- flowfile/web/static/assets/{DatabaseReader-f50c6558.css → DatabaseReader-ae61773c.css} +0 -27
- flowfile/web/static/assets/{DatabaseReader-0b10551e.js → DatabaseReader-d38c7295.js} +14 -114
- flowfile/web/static/assets/{DatabaseWriter-c17c6916.js → DatabaseWriter-b04ef46a.js} +13 -74
- flowfile/web/static/assets/{ExploreData-5bdae813.css → ExploreData-2d0cf4db.css} +8 -14
- flowfile/web/static/assets/ExploreData-5fa10ed8.js +192 -0
- flowfile/web/static/assets/{ExternalSource-3a66556c.js → ExternalSource-d39af878.js} +8 -79
- flowfile/web/static/assets/{Filter-91ad87e7.js → Filter-9b6d08db.js} +12 -85
- flowfile/web/static/assets/{Filter-a9d08ba1.css → Filter-f62091b3.css} +3 -3
- flowfile/web/static/assets/{Formula-3c395ab1.js → Formula-6b04fb1d.js} +20 -87
- flowfile/web/static/assets/{Formula-29f19d21.css → Formula-bb96803d.css} +4 -4
- flowfile/web/static/assets/{FuzzyMatch-6857de82.css → FuzzyMatch-1010f966.css} +42 -42
- flowfile/web/static/assets/{FuzzyMatch-2df0d230.js → FuzzyMatch-999521f4.js} +16 -87
- flowfile/web/static/assets/{GraphSolver-d285877f.js → GraphSolver-17dd2198.js} +13 -159
- flowfile/web/static/assets/GraphSolver-f0cb7bfb.css +22 -0
- flowfile/web/static/assets/{GroupBy-0bd1cc6b.js → GroupBy-6b039e18.js} +12 -75
- flowfile/web/static/assets/{Unique-b5615727.css → GroupBy-b9505323.css} +8 -8
- flowfile/web/static/assets/{Join-5a78a203.js → Join-24d0f113.js} +15 -85
- flowfile/web/static/assets/{Join-f45eff22.css → Join-fd79b451.css} +20 -20
- flowfile/web/static/assets/{ManualInput-a71b52c6.css → ManualInput-3246a08d.css} +20 -20
- flowfile/web/static/assets/{ManualInput-93aef9d6.js → ManualInput-34639209.js} +11 -82
- flowfile/web/static/assets/MultiSelect-0e8724a3.js +5 -0
- flowfile/web/static/assets/MultiSelect.vue_vue_type_script_setup_true_lang-b0e538c2.js +63 -0
- flowfile/web/static/assets/NumericInput-3d63a470.js +5 -0
- flowfile/web/static/assets/NumericInput.vue_vue_type_script_setup_true_lang-e0edeccc.js +35 -0
- flowfile/web/static/assets/Output-283fe388.css +37 -0
- flowfile/web/static/assets/{Output-411ecaee.js → Output-edea9802.js} +62 -273
- flowfile/web/static/assets/{Pivot-89db4b04.js → Pivot-61d19301.js} +14 -138
- flowfile/web/static/assets/Pivot-cf333e3d.css +22 -0
- flowfile/web/static/assets/PivotValidation-891ddfb0.css +13 -0
- flowfile/web/static/assets/PivotValidation-c46cd420.css +13 -0
- flowfile/web/static/assets/PivotValidation-de9f43fe.js +61 -0
- flowfile/web/static/assets/PivotValidation-f97fec5b.js +61 -0
- flowfile/web/static/assets/{PolarsCode-a9f974f8.js → PolarsCode-bc3c9984.js} +13 -80
- flowfile/web/static/assets/Read-64a3f259.js +218 -0
- flowfile/web/static/assets/Read-e808b239.css +62 -0
- flowfile/web/static/assets/RecordCount-3d5039be.js +53 -0
- flowfile/web/static/assets/{RecordId-55ae7d36.js → RecordId-597510e0.js} +8 -80
- flowfile/web/static/assets/SQLQueryComponent-36cef432.css +27 -0
- flowfile/web/static/assets/SQLQueryComponent-df51adbe.js +38 -0
- flowfile/web/static/assets/{Sample-b4a18476.js → Sample-4be0a507.js} +8 -77
- flowfile/web/static/assets/{SecretManager-b066d13a.js → SecretManager-4839be57.js} +2 -2
- flowfile/web/static/assets/{Select-727688dc.js → Select-9b72f201.js} +11 -85
- flowfile/web/static/assets/SettingsSection-2e4d03c4.css +21 -0
- flowfile/web/static/assets/SettingsSection-5c696bee.css +20 -0
- flowfile/web/static/assets/SettingsSection-71e6b7e3.css +21 -0
- flowfile/web/static/assets/SettingsSection-7ded385d.js +45 -0
- flowfile/web/static/assets/{SettingsSection-695ac487.js → SettingsSection-e1e9c953.js} +2 -40
- flowfile/web/static/assets/SettingsSection-f0f75a42.js +53 -0
- flowfile/web/static/assets/SingleSelect-6c777aac.js +5 -0
- flowfile/web/static/assets/SingleSelect.vue_vue_type_script_setup_true_lang-33e3ff9b.js +62 -0
- flowfile/web/static/assets/SliderInput-7cb93e62.js +40 -0
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +4 -0
- flowfile/web/static/assets/{GroupBy-ab1ea74b.css → Sort-3643d625.css} +8 -8
- flowfile/web/static/assets/{Sort-be3339a8.js → Sort-6cbde21a.js} +12 -97
- flowfile/web/static/assets/TextInput-d9a40c11.js +5 -0
- flowfile/web/static/assets/TextInput.vue_vue_type_script_setup_true_lang-5896c375.js +32 -0
- flowfile/web/static/assets/{TextToRows-c92d1ec2.css → TextToRows-5d2c1190.css} +9 -9
- flowfile/web/static/assets/{TextToRows-7b8998da.js → TextToRows-c4fcbf4d.js} +14 -83
- flowfile/web/static/assets/ToggleSwitch-4ef91d19.js +5 -0
- flowfile/web/static/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-38478c20.js +31 -0
- flowfile/web/static/assets/{UnavailableFields-8b0cb48e.js → UnavailableFields-a03f512c.js} +2 -2
- flowfile/web/static/assets/{Union-8d9ac7f9.css → Union-af6c3d9b.css} +6 -6
- flowfile/web/static/assets/Union-bfe9b996.js +77 -0
- flowfile/web/static/assets/{Unique-af5a80b4.js → Unique-5d023a27.js} +23 -104
- flowfile/web/static/assets/{Sort-7ccfa0fe.css → Unique-f9fb0809.css} +8 -8
- flowfile/web/static/assets/Unpivot-1e422df3.css +30 -0
- flowfile/web/static/assets/{Unpivot-5195d411.js → Unpivot-91cc5354.js} +12 -166
- flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +13 -0
- flowfile/web/static/assets/UnpivotValidation-7ee2de44.js +51 -0
- flowfile/web/static/assets/{ExploreData-18a4fe52.js → VueGraphicWalker-e51b9924.js} +4 -264
- flowfile/web/static/assets/VueGraphicWalker-ed5ab88b.css +6 -0
- flowfile/web/static/assets/{api-cb00cce6.js → api-c1bad5ca.js} +1 -1
- flowfile/web/static/assets/{api-023d1733.js → api-cf1221f0.js} +1 -1
- flowfile/web/static/assets/{designer-2197d782.css → designer-8da3ba3a.css} +859 -201
- flowfile/web/static/assets/{designer-6c322d8e.js → designer-9633482a.js} +2297 -733
- flowfile/web/static/assets/{documentation-4d1fafe1.js → documentation-ca400224.js} +1 -1
- flowfile/web/static/assets/{dropDown-0b46dd77.js → dropDown-614b998d.js} +1 -1
- flowfile/web/static/assets/{fullEditor-ec4e4f95.js → fullEditor-f7971590.js} +2 -2
- flowfile/web/static/assets/{genericNodeSettings-def5879b.js → genericNodeSettings-4fe5f36b.js} +3 -3
- flowfile/web/static/assets/{index-681a3ed0.css → index-50508d4d.css} +8 -0
- flowfile/web/static/assets/{index-683fc198.js → index-5429bbf8.js} +208 -31
- flowfile/web/static/assets/nodeInput-5d0d6b79.js +41 -0
- flowfile/web/static/assets/outputCsv-076b85ab.js +86 -0
- flowfile/web/static/assets/{Output-48f81019.css → outputCsv-9cc59e0b.css} +0 -143
- flowfile/web/static/assets/outputExcel-0fd17dbe.js +56 -0
- flowfile/web/static/assets/outputExcel-b41305c0.css +102 -0
- flowfile/web/static/assets/outputParquet-b61e0847.js +31 -0
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +4 -0
- flowfile/web/static/assets/readCsv-a8bb8b61.js +179 -0
- flowfile/web/static/assets/readCsv-c767cb37.css +52 -0
- flowfile/web/static/assets/readExcel-67b4aee0.js +201 -0
- flowfile/web/static/assets/readExcel-806d2826.css +64 -0
- flowfile/web/static/assets/readParquet-48c81530.css +19 -0
- flowfile/web/static/assets/readParquet-92ce1dbc.js +23 -0
- flowfile/web/static/assets/{secretApi-baceb6f9.js → secretApi-68435402.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-de91449a.js → selectDynamic-92e25ee3.js} +7 -7
- flowfile/web/static/assets/{selectDynamic-b062bc9b.css → selectDynamic-aa913ff4.css} +16 -16
- flowfile/web/static/assets/user-defined-icon-0ae16c90.png +0 -0
- flowfile/web/static/assets/{vue-codemirror.esm-dc5e3348.js → vue-codemirror.esm-41b0e0d7.js} +65 -36
- flowfile/web/static/assets/{vue-content-loader.es-ba94b82f.js → vue-content-loader.es-2c8e608f.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/METADATA +5 -3
- {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/RECORD +191 -121
- {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/WHEEL +1 -1
- {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info}/entry_points.txt +1 -0
- flowfile_core/__init__.py +3 -0
- flowfile_core/configs/flow_logger.py +5 -13
- flowfile_core/configs/node_store/__init__.py +30 -0
- flowfile_core/configs/node_store/nodes.py +383 -99
- flowfile_core/configs/node_store/user_defined_node_registry.py +193 -0
- flowfile_core/configs/settings.py +2 -1
- flowfile_core/database/connection.py +5 -21
- flowfile_core/fileExplorer/funcs.py +239 -121
- flowfile_core/flowfile/analytics/analytics_processor.py +1 -0
- flowfile_core/flowfile/code_generator/code_generator.py +62 -64
- flowfile_core/flowfile/flow_data_engine/create/funcs.py +73 -56
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +77 -86
- flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +4 -0
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +19 -34
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +36 -0
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +23 -23
- flowfile_core/flowfile/flow_data_engine/join/utils.py +1 -1
- flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +9 -4
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +212 -86
- flowfile_core/flowfile/flow_data_engine/utils.py +2 -0
- flowfile_core/flowfile/flow_graph.py +240 -54
- flowfile_core/flowfile/flow_node/flow_node.py +48 -13
- flowfile_core/flowfile/flow_node/models.py +2 -1
- flowfile_core/flowfile/handler.py +24 -5
- flowfile_core/flowfile/manage/compatibility_enhancements.py +404 -41
- flowfile_core/flowfile/manage/io_flowfile.py +394 -0
- flowfile_core/flowfile/node_designer/__init__.py +47 -0
- flowfile_core/flowfile/node_designer/_type_registry.py +197 -0
- flowfile_core/flowfile/node_designer/custom_node.py +371 -0
- flowfile_core/flowfile/node_designer/ui_components.py +277 -0
- flowfile_core/flowfile/schema_callbacks.py +17 -10
- flowfile_core/flowfile/setting_generator/settings.py +15 -10
- flowfile_core/main.py +5 -1
- flowfile_core/routes/routes.py +73 -30
- flowfile_core/routes/user_defined_components.py +55 -0
- flowfile_core/schemas/cloud_storage_schemas.py +0 -2
- flowfile_core/schemas/input_schema.py +228 -65
- flowfile_core/schemas/output_model.py +5 -2
- flowfile_core/schemas/schemas.py +153 -35
- flowfile_core/schemas/transform_schema.py +1083 -412
- flowfile_core/schemas/yaml_types.py +103 -0
- flowfile_core/types.py +156 -0
- flowfile_core/utils/validate_setup.py +3 -1
- flowfile_frame/__init__.py +3 -1
- flowfile_frame/flow_frame.py +31 -24
- flowfile_frame/flow_frame_methods.py +12 -9
- flowfile_worker/__init__.py +9 -35
- flowfile_worker/create/__init__.py +3 -21
- flowfile_worker/create/funcs.py +68 -56
- flowfile_worker/create/models.py +130 -62
- flowfile_worker/main.py +5 -2
- flowfile_worker/routes.py +52 -13
- shared/__init__.py +15 -0
- shared/storage_config.py +258 -0
- tools/migrate/README.md +56 -0
- tools/migrate/__init__.py +12 -0
- tools/migrate/__main__.py +131 -0
- tools/migrate/legacy_schemas.py +621 -0
- tools/migrate/migrate.py +598 -0
- tools/migrate/tests/__init__.py +0 -0
- tools/migrate/tests/conftest.py +23 -0
- tools/migrate/tests/test_migrate.py +627 -0
- tools/migrate/tests/test_migration_e2e.py +1010 -0
- tools/migrate/tests/test_node_migrations.py +813 -0
- flowfile/web/static/assets/GraphSolver-17fd26db.css +0 -68
- flowfile/web/static/assets/Pivot-f415e85f.css +0 -35
- flowfile/web/static/assets/Read-80dc1675.css +0 -197
- flowfile/web/static/assets/Read-c3b1929c.js +0 -701
- flowfile/web/static/assets/RecordCount-4e95f98e.js +0 -122
- flowfile/web/static/assets/Union-89fd73dc.js +0 -146
- flowfile/web/static/assets/Unpivot-246e9bbd.css +0 -77
- flowfile/web/static/assets/nodeTitle-a16db7c3.js +0 -227
- flowfile/web/static/assets/nodeTitle-f4b12bcb.css +0 -134
- flowfile_core/flowfile/manage/open_flowfile.py +0 -135
- {flowfile-0.3.9.dist-info → flowfile-0.5.1.dist-info/licenses}/LICENSE +0 -0
- /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
|
@@ -12,11 +12,12 @@ from flowfile_core.configs.flow_logger import main_logger
|
|
|
12
12
|
from flowfile_core.flowfile.flow_data_engine.flow_file_column.main import FlowfileColumn, PlType
|
|
13
13
|
from flowfile_core.schemas import transform_schema
|
|
14
14
|
from flowfile_core.schemas import input_schema
|
|
15
|
+
from flowfile_core.schemas.transform_schema import FuzzyMatchInputManager
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def _ensure_all_columns_have_select(left_cols: List[str],
|
|
18
19
|
right_cols: List[str],
|
|
19
|
-
fuzzy_match_input: transform_schema.
|
|
20
|
+
fuzzy_match_input: transform_schema.FuzzyMatchInputManager):
|
|
20
21
|
"""
|
|
21
22
|
Ensure that all columns in the left and right FlowDataEngines are included in the fuzzy match input's select
|
|
22
23
|
statements.
|
|
@@ -38,7 +39,7 @@ def _ensure_all_columns_have_select(left_cols: List[str],
|
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
def _order_join_inputs_based_on_col_order(col_order: List[str], join_inputs: transform_schema.
|
|
42
|
+
def _order_join_inputs_based_on_col_order(col_order: List[str], join_inputs: transform_schema.JoinInputsManager) -> None:
|
|
42
43
|
"""
|
|
43
44
|
Ensure that the select columns in the fuzzy match input match the order of the incoming columns.
|
|
44
45
|
This function modifies the join_inputs object in-place.
|
|
@@ -46,36 +47,42 @@ def _order_join_inputs_based_on_col_order(col_order: List[str], join_inputs: tra
|
|
|
46
47
|
Returns:
|
|
47
48
|
None
|
|
48
49
|
"""
|
|
49
|
-
select_map = {select.
|
|
50
|
+
select_map = {select.old_name: select for select in join_inputs.renames}
|
|
50
51
|
ordered_renames = [select_map[col] for col in col_order if col in select_map]
|
|
51
|
-
join_inputs.renames = ordered_renames
|
|
52
|
+
join_inputs.select_inputs.renames = ordered_renames
|
|
52
53
|
|
|
53
54
|
|
|
54
|
-
def calculate_fuzzy_match_schema(fm_input: transform_schema.
|
|
55
|
+
def calculate_fuzzy_match_schema(fm_input: transform_schema.FuzzyMatchInputManager,
|
|
55
56
|
left_schema: List[FlowfileColumn],
|
|
56
57
|
right_schema: List[FlowfileColumn]):
|
|
57
58
|
_ensure_all_columns_have_select(left_cols=[col.column_name for col in left_schema],
|
|
58
59
|
right_cols=[col.column_name for col in right_schema],
|
|
59
60
|
fuzzy_match_input=fm_input)
|
|
61
|
+
|
|
60
62
|
_order_join_inputs_based_on_col_order(col_order=[col.column_name for col in left_schema],
|
|
61
63
|
join_inputs=fm_input.left_select)
|
|
62
64
|
_order_join_inputs_based_on_col_order(col_order=[col.column_name for col in right_schema],
|
|
63
65
|
join_inputs=fm_input.right_select)
|
|
66
|
+
for column in fm_input.left_select.renames:
|
|
67
|
+
if column.join_key:
|
|
68
|
+
column.keep = True
|
|
69
|
+
for column in fm_input.right_select.renames:
|
|
70
|
+
if column.join_key:
|
|
71
|
+
column.keep = True
|
|
72
|
+
|
|
64
73
|
left_schema_dict, right_schema_dict = ({ls.name: ls for ls in left_schema}, {rs.name: rs for rs in right_schema})
|
|
65
74
|
fm_input.auto_rename()
|
|
66
|
-
|
|
67
75
|
right_renames = {column.old_name: column.new_name for column in fm_input.right_select.renames}
|
|
68
76
|
new_join_mapping = rename_fuzzy_right_mapping(fm_input.join_mapping, right_renames)
|
|
69
|
-
|
|
70
77
|
output_schema = []
|
|
71
78
|
for column in fm_input.left_select.renames:
|
|
72
79
|
column_schema = left_schema_dict.get(column.old_name)
|
|
73
|
-
if column_schema and column.keep:
|
|
80
|
+
if column_schema and (column.keep or column.join_key):
|
|
74
81
|
output_schema.append(FlowfileColumn.from_input(column.new_name, column_schema.data_type,
|
|
75
82
|
example_values=column_schema.example_values))
|
|
76
83
|
for column in fm_input.right_select.renames:
|
|
77
84
|
column_schema = right_schema_dict.get(column.old_name)
|
|
78
|
-
if column_schema and column.keep:
|
|
85
|
+
if column_schema and (column.keep or column.join_key):
|
|
79
86
|
output_schema.append(FlowfileColumn.from_input(column.new_name, column_schema.data_type,
|
|
80
87
|
example_values=column_schema.example_values))
|
|
81
88
|
set_name_in_fuzzy_mappings(new_join_mapping)
|
|
@@ -117,7 +124,7 @@ def pre_calculate_pivot_schema(node_input_schema: List[FlowfileColumn],
|
|
|
117
124
|
pivot_input.index_columns]
|
|
118
125
|
val_column_schema = get_schema_of_column(node_input_schema, pivot_input.value_col)
|
|
119
126
|
if output_fields is not None and len(output_fields) > 0:
|
|
120
|
-
return index_columns_schema+[FlowfileColumn(PlType(
|
|
127
|
+
return index_columns_schema+[FlowfileColumn(PlType(column_name=output_field.name,
|
|
121
128
|
pl_datatype=output_field.data_type)) for output_field in
|
|
122
129
|
output_fields]
|
|
123
130
|
|
|
@@ -39,11 +39,14 @@ def join(node_data: "NodeData") -> NodeData:
|
|
|
39
39
|
join_key = overlapping_cols[0]
|
|
40
40
|
else:
|
|
41
41
|
join_key = ''
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
join_input_manager = transform_schema.JoinInputManager(
|
|
43
|
+
transform_schema.JoinInput(join_mapping=join_key,
|
|
44
|
+
left_select=node_data.main_input.columns,
|
|
45
|
+
right_select=node_data.right_input.columns
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
join_input_manager.auto_rename()
|
|
49
|
+
ji = join_input_manager.to_join_input()
|
|
47
50
|
node_data.setting_input = input_schema.NodeJoin(flow_id=node_data.flow_id,
|
|
48
51
|
node_id=node_data.node_id,
|
|
49
52
|
join_input=ji)
|
|
@@ -53,13 +56,15 @@ def join(node_data: "NodeData") -> NodeData:
|
|
|
53
56
|
@setting_generator_method
|
|
54
57
|
def cross_join(node_data: "NodeData") -> NodeData:
|
|
55
58
|
if node_data.right_input and node_data.main_input:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
cj_input_manager = transform_schema.CrossJoinInputManager(
|
|
60
|
+
transform_schema.CrossJoinInput(left_select=node_data.main_input.columns,
|
|
61
|
+
right_select=node_data.right_input.columns)
|
|
62
|
+
)
|
|
63
|
+
cj_input_manager.auto_rename()
|
|
64
|
+
cj = cj_input_manager.to_cross_join_input()
|
|
60
65
|
node_data.setting_input = input_schema.NodeCrossJoin(flow_id=node_data.flow_id,
|
|
61
66
|
node_id=node_data.node_id,
|
|
62
|
-
cross_join_input=
|
|
67
|
+
cross_join_input=cj)
|
|
63
68
|
return node_data
|
|
64
69
|
|
|
65
70
|
|
flowfile_core/main.py
CHANGED
|
@@ -7,6 +7,8 @@ import uvicorn
|
|
|
7
7
|
from fastapi import FastAPI
|
|
8
8
|
from fastapi.middleware.cors import CORSMiddleware
|
|
9
9
|
|
|
10
|
+
from shared.storage_config import storage
|
|
11
|
+
|
|
10
12
|
from flowfile_core import ServerRun
|
|
11
13
|
from flowfile_core.configs.settings import (SERVER_HOST, SERVER_PORT, WORKER_HOST, WORKER_PORT, WORKER_URL,)
|
|
12
14
|
|
|
@@ -16,15 +18,16 @@ from flowfile_core.routes.routes import router
|
|
|
16
18
|
from flowfile_core.routes.public import router as public_router
|
|
17
19
|
from flowfile_core.routes.logs import router as logs_router
|
|
18
20
|
from flowfile_core.routes.cloud_connections import router as cloud_connections_router
|
|
21
|
+
from flowfile_core.routes.user_defined_components import router as user_defined_components_router
|
|
19
22
|
|
|
20
23
|
from flowfile_core.configs.flow_logger import clear_all_flow_logs
|
|
24
|
+
storage.cleanup_directories()
|
|
21
25
|
|
|
22
26
|
os.environ["FLOWFILE_MODE"] = "electron"
|
|
23
27
|
|
|
24
28
|
should_exit = False
|
|
25
29
|
server_instance = None
|
|
26
30
|
|
|
27
|
-
|
|
28
31
|
@asynccontextmanager
|
|
29
32
|
async def shutdown_handler(app: FastAPI):
|
|
30
33
|
"""Handles the graceful startup and shutdown of the FastAPI application.
|
|
@@ -77,6 +80,7 @@ app.include_router(logs_router, tags=["logs"])
|
|
|
77
80
|
app.include_router(auth_router, prefix="/auth", tags=["auth"])
|
|
78
81
|
app.include_router(secrets_router, prefix="/secrets", tags=["secrets"])
|
|
79
82
|
app.include_router(cloud_connections_router, prefix="/cloud_connections", tags=["cloud_connections"])
|
|
83
|
+
app.include_router(user_defined_components_router, prefix="/user_defined_components", tags=["user_defined_components"])
|
|
80
84
|
|
|
81
85
|
|
|
82
86
|
@app.post("/shutdown")
|
flowfile_core/routes/routes.py
CHANGED
|
@@ -12,49 +12,49 @@ import logging
|
|
|
12
12
|
import os
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import List, Dict, Any, Optional
|
|
15
|
-
from sqlalchemy.orm import Session
|
|
16
15
|
|
|
17
16
|
from fastapi import APIRouter, File, UploadFile, BackgroundTasks, HTTPException, status, Body, Depends
|
|
18
17
|
from fastapi.responses import JSONResponse, Response
|
|
19
18
|
# External dependencies
|
|
20
19
|
from polars_expr_transformer.function_overview import get_all_expressions, get_expression_overview
|
|
20
|
+
from sqlalchemy.orm import Session
|
|
21
21
|
|
|
22
|
+
from flowfile_core import flow_file_handler
|
|
22
23
|
# Core modules
|
|
23
24
|
from flowfile_core.auth.jwt import get_current_active_user
|
|
24
25
|
from flowfile_core.configs import logger
|
|
25
|
-
from flowfile_core.configs.node_store import
|
|
26
|
-
from flowfile_core.
|
|
26
|
+
from flowfile_core.configs.node_store import nodes_list, check_if_has_default_setting
|
|
27
|
+
from flowfile_core.database.connection import get_db
|
|
27
28
|
# File handling
|
|
28
29
|
from flowfile_core.fileExplorer.funcs import (
|
|
29
|
-
|
|
30
|
+
SecureFileExplorer,
|
|
30
31
|
FileInfo,
|
|
31
32
|
get_files_from_directory
|
|
32
33
|
)
|
|
33
|
-
from flowfile_core.flowfile.flow_graph import add_connection, delete_connection
|
|
34
|
-
from flowfile_core.flowfile.code_generator.code_generator import export_flow_to_polars
|
|
35
34
|
from flowfile_core.flowfile.analytics.analytics_processor import AnalyticsProcessor
|
|
35
|
+
from flowfile_core.flowfile.code_generator.code_generator import export_flow_to_polars
|
|
36
|
+
from flowfile_core.flowfile.database_connection_manager.db_connections import (store_database_connection,
|
|
37
|
+
get_database_connection,
|
|
38
|
+
delete_database_connection,
|
|
39
|
+
get_all_database_connections_interface)
|
|
36
40
|
from flowfile_core.flowfile.extensions import get_instant_func_results
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
from flowfile_core.flowfile.flow_graph import add_connection, delete_connection
|
|
39
42
|
from flowfile_core.flowfile.sources.external_sources.sql_source.sql_source import create_sql_source_from_db_settings
|
|
40
43
|
from flowfile_core.run_lock import get_flow_run_lock
|
|
41
|
-
# Schema and models
|
|
42
44
|
from flowfile_core.schemas import input_schema, schemas, output_model
|
|
43
45
|
from flowfile_core.utils import excel_file_manager
|
|
44
|
-
from flowfile_core.utils.fileManager import create_dir
|
|
46
|
+
from flowfile_core.utils.fileManager import create_dir
|
|
45
47
|
from flowfile_core.utils.utils import camel_case_to_snake_case
|
|
46
|
-
from
|
|
47
|
-
from flowfile_core.flowfile.database_connection_manager.db_connections import (store_database_connection,
|
|
48
|
-
get_database_connection,
|
|
49
|
-
delete_database_connection,
|
|
50
|
-
get_all_database_connections_interface)
|
|
51
|
-
from flowfile_core.database.connection import get_db
|
|
48
|
+
from shared.storage_config import storage
|
|
52
49
|
|
|
53
50
|
|
|
54
51
|
router = APIRouter(dependencies=[Depends(get_current_active_user)])
|
|
55
52
|
|
|
56
53
|
# Initialize services
|
|
57
|
-
file_explorer =
|
|
54
|
+
file_explorer = SecureFileExplorer(
|
|
55
|
+
start_path=storage.user_data_directory,
|
|
56
|
+
sandbox_root=storage.user_data_directory
|
|
57
|
+
)
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def get_node_model(setting_name_ref: str):
|
|
@@ -148,7 +148,7 @@ async def get_directory_contents(directory: str, file_types: List[str] = None,
|
|
|
148
148
|
Returns:
|
|
149
149
|
A list of `FileInfo` objects representing the directory's contents.
|
|
150
150
|
"""
|
|
151
|
-
directory_explorer =
|
|
151
|
+
directory_explorer = SecureFileExplorer(directory, storage.user_data_directory)
|
|
152
152
|
try:
|
|
153
153
|
return directory_explorer.list_contents(show_hidden=include_hidden, file_types=file_types)
|
|
154
154
|
except Exception as e:
|
|
@@ -198,6 +198,24 @@ async def get_active_flow_file_sessions() -> List[schemas.FlowSettings]:
|
|
|
198
198
|
return [flf.flow_settings for flf in flow_file_handler.flowfile_flows]
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
@router.post("/node/trigger_fetch_data", tags=['editor'])
|
|
202
|
+
async def trigger_fetch_node_data(flow_id: int, node_id: int, background_tasks: BackgroundTasks):
|
|
203
|
+
"""Fetches and refreshes the data for a specific node."""
|
|
204
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
205
|
+
lock = get_flow_run_lock(flow_id)
|
|
206
|
+
async with lock:
|
|
207
|
+
if flow.flow_settings.is_running:
|
|
208
|
+
raise HTTPException(422, 'Flow is already running')
|
|
209
|
+
try:
|
|
210
|
+
flow.validate_if_node_can_be_fetched(node_id)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
raise HTTPException(422, str(e))
|
|
213
|
+
background_tasks.add_task(flow.trigger_fetch_node, node_id)
|
|
214
|
+
return JSONResponse(content={"message": "Data started",
|
|
215
|
+
"flow_id": flow_id,
|
|
216
|
+
"node_id": node_id}, status_code=status.HTTP_200_OK)
|
|
217
|
+
|
|
218
|
+
|
|
201
219
|
@router.post('/flow/run/', tags=['editor'])
|
|
202
220
|
async def run_flow(flow_id: int, background_tasks: BackgroundTasks) -> JSONResponse:
|
|
203
221
|
"""Executes a flow in a background task.
|
|
@@ -228,6 +246,16 @@ def cancel_flow(flow_id: int):
|
|
|
228
246
|
flow.cancel()
|
|
229
247
|
|
|
230
248
|
|
|
249
|
+
@router.post("/flow/apply_standard_layout/", tags=["editor"])
|
|
250
|
+
def apply_standard_layout(flow_id: int):
|
|
251
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
252
|
+
if not flow:
|
|
253
|
+
raise HTTPException(status_code=404, detail="Flow not found")
|
|
254
|
+
if flow.flow_settings.is_running:
|
|
255
|
+
raise HTTPException(422, "Flow is running")
|
|
256
|
+
flow.apply_layout()
|
|
257
|
+
|
|
258
|
+
|
|
231
259
|
@router.get('/flow/run_status/', tags=['editor'],
|
|
232
260
|
response_model=output_model.RunInformation)
|
|
233
261
|
def get_run_status(flow_id: int, response: Response):
|
|
@@ -240,8 +268,8 @@ def get_run_status(flow_id: int, response: Response):
|
|
|
240
268
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
241
269
|
if flow.flow_settings.is_running:
|
|
242
270
|
response.status_code = status.HTTP_202_ACCEPTED
|
|
243
|
-
|
|
244
|
-
|
|
271
|
+
else:
|
|
272
|
+
response.status_code = status.HTTP_200_OK
|
|
245
273
|
return flow.get_run_info()
|
|
246
274
|
|
|
247
275
|
|
|
@@ -324,7 +352,7 @@ def add_node(flow_id: int, node_id: int, node_type: str, pos_x: int = 0, pos_y:
|
|
|
324
352
|
logger.info("Adding node")
|
|
325
353
|
flow.add_node_promise(node_promise)
|
|
326
354
|
|
|
327
|
-
if
|
|
355
|
+
if check_if_has_default_setting(node_type):
|
|
328
356
|
logger.info(f'Found standard settings for {node_type}, trying to upload them')
|
|
329
357
|
setting_name_ref = 'node' + node_type.replace('_', '')
|
|
330
358
|
node_model = get_node_model(setting_name_ref)
|
|
@@ -439,11 +467,24 @@ def get_generated_code(flow_id: int) -> str:
|
|
|
439
467
|
|
|
440
468
|
|
|
441
469
|
@router.post('/editor/create_flow/', tags=['editor'])
|
|
442
|
-
def create_flow(flow_path: str):
|
|
470
|
+
def create_flow(flow_path: str = None, name: str = None):
|
|
443
471
|
"""Creates a new, empty flow file at the specified path and registers a session for it."""
|
|
444
|
-
flow_path
|
|
445
|
-
|
|
446
|
-
|
|
472
|
+
if flow_path is not None and name is None:
|
|
473
|
+
name = Path(flow_path).stem
|
|
474
|
+
elif flow_path is not None and name is not None:
|
|
475
|
+
if name not in flow_path and (flow_path.endswith(".yaml") or flow_path.endswith(".yml")):
|
|
476
|
+
raise HTTPException(422, 'The name must be part of the flow path when a full path is provided')
|
|
477
|
+
elif name in flow_path and not (flow_path.endswith(".yaml") or flow_path.endswith(".yml")):
|
|
478
|
+
flow_path = str(Path(flow_path) / (name + ".yaml"))
|
|
479
|
+
elif name not in flow_path and (name.endswith(".yaml") or name.endswith(".yml")):
|
|
480
|
+
flow_path = str(Path(flow_path) / name)
|
|
481
|
+
elif name not in flow_path and not (name.endswith(".yaml") or name.endswith(".yml")):
|
|
482
|
+
flow_path = str(Path(flow_path) / (name + ".yaml"))
|
|
483
|
+
if flow_path is not None:
|
|
484
|
+
flow_path_ref = Path(flow_path)
|
|
485
|
+
if not flow_path_ref.parent.exists():
|
|
486
|
+
raise HTTPException(422, 'The directory does not exist')
|
|
487
|
+
return flow_file_handler.add_flow(name=name, flow_path=flow_path)
|
|
447
488
|
|
|
448
489
|
|
|
449
490
|
@router.post('/editor/close_flow/', tags=['editor'])
|
|
@@ -471,6 +512,7 @@ def add_generic_settings(input_data: Dict[str, Any], node_type: str, current_use
|
|
|
471
512
|
add_func = getattr(flow, 'add_' + node_type)
|
|
472
513
|
parsed_input = None
|
|
473
514
|
setting_name_ref = 'node' + node_type.replace('_', '')
|
|
515
|
+
|
|
474
516
|
if add_func is None:
|
|
475
517
|
raise HTTPException(404, 'could not find the function')
|
|
476
518
|
try:
|
|
@@ -496,10 +538,11 @@ def get_list_of_saved_flows(path: str):
|
|
|
496
538
|
except:
|
|
497
539
|
return []
|
|
498
540
|
|
|
499
|
-
|
|
500
|
-
|
|
541
|
+
|
|
542
|
+
@router.get('/node_list', response_model=List[schemas.NodeTemplate])
|
|
543
|
+
def get_node_list() -> List[schemas.NodeTemplate]:
|
|
501
544
|
"""Retrieves the list of all available node types and their templates."""
|
|
502
|
-
return
|
|
545
|
+
return nodes_list
|
|
503
546
|
|
|
504
547
|
|
|
505
548
|
@router.get('/node', response_model=output_model.NodeData, tags=['editor'])
|
|
@@ -555,7 +598,7 @@ async def get_downstream_node_ids(flow_id: int, node_id: int) -> List[int]:
|
|
|
555
598
|
|
|
556
599
|
@router.get('/import_flow/', tags=['editor'], response_model=int)
|
|
557
600
|
def import_saved_flow(flow_path: str) -> int:
|
|
558
|
-
"""Imports a flow from a saved `.
|
|
601
|
+
"""Imports a flow from a saved `.yaml` and registers it as a new session."""
|
|
559
602
|
flow_path = Path(flow_path)
|
|
560
603
|
if not flow_path.exists():
|
|
561
604
|
raise HTTPException(404, 'File not found')
|
|
@@ -564,7 +607,7 @@ def import_saved_flow(flow_path: str) -> int:
|
|
|
564
607
|
|
|
565
608
|
@router.get('/save_flow', tags=['editor'])
|
|
566
609
|
def save_flow(flow_id: int, flow_path: str = None):
|
|
567
|
-
"""Saves the current state of a flow to a `.
|
|
610
|
+
"""Saves the current state of a flow to a `.yaml`."""
|
|
568
611
|
flow = flow_file_handler.get_flow(flow_id)
|
|
569
612
|
flow.save_flow(flow_path=flow_path)
|
|
570
613
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, HTTPException, Depends
|
|
5
|
+
|
|
6
|
+
from flowfile_core import flow_file_handler
|
|
7
|
+
# Core modules
|
|
8
|
+
from flowfile_core.auth.jwt import get_current_active_user
|
|
9
|
+
from flowfile_core.configs import logger
|
|
10
|
+
from flowfile_core.configs.node_store import CUSTOM_NODE_STORE
|
|
11
|
+
# File handling
|
|
12
|
+
from flowfile_core.schemas import input_schema
|
|
13
|
+
from flowfile_core.utils.utils import camel_case_to_snake_case
|
|
14
|
+
|
|
15
|
+
# External dependencies
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
router = APIRouter()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@router.get("/custom-node-schema", summary="Get a simple UI schema")
|
|
22
|
+
def get_simple_custom_object(flow_id: int, node_id: int):
|
|
23
|
+
"""
|
|
24
|
+
This endpoint returns a hardcoded JSON object that represents the UI
|
|
25
|
+
for our SimpleFilterNode.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
node = flow_file_handler.get_node(flow_id=flow_id, node_id=node_id)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
31
|
+
user_defined_node = CUSTOM_NODE_STORE.get(node.node_type)
|
|
32
|
+
|
|
33
|
+
if not user_defined_node:
|
|
34
|
+
raise HTTPException(status_code=404, detail=f"Node type '{node.node_type}' not found")
|
|
35
|
+
if node.is_setup:
|
|
36
|
+
settings = node.setting_input.settings
|
|
37
|
+
return user_defined_node.from_settings(settings).get_frontend_schema()
|
|
38
|
+
return user_defined_node().get_frontend_schema()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@router.post("/update_user_defined_node", tags=["transform"])
|
|
42
|
+
def update_user_defined_node(input_data: Dict[str, Any], node_type: str, current_user=Depends(get_current_active_user)):
|
|
43
|
+
input_data['user_id'] = current_user.id
|
|
44
|
+
node_type = camel_case_to_snake_case(node_type)
|
|
45
|
+
flow_id = int(input_data.get('flow_id'))
|
|
46
|
+
logger.info(f'Updating the data for flow: {flow_id}, node {input_data["node_id"]}')
|
|
47
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
48
|
+
user_defined_model = CUSTOM_NODE_STORE.get(node_type)
|
|
49
|
+
if not user_defined_model:
|
|
50
|
+
raise HTTPException(status_code=404, detail=f"Node type '{node_type}' not found")
|
|
51
|
+
|
|
52
|
+
user_defined_node_settings = input_schema.UserDefinedNode.model_validate(input_data)
|
|
53
|
+
initialized_model = user_defined_model.from_settings(user_defined_node_settings.settings)
|
|
54
|
+
|
|
55
|
+
flow.add_user_defined_node(custom_node=initialized_model, user_defined_node_settings=user_defined_node_settings)
|
|
@@ -136,11 +136,9 @@ class CloudStorageReadSettings(CloudStorageSettings):
|
|
|
136
136
|
|
|
137
137
|
scan_mode: Literal["single_file", "directory"] = "single_file"
|
|
138
138
|
file_format: Literal["csv", "parquet", "json", "delta", "iceberg"] = "parquet"
|
|
139
|
-
# CSV specific options
|
|
140
139
|
csv_has_header: Optional[bool] = True
|
|
141
140
|
csv_delimiter: Optional[str] = ","
|
|
142
141
|
csv_encoding: Optional[str] = "utf8"
|
|
143
|
-
# Deltalake specific settings
|
|
144
142
|
delta_version: Optional[int] = None
|
|
145
143
|
|
|
146
144
|
|