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
|
@@ -1,70 +1,433 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compatibility enhancements for opening old flowfile versions.
|
|
3
|
+
Migrates old schema structures to new ones during file load.
|
|
4
|
+
"""
|
|
5
|
+
import pickle
|
|
6
|
+
from typing import Any
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
1
9
|
from flowfile_core.schemas import schemas, input_schema
|
|
10
|
+
from tools.migrate.legacy_schemas import LEGACY_CLASS_MAP
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# LEGACY PICKLE LOADING
|
|
15
|
+
# =============================================================================
|
|
16
|
+
|
|
17
|
+
class LegacyUnpickler(pickle.Unpickler):
|
|
18
|
+
"""
|
|
19
|
+
Custom unpickler that redirects class lookups to legacy dataclass definitions.
|
|
20
|
+
|
|
21
|
+
When loading old .flowfile pickles, transform_schema classes were dataclasses.
|
|
22
|
+
Now they're Pydantic BaseModels. This unpickler intercepts those classes and
|
|
23
|
+
loads them as the legacy dataclass versions, which can then be migrated.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def find_class(self, module: str, name: str):
|
|
27
|
+
"""Override to redirect transform_schema dataclasses to legacy definitions."""
|
|
28
|
+
if name in LEGACY_CLASS_MAP:
|
|
29
|
+
return LEGACY_CLASS_MAP[name]
|
|
30
|
+
return super().find_class(module, name)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_flowfile_pickle(path: str) -> Any:
|
|
34
|
+
"""
|
|
35
|
+
Load a flowfile pickle using legacy-compatible unpickling.
|
|
36
|
+
|
|
37
|
+
This handles old flowfiles where transform_schema classes were dataclasses
|
|
38
|
+
by loading them as legacy dataclass instances, which can then be migrated
|
|
39
|
+
to the new Pydantic BaseModel versions.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
path: Path to the .flowfile pickle
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The deserialized FlowInformation object
|
|
46
|
+
"""
|
|
47
|
+
resolved_path = Path(path).resolve()
|
|
48
|
+
with open(resolved_path, 'rb') as f:
|
|
49
|
+
return LegacyUnpickler(f).load()
|
|
2
50
|
|
|
3
51
|
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# DATACLASS DETECTION AND MIGRATION
|
|
54
|
+
# =============================================================================
|
|
55
|
+
|
|
56
|
+
def _is_dataclass_instance(obj: Any) -> bool:
|
|
57
|
+
"""Check if an object is a dataclass instance (not a Pydantic model)."""
|
|
58
|
+
return hasattr(obj, '__dataclass_fields__') and not hasattr(obj, 'model_dump')
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _migrate_dataclass_to_basemodel(obj: Any, model_class: type) -> Any:
|
|
62
|
+
"""Convert a dataclass instance to a Pydantic BaseModel instance."""
|
|
63
|
+
if obj is None:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
if not _is_dataclass_instance(obj):
|
|
67
|
+
return obj # Already a BaseModel or dict
|
|
68
|
+
|
|
69
|
+
from dataclasses import fields, asdict
|
|
70
|
+
try:
|
|
71
|
+
data = asdict(obj)
|
|
72
|
+
except Exception:
|
|
73
|
+
# Fallback: manually extract attributes
|
|
74
|
+
data = {f.name: getattr(obj, f.name, None) for f in fields(obj)}
|
|
75
|
+
|
|
76
|
+
return model_class.model_validate(data)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# =============================================================================
|
|
80
|
+
# NODE-SPECIFIC COMPATIBILITY FUNCTIONS
|
|
81
|
+
# =============================================================================
|
|
82
|
+
|
|
4
83
|
def ensure_compatibility_node_read(node_read: input_schema.NodeRead):
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
84
|
+
"""Migrate old NodeRead/ReceivedTable structure to new table_settings format."""
|
|
85
|
+
if not hasattr(node_read, 'received_file') or node_read.received_file is None:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
received_file = node_read.received_file
|
|
89
|
+
|
|
90
|
+
# Ensure fields list exists
|
|
91
|
+
if not hasattr(received_file, 'fields'):
|
|
92
|
+
setattr(received_file, 'fields', [])
|
|
93
|
+
|
|
94
|
+
# Check if already migrated (has table_settings as proper object, not dict)
|
|
95
|
+
if hasattr(received_file, 'table_settings') and received_file.table_settings is not None:
|
|
96
|
+
if not isinstance(received_file.table_settings, dict):
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
# Determine file_type - use existing or infer from attributes
|
|
100
|
+
file_type = getattr(received_file, 'file_type', None)
|
|
101
|
+
if file_type is None:
|
|
102
|
+
path = getattr(received_file, 'path', '') or ''
|
|
103
|
+
if path.endswith('.parquet'):
|
|
104
|
+
file_type = 'parquet'
|
|
105
|
+
elif path.endswith(('.xlsx', '.xls')):
|
|
106
|
+
file_type = 'excel'
|
|
107
|
+
elif path.endswith('.json'):
|
|
108
|
+
file_type = 'json'
|
|
109
|
+
else:
|
|
110
|
+
file_type = 'csv'
|
|
111
|
+
|
|
112
|
+
# Build table_settings based on file_type, extracting old flat attributes
|
|
113
|
+
table_settings_dict = _build_input_table_settings(received_file, file_type)
|
|
114
|
+
|
|
115
|
+
# Re-validate the entire ReceivedTable to get proper Pydantic model
|
|
116
|
+
received_file_dict = received_file.model_dump()
|
|
117
|
+
received_file_dict['file_type'] = file_type
|
|
118
|
+
received_file_dict['table_settings'] = table_settings_dict
|
|
119
|
+
|
|
120
|
+
# Create new validated ReceivedTable and replace
|
|
121
|
+
new_received_file = input_schema.ReceivedTable.model_validate(received_file_dict)
|
|
122
|
+
node_read.received_file = new_received_file
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _build_input_table_settings(received_file: Any, file_type: str) -> dict:
|
|
126
|
+
"""Build appropriate table_settings dict from old flat attributes."""
|
|
127
|
+
|
|
128
|
+
if file_type == 'csv':
|
|
129
|
+
return {
|
|
130
|
+
'file_type': 'csv',
|
|
131
|
+
'reference': getattr(received_file, 'reference', ''),
|
|
132
|
+
'starting_from_line': getattr(received_file, 'starting_from_line', 0),
|
|
133
|
+
'delimiter': getattr(received_file, 'delimiter', ','),
|
|
134
|
+
'has_headers': getattr(received_file, 'has_headers', True),
|
|
135
|
+
'encoding': getattr(received_file, 'encoding', 'utf-8'),
|
|
136
|
+
'parquet_ref': getattr(received_file, 'parquet_ref', None),
|
|
137
|
+
'row_delimiter': getattr(received_file, 'row_delimiter', '\n'),
|
|
138
|
+
'quote_char': getattr(received_file, 'quote_char', '"'),
|
|
139
|
+
'infer_schema_length': getattr(received_file, 'infer_schema_length', 10_000),
|
|
140
|
+
'truncate_ragged_lines': getattr(received_file, 'truncate_ragged_lines', False),
|
|
141
|
+
'ignore_errors': getattr(received_file, 'ignore_errors', False),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
elif file_type == 'json':
|
|
145
|
+
return {
|
|
146
|
+
'file_type': 'json',
|
|
147
|
+
'reference': getattr(received_file, 'reference', ''),
|
|
148
|
+
'starting_from_line': getattr(received_file, 'starting_from_line', 0),
|
|
149
|
+
'delimiter': getattr(received_file, 'delimiter', ','),
|
|
150
|
+
'has_headers': getattr(received_file, 'has_headers', True),
|
|
151
|
+
'encoding': getattr(received_file, 'encoding', 'utf-8'),
|
|
152
|
+
'parquet_ref': getattr(received_file, 'parquet_ref', None),
|
|
153
|
+
'row_delimiter': getattr(received_file, 'row_delimiter', '\n'),
|
|
154
|
+
'quote_char': getattr(received_file, 'quote_char', '"'),
|
|
155
|
+
'infer_schema_length': getattr(received_file, 'infer_schema_length', 10_000),
|
|
156
|
+
'truncate_ragged_lines': getattr(received_file, 'truncate_ragged_lines', False),
|
|
157
|
+
'ignore_errors': getattr(received_file, 'ignore_errors', False),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
elif file_type == 'parquet':
|
|
161
|
+
return {'file_type': 'parquet'}
|
|
162
|
+
|
|
163
|
+
elif file_type == 'excel':
|
|
164
|
+
return {
|
|
165
|
+
'file_type': 'excel',
|
|
166
|
+
'sheet_name': getattr(received_file, 'sheet_name', None),
|
|
167
|
+
'start_row': getattr(received_file, 'start_row', 0),
|
|
168
|
+
'start_column': getattr(received_file, 'start_column', 0),
|
|
169
|
+
'end_row': getattr(received_file, 'end_row', 0),
|
|
170
|
+
'end_column': getattr(received_file, 'end_column', 0),
|
|
171
|
+
'has_headers': getattr(received_file, 'has_headers', True),
|
|
172
|
+
'type_inference': getattr(received_file, 'type_inference', False),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Default to csv settings
|
|
176
|
+
return {'file_type': 'csv', 'delimiter': ',', 'encoding': 'utf-8', 'has_headers': True}
|
|
9
177
|
|
|
10
178
|
|
|
11
179
|
def ensure_compatibility_node_output(node_output: input_schema.NodeOutput):
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
180
|
+
"""Migrate old OutputSettings structure to new table_settings format."""
|
|
181
|
+
if not hasattr(node_output, 'output_settings') or node_output.output_settings is None:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
output_settings = node_output.output_settings
|
|
185
|
+
|
|
186
|
+
# Check if already migrated (has table_settings as proper object, not dict)
|
|
187
|
+
if hasattr(output_settings, 'table_settings') and output_settings.table_settings is not None:
|
|
188
|
+
if not isinstance(output_settings.table_settings, dict):
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
# Migrate from old separate fields to new table_settings
|
|
192
|
+
file_type = getattr(output_settings, 'file_type', 'csv')
|
|
193
|
+
table_settings_dict = _build_output_table_settings(output_settings, file_type)
|
|
194
|
+
|
|
195
|
+
# Re-validate the entire OutputSettings to get proper Pydantic model
|
|
196
|
+
output_settings_dict = output_settings.model_dump()
|
|
197
|
+
output_settings_dict['table_settings'] = table_settings_dict
|
|
198
|
+
|
|
199
|
+
# Remove old fields if they exist
|
|
200
|
+
for old_field in ['output_csv_table', 'output_parquet_table', 'output_excel_table']:
|
|
201
|
+
output_settings_dict.pop(old_field, None)
|
|
202
|
+
|
|
203
|
+
# Create new validated OutputSettings and replace
|
|
204
|
+
new_output_settings = input_schema.OutputSettings.model_validate(output_settings_dict)
|
|
205
|
+
node_output.output_settings = new_output_settings
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _build_output_table_settings(output_settings: Any, file_type: str) -> dict:
|
|
209
|
+
"""Build appropriate output table_settings from old separate table fields."""
|
|
210
|
+
|
|
211
|
+
if file_type == 'csv':
|
|
212
|
+
old_csv = getattr(output_settings, 'output_csv_table', None)
|
|
213
|
+
if old_csv is not None:
|
|
214
|
+
return {
|
|
215
|
+
'file_type': 'csv',
|
|
216
|
+
'delimiter': getattr(old_csv, 'delimiter', ','),
|
|
217
|
+
'encoding': getattr(old_csv, 'encoding', 'utf-8'),
|
|
218
|
+
}
|
|
219
|
+
return {'file_type': 'csv', 'delimiter': ',', 'encoding': 'utf-8'}
|
|
220
|
+
|
|
221
|
+
elif file_type == 'parquet':
|
|
222
|
+
return {'file_type': 'parquet'}
|
|
223
|
+
|
|
224
|
+
elif file_type == 'excel':
|
|
225
|
+
old_excel = getattr(output_settings, 'output_excel_table', None)
|
|
226
|
+
if old_excel is not None:
|
|
227
|
+
return {
|
|
228
|
+
'file_type': 'excel',
|
|
229
|
+
'sheet_name': getattr(old_excel, 'sheet_name', 'Sheet1'),
|
|
230
|
+
}
|
|
231
|
+
return {'file_type': 'excel', 'sheet_name': 'Sheet1'}
|
|
232
|
+
|
|
233
|
+
return {'file_type': 'csv', 'delimiter': ',', 'encoding': 'utf-8'}
|
|
16
234
|
|
|
17
235
|
|
|
18
236
|
def ensure_compatibility_node_select(node_select: input_schema.NodeSelect):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
237
|
+
"""Ensure NodeSelect has position attributes, sorted_by field, and handle dataclass migrations."""
|
|
238
|
+
if not hasattr(node_select, 'select_input'):
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
# Handle dataclass -> BaseModel migration for select_input items
|
|
242
|
+
if node_select.select_input:
|
|
243
|
+
from flowfile_core.schemas import transform_schema
|
|
244
|
+
new_select_input = []
|
|
245
|
+
needs_migration = any(_is_dataclass_instance(si) for si in node_select.select_input)
|
|
246
|
+
|
|
247
|
+
if needs_migration:
|
|
248
|
+
for si in node_select.select_input:
|
|
249
|
+
if _is_dataclass_instance(si):
|
|
250
|
+
new_si = _migrate_dataclass_to_basemodel(si, transform_schema.SelectInput)
|
|
251
|
+
new_select_input.append(new_si)
|
|
252
|
+
else:
|
|
253
|
+
new_select_input.append(si)
|
|
254
|
+
node_select.select_input = new_select_input
|
|
255
|
+
|
|
256
|
+
# Ensure position attributes exist
|
|
257
|
+
if any(not hasattr(select_input, 'position') for select_input in node_select.select_input):
|
|
258
|
+
for _index, select_input in enumerate(node_select.select_input):
|
|
259
|
+
setattr(select_input, 'position', _index)
|
|
260
|
+
|
|
261
|
+
if not hasattr(node_select, 'sorted_by'):
|
|
262
|
+
setattr(node_select, 'sorted_by', 'none')
|
|
25
263
|
|
|
26
264
|
|
|
27
265
|
def ensure_compatibility_node_joins(node_settings: input_schema.NodeFuzzyMatch | input_schema.NodeJoin):
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
266
|
+
"""Ensure join nodes have position attributes on renames and handle dataclass migrations."""
|
|
267
|
+
if not hasattr(node_settings, 'join_input') or node_settings.join_input is None:
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
join_input = node_settings.join_input
|
|
271
|
+
|
|
272
|
+
# Check if right_select and left_select exist
|
|
273
|
+
if not hasattr(join_input, 'right_select') or not hasattr(join_input, 'left_select'):
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
from flowfile_core.schemas import transform_schema
|
|
277
|
+
|
|
278
|
+
# Handle dataclass -> BaseModel migration for join_mapping
|
|
279
|
+
if hasattr(join_input, 'join_mapping') and join_input.join_mapping:
|
|
280
|
+
new_mapping = []
|
|
281
|
+
for jm in join_input.join_mapping:
|
|
282
|
+
if _is_dataclass_instance(jm):
|
|
283
|
+
new_jm = _migrate_dataclass_to_basemodel(jm, transform_schema.JoinMap)
|
|
284
|
+
new_mapping.append(new_jm)
|
|
285
|
+
else:
|
|
286
|
+
new_mapping.append(jm)
|
|
287
|
+
join_input.join_mapping = new_mapping
|
|
288
|
+
|
|
289
|
+
# Handle dataclass -> BaseModel migration for renames in selects
|
|
290
|
+
for select_attr in ['right_select', 'left_select']:
|
|
291
|
+
select = getattr(join_input, select_attr, None)
|
|
292
|
+
if select is None:
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
renames = getattr(select, 'renames', []) or []
|
|
296
|
+
if renames and any(_is_dataclass_instance(r) for r in renames):
|
|
297
|
+
new_renames = []
|
|
298
|
+
for r in renames:
|
|
299
|
+
if _is_dataclass_instance(r):
|
|
300
|
+
new_r = _migrate_dataclass_to_basemodel(r, transform_schema.SelectInput)
|
|
301
|
+
new_renames.append(new_r)
|
|
302
|
+
else:
|
|
303
|
+
new_renames.append(r)
|
|
304
|
+
select.renames = new_renames
|
|
305
|
+
|
|
306
|
+
right_renames = getattr(join_input.right_select, 'renames', []) or []
|
|
307
|
+
left_renames = getattr(join_input.left_select, 'renames', []) or []
|
|
308
|
+
|
|
309
|
+
# Ensure position attributes exist
|
|
310
|
+
if any(not hasattr(r, 'position') for r in right_renames + left_renames):
|
|
311
|
+
for _index, select_input in enumerate(right_renames + left_renames):
|
|
31
312
|
setattr(select_input, 'position', _index)
|
|
32
313
|
|
|
33
314
|
|
|
34
315
|
def ensure_description(node: input_schema.NodeBase):
|
|
316
|
+
"""Ensure node has description field."""
|
|
35
317
|
if not hasattr(node, 'description'):
|
|
36
318
|
setattr(node, 'description', '')
|
|
37
319
|
|
|
38
320
|
|
|
39
321
|
def ensure_compatibility_node_polars(node_polars: input_schema.NodePolarsCode):
|
|
322
|
+
"""Migrate old NodePolarsCode structure:
|
|
323
|
+
- depending_on_id (single) -> depending_on_ids (list)
|
|
324
|
+
- PolarsCodeInput from dataclass to BaseModel
|
|
325
|
+
"""
|
|
326
|
+
# Handle depending_on_id -> depending_on_ids migration
|
|
40
327
|
if hasattr(node_polars, 'depending_on_id'):
|
|
41
|
-
|
|
328
|
+
old_id = getattr(node_polars, 'depending_on_id', None)
|
|
329
|
+
if not hasattr(node_polars, 'depending_on_ids') or node_polars.depending_on_ids is None:
|
|
330
|
+
if old_id is not None:
|
|
331
|
+
setattr(node_polars, 'depending_on_ids', [old_id])
|
|
332
|
+
else:
|
|
333
|
+
setattr(node_polars, 'depending_on_ids', [])
|
|
42
334
|
|
|
335
|
+
# Handle PolarsCodeInput dataclass -> BaseModel migration
|
|
336
|
+
if hasattr(node_polars, 'polars_code_input') and node_polars.polars_code_input is not None:
|
|
337
|
+
polars_code_input = node_polars.polars_code_input
|
|
43
338
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
339
|
+
if _is_dataclass_instance(polars_code_input):
|
|
340
|
+
from flowfile_core.schemas import transform_schema
|
|
341
|
+
new_polars_code_input = _migrate_dataclass_to_basemodel(
|
|
342
|
+
polars_code_input, transform_schema.PolarsCodeInput
|
|
343
|
+
)
|
|
344
|
+
node_polars.polars_code_input = new_polars_code_input
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# =============================================================================
|
|
348
|
+
# FLOW-LEVEL COMPATIBILITY
|
|
349
|
+
# =============================================================================
|
|
350
|
+
|
|
351
|
+
def ensure_flow_settings(flow_storage_obj: schemas.FlowInformation, flow_path: str):
|
|
352
|
+
"""Ensure flow_settings exists and has all required fields."""
|
|
353
|
+
if not hasattr(flow_storage_obj, 'flow_settings') or flow_storage_obj.flow_settings is None:
|
|
354
|
+
flow_settings = schemas.FlowSettings(
|
|
355
|
+
flow_id=flow_storage_obj.flow_id,
|
|
356
|
+
path=flow_path,
|
|
357
|
+
name=flow_storage_obj.flow_name
|
|
358
|
+
)
|
|
48
359
|
setattr(flow_storage_obj, 'flow_settings', flow_settings)
|
|
49
360
|
flow_storage_obj = schemas.FlowInformation.model_validate(flow_storage_obj)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
361
|
+
return flow_storage_obj
|
|
362
|
+
|
|
363
|
+
fs = flow_storage_obj.flow_settings
|
|
364
|
+
|
|
365
|
+
if not hasattr(fs, 'execution_location'):
|
|
366
|
+
setattr(fs, 'execution_location', "remote")
|
|
367
|
+
|
|
368
|
+
if not hasattr(fs, 'is_running'):
|
|
369
|
+
setattr(fs, 'is_running', False)
|
|
370
|
+
|
|
371
|
+
if not hasattr(fs, 'is_canceled'):
|
|
372
|
+
setattr(fs, 'is_canceled', False)
|
|
373
|
+
|
|
374
|
+
if not hasattr(fs, 'show_detailed_progress'):
|
|
375
|
+
setattr(fs, 'show_detailed_progress', True)
|
|
376
|
+
|
|
377
|
+
return flow_storage_obj
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# =============================================================================
|
|
381
|
+
# MAIN ENTRY POINT
|
|
382
|
+
# =============================================================================
|
|
383
|
+
|
|
384
|
+
def ensure_compatibility(flow_storage_obj: schemas.FlowInformation, flow_path: str):
|
|
385
|
+
"""
|
|
386
|
+
Main compatibility function - migrates old flowfile schemas to current version.
|
|
387
|
+
|
|
388
|
+
Handles migrations for:
|
|
389
|
+
- FlowSettings structure
|
|
390
|
+
- NodeRead (ReceivedTable with table_settings)
|
|
391
|
+
- NodeOutput (OutputSettings with table_settings)
|
|
392
|
+
- NodeSelect (position attributes, dataclass -> BaseModel)
|
|
393
|
+
- NodeJoin/NodeFuzzyMatch (join input positions, dataclass -> BaseModel)
|
|
394
|
+
- NodePolarsCode (depending_on_ids, dataclass -> BaseModel)
|
|
395
|
+
- Node descriptions
|
|
396
|
+
"""
|
|
397
|
+
flow_storage_obj = ensure_flow_settings(flow_storage_obj, flow_path)
|
|
398
|
+
|
|
57
399
|
for _id, node_information in flow_storage_obj.data.items():
|
|
58
|
-
if not hasattr(node_information, 'setting_input'):
|
|
400
|
+
if not hasattr(node_information, 'setting_input') or node_information.setting_input is None:
|
|
59
401
|
continue
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
elif
|
|
67
|
-
|
|
68
|
-
elif
|
|
69
|
-
|
|
70
|
-
|
|
402
|
+
|
|
403
|
+
setting_input = node_information.setting_input
|
|
404
|
+
class_name = setting_input.__class__.__name__
|
|
405
|
+
|
|
406
|
+
if class_name == 'NodeRead':
|
|
407
|
+
ensure_compatibility_node_read(setting_input)
|
|
408
|
+
elif class_name == 'NodeSelect':
|
|
409
|
+
ensure_compatibility_node_select(setting_input)
|
|
410
|
+
elif class_name == 'NodeOutput':
|
|
411
|
+
ensure_compatibility_node_output(setting_input)
|
|
412
|
+
elif class_name in ('NodeJoin', 'NodeFuzzyMatch'):
|
|
413
|
+
ensure_compatibility_node_joins(setting_input)
|
|
414
|
+
elif class_name == 'NodePolarsCode':
|
|
415
|
+
ensure_compatibility_node_polars(setting_input)
|
|
416
|
+
|
|
417
|
+
ensure_description(setting_input)
|
|
418
|
+
|
|
419
|
+
return flow_storage_obj
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def load_and_migrate_flowfile(flow_path: str) -> schemas.FlowInformation:
|
|
423
|
+
"""
|
|
424
|
+
Convenience function: Load a flowfile and apply all compatibility migrations.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
flow_path: Path to the .flowfile pickle
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Fully migrated FlowInformation object
|
|
431
|
+
"""
|
|
432
|
+
flow_storage_obj = load_flowfile_pickle(flow_path)
|
|
433
|
+
return ensure_compatibility(flow_storage_obj, flow_path)
|