Flowfile 0.4.1__py3-none-any.whl → 0.5.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. build_backends/main.py +25 -22
  2. build_backends/main_prd.py +10 -19
  3. flowfile/__init__.py +179 -73
  4. flowfile/__main__.py +10 -7
  5. flowfile/api.py +52 -59
  6. flowfile/web/__init__.py +14 -9
  7. flowfile/web/static/assets/AdminView-49392a9a.js +713 -0
  8. flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
  9. flowfile/web/static/assets/CloudConnectionView-36bcd6df.css +72 -0
  10. flowfile/web/static/assets/{CloudConnectionManager-d3248f8d.js → CloudConnectionView-f13f202b.js} +11 -11
  11. flowfile/web/static/assets/{CloudStorageReader-d65bf041.js → CloudStorageReader-0023d4a5.js} +10 -8
  12. flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
  13. flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
  14. flowfile/web/static/assets/{CloudStorageWriter-e83be3ed.js → CloudStorageWriter-8e781e11.js} +10 -8
  15. flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
  16. flowfile/web/static/assets/{ColumnSelector-cce661cf.js → ColumnSelector-8ad68ea9.js} +3 -5
  17. flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
  18. flowfile/web/static/assets/{ContextMenu-11a4652a.js → ContextMenu-31ee57f0.js} +3 -3
  19. flowfile/web/static/assets/{ContextMenu-160afb08.js → ContextMenu-69a74055.js} +3 -3
  20. flowfile/web/static/assets/{ContextMenu-cf18d2cc.js → ContextMenu-8e2051c6.js} +3 -3
  21. flowfile/web/static/assets/{ContextMenu-4c74eef1.css → ContextMenu-8ec1729e.css} +6 -6
  22. flowfile/web/static/assets/{ContextMenu-63cfa99b.css → ContextMenu-9b310c60.css} +6 -6
  23. flowfile/web/static/assets/{CrossJoin-d395d38c.js → CrossJoin-03df6938.js} +12 -10
  24. flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
  25. flowfile/web/static/assets/CustomNode-59e99a86.css +32 -0
  26. flowfile/web/static/assets/{CustomNode-b812dc0b.js → CustomNode-8479239b.js} +36 -24
  27. flowfile/web/static/assets/{DatabaseConnectionSettings-7000bf2c.js → DatabaseConnectionSettings-869e3efd.js} +5 -4
  28. flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-e91df89a.css} +13 -13
  29. flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-36898a00.css} +24 -24
  30. flowfile/web/static/assets/{DatabaseReader-4f035d0c.js → DatabaseReader-c58b9552.js} +25 -15
  31. flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
  32. flowfile/web/static/assets/{DatabaseManager-9662ec5b.js → DatabaseView-d26a9140.js} +11 -11
  33. flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
  34. flowfile/web/static/assets/{DatabaseWriter-f65dcd54.js → DatabaseWriter-4d05ddc7.js} +17 -10
  35. flowfile/web/static/assets/{designer-e3c150ec.css → DesignerView-a6d0ee84.css} +629 -538
  36. flowfile/web/static/assets/{designer-f3656d8c.js → DesignerView-e6f5c0e8.js} +1214 -3209
  37. flowfile/web/static/assets/{documentation-52b241e7.js → DocumentationView-2e78ef1b.js} +5 -5
  38. flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-fd46c656.css} +7 -7
  39. flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
  40. flowfile/web/static/assets/{ExploreData-94c43dfc.js → ExploreData-7b54caca.js} +18 -9
  41. flowfile/web/static/assets/{ExternalSource-ac04b3cc.js → ExternalSource-3fa399b2.js} +9 -7
  42. flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-47ab05a3.css} +17 -17
  43. flowfile/web/static/assets/Filter-7494ea97.css +48 -0
  44. flowfile/web/static/assets/Filter-8cbbdbf3.js +287 -0
  45. flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
  46. flowfile/web/static/assets/{Formula-71472193.js → Formula-aac42b1e.js} +13 -11
  47. flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
  48. flowfile/web/static/assets/{FuzzyMatch-b317f631.js → FuzzyMatch-cd9bbfca.js} +12 -10
  49. flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
  50. flowfile/web/static/assets/{GraphSolver-754a234f.js → GraphSolver-c7e6780e.js} +13 -11
  51. flowfile/web/static/assets/{GroupBy-6c6f9802.js → GroupBy-93c5d22b.js} +9 -7
  52. flowfile/web/static/assets/{GroupBy-b9505323.css → GroupBy-be7ac0bf.css} +10 -10
  53. flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
  54. flowfile/web/static/assets/{Join-a1b800be.js → Join-a19b2de2.js} +13 -11
  55. flowfile/web/static/assets/LoginView-0df4ed0a.js +134 -0
  56. flowfile/web/static/assets/LoginView-d325d632.css +172 -0
  57. flowfile/web/static/assets/ManualInput-3702e677.css +293 -0
  58. flowfile/web/static/assets/{ManualInput-a9640276.js → ManualInput-8d3374b2.js} +170 -116
  59. flowfile/web/static/assets/{MultiSelect-97213888.js → MultiSelect-ad1b6243.js} +2 -2
  60. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-6ffe088a.js → MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js} +1 -1
  61. flowfile/web/static/assets/NodeDesigner-40b647c9.js +2610 -0
  62. flowfile/web/static/assets/NodeDesigner-5f53be3f.css +1429 -0
  63. flowfile/web/static/assets/{NumericInput-e638088a.js → NumericInput-7100234c.js} +2 -2
  64. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-90eb2cba.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
  65. flowfile/web/static/assets/{Output-ddc9079f.css → Output-35e97000.css} +6 -6
  66. flowfile/web/static/assets/{Output-76750610.js → Output-f5efd2aa.js} +60 -38
  67. flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
  68. flowfile/web/static/assets/{Pivot-7814803f.js → Pivot-d981d23c.js} +11 -9
  69. flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
  70. flowfile/web/static/assets/{PivotValidation-f92137d2.js → PivotValidation-39386e95.js} +3 -3
  71. flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
  72. flowfile/web/static/assets/{PivotValidation-76dd431a.js → PivotValidation-63de1f73.js} +3 -3
  73. flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
  74. flowfile/web/static/assets/{PolarsCode-889c3008.js → PolarsCode-f9d69217.js} +18 -9
  75. flowfile/web/static/assets/PopOver-b22f049e.js +939 -0
  76. flowfile/web/static/assets/PopOver-d96599db.css +33 -0
  77. flowfile/web/static/assets/{Read-6b17491f.css → Read-36e7bd51.css} +12 -12
  78. flowfile/web/static/assets/{Read-637b72a7.js → Read-aec2e377.js} +83 -105
  79. flowfile/web/static/assets/{RecordCount-2b050c41.js → RecordCount-78ed6845.js} +6 -4
  80. flowfile/web/static/assets/{RecordId-81df7784.js → RecordId-2156e890.js} +8 -6
  81. flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
  82. flowfile/web/static/assets/{SQLQueryComponent-88dcfe53.js → SQLQueryComponent-48c72f5b.js} +3 -3
  83. flowfile/web/static/assets/{Sample-258ad2a9.js → Sample-1352ca74.js} +6 -4
  84. flowfile/web/static/assets/SecretSelector-22b5ff89.js +113 -0
  85. flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
  86. flowfile/web/static/assets/{SecretManager-2a2cb7e2.js → SecretsView-17df66ee.js} +35 -36
  87. flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
  88. flowfile/web/static/assets/{Select-850215fd.js → Select-0aee4c54.js} +9 -7
  89. flowfile/web/static/assets/{SettingsSection-55bae608.js → SettingsSection-0784e157.js} +3 -3
  90. flowfile/web/static/assets/{SettingsSection-71e6b7e3.css → SettingsSection-07fbbc39.css} +4 -4
  91. flowfile/web/static/assets/{SettingsSection-5c696bee.css → SettingsSection-26fe48d4.css} +4 -4
  92. flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
  93. flowfile/web/static/assets/{SettingsSection-0e8d9123.js → SettingsSection-cd341bb6.js} +3 -3
  94. flowfile/web/static/assets/{SettingsSection-29b4fa6b.js → SettingsSection-f2002a6d.js} +3 -3
  95. flowfile/web/static/assets/{SingleSelect-bebd408b.js → SingleSelect-460cc0ea.js} +2 -2
  96. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-6093741c.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
  97. flowfile/web/static/assets/{SliderInput-6a05ab61.js → SliderInput-5d926864.js} +7 -4
  98. flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
  99. flowfile/web/static/assets/{Sort-10ab48ed.js → Sort-3cdc971b.js} +9 -7
  100. flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
  101. flowfile/web/static/assets/{TextInput-df9d6259.js → TextInput-a2d0bfbd.js} +2 -2
  102. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-000e1178.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
  103. flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
  104. flowfile/web/static/assets/{TextToRows-6c2d93d8.js → TextToRows-918945f7.js} +11 -10
  105. flowfile/web/static/assets/{ToggleSwitch-0ff7ac52.js → ToggleSwitch-f0ef5196.js} +2 -2
  106. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-c6dc3029.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
  107. flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
  108. flowfile/web/static/assets/{UnavailableFields-1bab97cb.js → UnavailableFields-bdad6144.js} +4 -4
  109. flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
  110. flowfile/web/static/assets/{Union-b563478a.js → Union-e8ab8c86.js} +8 -6
  111. flowfile/web/static/assets/{Unique-f90db5db.js → Unique-8cd4f976.js} +13 -22
  112. flowfile/web/static/assets/{Sort-3643d625.css → Unique-9fb2f567.css} +10 -10
  113. flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-710a2948.css} +7 -7
  114. flowfile/web/static/assets/{Unpivot-bcb0025f.js → Unpivot-8da14095.js} +10 -8
  115. flowfile/web/static/assets/{UnpivotValidation-c4e73b04.js → UnpivotValidation-6f7d89ff.js} +3 -3
  116. flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
  117. flowfile/web/static/assets/{VueGraphicWalker-bb8535e2.js → VueGraphicWalker-3fb312e1.js} +4 -4
  118. flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
  119. flowfile/web/static/assets/{api-4c8e3822.js → api-24483f0d.js} +1 -1
  120. flowfile/web/static/assets/{api-2d6adc4f.js → api-8b81fa73.js} +1 -1
  121. flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
  122. flowfile/web/static/assets/{dropDown-1bca8a74.js → dropDown-ac0fda9d.js} +3 -3
  123. flowfile/web/static/assets/{fullEditor-2985687e.js → fullEditor-5497a84a.js} +11 -10
  124. flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-a0be62b3.css} +74 -62
  125. flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
  126. flowfile/web/static/assets/{genericNodeSettings-0476ba4e.js → genericNodeSettings-99014e1d.js} +5 -5
  127. flowfile/web/static/assets/index-07dda503.js +38 -0
  128. flowfile/web/static/assets/index-3ba44389.js +2696 -0
  129. flowfile/web/static/assets/{index-50508d4d.css → index-e6289dd0.css} +1945 -569
  130. flowfile/web/static/assets/{index-246f201c.js → index-fb6493ae.js} +41626 -40869
  131. flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
  132. flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
  133. flowfile/web/static/assets/{outputCsv-d686eeaf.js → outputCsv-8f8ba42d.js} +3 -3
  134. flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
  135. flowfile/web/static/assets/{outputExcel-8809ea2f.js → outputExcel-393f4fef.js} +3 -3
  136. flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
  137. flowfile/web/static/assets/{outputParquet-53ba645a.js → outputParquet-07c81f65.js} +4 -4
  138. flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
  139. flowfile/web/static/assets/{readCsv-053bf97b.js → readCsv-07f6d9ad.js} +21 -20
  140. flowfile/web/static/assets/{readCsv-bca3ed53.css → readCsv-3bfac4c3.css} +15 -15
  141. flowfile/web/static/assets/{readExcel-e1b381ea.css → readExcel-3db6b763.css} +13 -13
  142. flowfile/web/static/assets/{readExcel-ad531eab.js → readExcel-ed69bc8f.js} +10 -12
  143. flowfile/web/static/assets/{readParquet-cee068e2.css → readParquet-c5244ad5.css} +4 -4
  144. flowfile/web/static/assets/{readParquet-58e899a1.js → readParquet-e3ed4528.js} +4 -7
  145. flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
  146. flowfile/web/static/assets/{selectDynamic-b38de2ba.js → selectDynamic-80b92899.js} +5 -5
  147. flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
  148. flowfile/web/static/assets/{vue-codemirror.esm-db9b8936.js → vue-codemirror.esm-0965f39f.js} +31 -637
  149. flowfile/web/static/assets/{vue-content-loader.es-b5f3ac30.js → vue-content-loader.es-c506ad97.js} +1 -1
  150. flowfile/web/static/index.html +2 -2
  151. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +4 -4
  152. flowfile-0.5.3.dist-info/RECORD +402 -0
  153. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +1 -1
  154. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +1 -0
  155. flowfile_core/__init__.py +13 -3
  156. flowfile_core/auth/jwt.py +51 -16
  157. flowfile_core/auth/models.py +32 -7
  158. flowfile_core/auth/password.py +89 -0
  159. flowfile_core/auth/secrets.py +8 -6
  160. flowfile_core/configs/__init__.py +9 -7
  161. flowfile_core/configs/flow_logger.py +15 -14
  162. flowfile_core/configs/node_store/__init__.py +72 -4
  163. flowfile_core/configs/node_store/nodes.py +155 -172
  164. flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
  165. flowfile_core/configs/settings.py +28 -15
  166. flowfile_core/database/connection.py +7 -6
  167. flowfile_core/database/init_db.py +96 -2
  168. flowfile_core/database/models.py +3 -1
  169. flowfile_core/fileExplorer/__init__.py +17 -0
  170. flowfile_core/fileExplorer/funcs.py +123 -57
  171. flowfile_core/fileExplorer/utils.py +10 -11
  172. flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
  173. flowfile_core/flowfile/analytics/analytics_processor.py +27 -24
  174. flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
  175. flowfile_core/flowfile/analytics/utils.py +1 -1
  176. flowfile_core/flowfile/code_generator/code_generator.py +391 -279
  177. flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
  178. flowfile_core/flowfile/connection_manager/models.py +1 -1
  179. flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
  180. flowfile_core/flowfile/database_connection_manager/models.py +1 -1
  181. flowfile_core/flowfile/extensions.py +17 -12
  182. flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
  183. flowfile_core/flowfile/flow_data_engine/create/funcs.py +152 -103
  184. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +526 -477
  185. flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
  186. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
  187. flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
  188. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
  189. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
  190. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +43 -32
  191. flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
  192. flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
  193. flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +15 -11
  194. flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
  195. flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
  196. flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
  197. flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
  198. flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
  199. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
  200. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +360 -191
  201. flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
  202. flowfile_core/flowfile/flow_data_engine/utils.py +101 -67
  203. flowfile_core/flowfile/flow_graph.py +1011 -561
  204. flowfile_core/flowfile/flow_graph_utils.py +31 -49
  205. flowfile_core/flowfile/flow_node/flow_node.py +332 -232
  206. flowfile_core/flowfile/flow_node/models.py +54 -41
  207. flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
  208. flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
  209. flowfile_core/flowfile/handler.py +82 -32
  210. flowfile_core/flowfile/manage/compatibility_enhancements.py +493 -47
  211. flowfile_core/flowfile/manage/io_flowfile.py +391 -0
  212. flowfile_core/flowfile/node_designer/__init__.py +15 -13
  213. flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
  214. flowfile_core/flowfile/node_designer/custom_node.py +162 -36
  215. flowfile_core/flowfile/node_designer/ui_components.py +136 -35
  216. flowfile_core/flowfile/schema_callbacks.py +77 -54
  217. flowfile_core/flowfile/setting_generator/__init__.py +0 -1
  218. flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
  219. flowfile_core/flowfile/setting_generator/settings.py +72 -55
  220. flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
  221. flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
  222. flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
  223. flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
  224. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
  225. flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
  226. flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
  227. flowfile_core/flowfile/util/calculate_layout.py +9 -13
  228. flowfile_core/flowfile/util/execution_orderer.py +25 -17
  229. flowfile_core/flowfile/util/node_skipper.py +4 -4
  230. flowfile_core/flowfile/utils.py +19 -21
  231. flowfile_core/main.py +26 -19
  232. flowfile_core/routes/auth.py +284 -11
  233. flowfile_core/routes/cloud_connections.py +25 -25
  234. flowfile_core/routes/logs.py +21 -29
  235. flowfile_core/routes/public.py +3 -3
  236. flowfile_core/routes/routes.py +77 -43
  237. flowfile_core/routes/secrets.py +25 -27
  238. flowfile_core/routes/user_defined_components.py +483 -4
  239. flowfile_core/run_lock.py +0 -1
  240. flowfile_core/schemas/__init__.py +4 -6
  241. flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
  242. flowfile_core/schemas/cloud_storage_schemas.py +59 -55
  243. flowfile_core/schemas/input_schema.py +398 -154
  244. flowfile_core/schemas/output_model.py +50 -35
  245. flowfile_core/schemas/schemas.py +207 -67
  246. flowfile_core/schemas/transform_schema.py +1360 -435
  247. flowfile_core/schemas/yaml_types.py +117 -0
  248. flowfile_core/secret_manager/secret_manager.py +17 -13
  249. flowfile_core/{flowfile/node_designer/data_types.py → types.py} +33 -3
  250. flowfile_core/utils/arrow_reader.py +7 -6
  251. flowfile_core/utils/excel_file_manager.py +3 -3
  252. flowfile_core/utils/fileManager.py +7 -7
  253. flowfile_core/utils/fl_executor.py +8 -10
  254. flowfile_core/utils/utils.py +4 -4
  255. flowfile_core/utils/validate_setup.py +5 -4
  256. flowfile_frame/__init__.py +107 -50
  257. flowfile_frame/adapters.py +2 -9
  258. flowfile_frame/adding_expr.py +73 -32
  259. flowfile_frame/cloud_storage/frame_helpers.py +27 -23
  260. flowfile_frame/cloud_storage/secret_manager.py +12 -26
  261. flowfile_frame/config.py +2 -5
  262. flowfile_frame/expr.py +311 -218
  263. flowfile_frame/expr.pyi +160 -159
  264. flowfile_frame/expr_name.py +23 -23
  265. flowfile_frame/flow_frame.py +581 -489
  266. flowfile_frame/flow_frame.pyi +123 -104
  267. flowfile_frame/flow_frame_methods.py +236 -252
  268. flowfile_frame/group_frame.py +50 -20
  269. flowfile_frame/join.py +2 -2
  270. flowfile_frame/lazy.py +129 -87
  271. flowfile_frame/lazy_methods.py +83 -30
  272. flowfile_frame/list_name_space.py +55 -50
  273. flowfile_frame/selectors.py +148 -68
  274. flowfile_frame/series.py +9 -7
  275. flowfile_frame/utils.py +19 -21
  276. flowfile_worker/__init__.py +12 -4
  277. flowfile_worker/configs.py +11 -19
  278. flowfile_worker/create/__init__.py +14 -27
  279. flowfile_worker/create/funcs.py +143 -94
  280. flowfile_worker/create/models.py +139 -68
  281. flowfile_worker/create/pl_types.py +14 -15
  282. flowfile_worker/create/read_excel_tables.py +34 -41
  283. flowfile_worker/create/utils.py +22 -19
  284. flowfile_worker/external_sources/s3_source/main.py +18 -51
  285. flowfile_worker/external_sources/s3_source/models.py +34 -27
  286. flowfile_worker/external_sources/sql_source/main.py +8 -5
  287. flowfile_worker/external_sources/sql_source/models.py +13 -9
  288. flowfile_worker/flow_logger.py +10 -8
  289. flowfile_worker/funcs.py +214 -155
  290. flowfile_worker/main.py +11 -17
  291. flowfile_worker/models.py +35 -28
  292. flowfile_worker/process_manager.py +2 -3
  293. flowfile_worker/routes.py +121 -93
  294. flowfile_worker/secrets.py +9 -6
  295. flowfile_worker/spawner.py +80 -49
  296. flowfile_worker/utils.py +3 -2
  297. shared/__init__.py +2 -7
  298. shared/storage_config.py +25 -13
  299. test_utils/postgres/commands.py +3 -2
  300. test_utils/postgres/fixtures.py +9 -9
  301. test_utils/s3/commands.py +1 -1
  302. test_utils/s3/data_generator.py +3 -4
  303. test_utils/s3/demo_data_generator.py +4 -7
  304. test_utils/s3/fixtures.py +7 -5
  305. tools/migrate/README.md +56 -0
  306. tools/migrate/__init__.py +12 -0
  307. tools/migrate/__main__.py +118 -0
  308. tools/migrate/legacy_schemas.py +682 -0
  309. tools/migrate/migrate.py +610 -0
  310. tools/migrate/tests/__init__.py +0 -0
  311. tools/migrate/tests/conftest.py +21 -0
  312. tools/migrate/tests/test_migrate.py +622 -0
  313. tools/migrate/tests/test_migration_e2e.py +1009 -0
  314. tools/migrate/tests/test_node_migrations.py +843 -0
  315. flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
  316. flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
  317. flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
  318. flowfile/web/static/assets/Filter-812dcbca.js +0 -164
  319. flowfile/web/static/assets/Filter-f62091b3.css +0 -20
  320. flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
  321. flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
  322. flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
  323. flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
  324. flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
  325. flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
  326. flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
  327. flowfile/web/static/assets/secretApi-538058f3.js +0 -46
  328. flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
  329. flowfile-0.4.1.dist-info/RECORD +0 -376
  330. flowfile_core/flowfile/manage/open_flowfile.py +0 -143
  331. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
  332. /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
@@ -1,70 +1,516 @@
1
- from flowfile_core.schemas import schemas, input_schema
1
+ """
2
+ Compatibility enhancements for opening old flowfile versions.
3
+ Migrates old schema structures to new ones during file load.
4
+ """
5
+
6
+ import pickle
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from flowfile_core.schemas import input_schema, schemas
11
+ from tools.migrate.legacy_schemas import LEGACY_CLASS_MAP
12
+
13
+ # =============================================================================
14
+ # LEGACY PICKLE LOADING
15
+ # =============================================================================
16
+
17
+
18
+ class LegacyUnpickler(pickle.Unpickler):
19
+ """
20
+ Custom unpickler that redirects class lookups to legacy dataclass definitions.
21
+
22
+ When loading old .flowfile pickles, transform_schema classes were dataclasses.
23
+ Now they're Pydantic BaseModels. This unpickler intercepts those classes and
24
+ loads them as the legacy dataclass versions, which can then be migrated.
25
+ """
26
+
27
+ def find_class(self, module: str, name: str):
28
+ """Override to redirect transform_schema dataclasses to legacy definitions."""
29
+ if name in LEGACY_CLASS_MAP:
30
+ return LEGACY_CLASS_MAP[name]
31
+ return super().find_class(module, name)
32
+
33
+
34
+ def load_flowfile_pickle(path: str) -> Any:
35
+ """
36
+ Load a flowfile pickle using legacy-compatible unpickling.
37
+
38
+ This handles old flowfiles where transform_schema classes were dataclasses
39
+ by loading them as legacy dataclass instances, which can then be migrated
40
+ to the new Pydantic BaseModel versions.
41
+
42
+ Args:
43
+ path: Path to the .flowfile pickle
44
+
45
+ Returns:
46
+ The deserialized FlowInformation object
47
+ """
48
+ resolved_path = Path(path).resolve()
49
+ with open(resolved_path, "rb") as f:
50
+ return LegacyUnpickler(f).load()
51
+
52
+
53
+ # =============================================================================
54
+ # DATACLASS DETECTION AND MIGRATION
55
+ # =============================================================================
56
+
57
+
58
+ def _is_dataclass_instance(obj: Any) -> bool:
59
+ """Check if an object is a dataclass instance (not a Pydantic model)."""
60
+ return hasattr(obj, "__dataclass_fields__") and not hasattr(obj, "model_dump")
61
+
62
+
63
+ def _migrate_dataclass_to_basemodel(obj: Any, model_class: type) -> Any:
64
+ """Convert a dataclass instance to a Pydantic BaseModel instance."""
65
+ if obj is None:
66
+ return None
67
+
68
+ if not _is_dataclass_instance(obj):
69
+ return obj # Already a BaseModel or dict
70
+
71
+ from dataclasses import asdict, fields
72
+
73
+ try:
74
+ data = asdict(obj)
75
+ except Exception:
76
+ # Fallback: manually extract attributes
77
+ data = {f.name: getattr(obj, f.name, None) for f in fields(obj)}
78
+
79
+ return model_class.model_validate(data)
80
+
81
+
82
+ # =============================================================================
83
+ # NODE-SPECIFIC COMPATIBILITY FUNCTIONS
84
+ # =============================================================================
2
85
 
3
86
 
4
87
  def ensure_compatibility_node_read(node_read: input_schema.NodeRead):
5
- if hasattr(node_read, 'received_file'):
6
- if not hasattr(node_read.received_file, 'fields'):
7
- print('setting fields')
8
- setattr(node_read.received_file, 'fields', [])
88
+ """Migrate old NodeRead/ReceivedTable structure to new table_settings format."""
89
+ if not hasattr(node_read, "received_file") or node_read.received_file is None:
90
+ return
91
+
92
+ received_file = node_read.received_file
93
+
94
+ # Ensure fields list exists
95
+ if not hasattr(received_file, "fields"):
96
+ received_file.fields = []
97
+
98
+ # Check if already migrated (has table_settings as proper object, not dict)
99
+ if hasattr(received_file, "table_settings") and received_file.table_settings is not None:
100
+ if not isinstance(received_file.table_settings, dict):
101
+ return
102
+
103
+ # Determine file_type - use existing or infer from attributes
104
+ file_type = getattr(received_file, "file_type", None)
105
+ if file_type is None:
106
+ path = getattr(received_file, "path", "") or ""
107
+ if path.endswith(".parquet"):
108
+ file_type = "parquet"
109
+ elif path.endswith((".xlsx", ".xls")):
110
+ file_type = "excel"
111
+ elif path.endswith(".json"):
112
+ file_type = "json"
113
+ else:
114
+ file_type = "csv"
115
+
116
+ # Build table_settings based on file_type, extracting old flat attributes
117
+ table_settings_dict = _build_input_table_settings(received_file, file_type)
118
+
119
+ # Re-validate the entire ReceivedTable to get proper Pydantic model
120
+ received_file_dict = received_file.model_dump()
121
+ received_file_dict["file_type"] = file_type
122
+ received_file_dict["table_settings"] = table_settings_dict
123
+
124
+ # Create new validated ReceivedTable and replace
125
+ new_received_file = input_schema.ReceivedTable.model_validate(received_file_dict)
126
+ node_read.received_file = new_received_file
127
+
128
+
129
+ def _build_input_table_settings(received_file: Any, file_type: str) -> dict:
130
+ """Build appropriate table_settings dict from old flat attributes."""
131
+
132
+ if file_type == "csv":
133
+ return {
134
+ "file_type": "csv",
135
+ "reference": getattr(received_file, "reference", ""),
136
+ "starting_from_line": getattr(received_file, "starting_from_line", 0),
137
+ "delimiter": getattr(received_file, "delimiter", ","),
138
+ "has_headers": getattr(received_file, "has_headers", True),
139
+ "encoding": getattr(received_file, "encoding", "utf-8"),
140
+ "parquet_ref": getattr(received_file, "parquet_ref", None),
141
+ "row_delimiter": getattr(received_file, "row_delimiter", "\n"),
142
+ "quote_char": getattr(received_file, "quote_char", '"'),
143
+ "infer_schema_length": getattr(received_file, "infer_schema_length", 10_000),
144
+ "truncate_ragged_lines": getattr(received_file, "truncate_ragged_lines", False),
145
+ "ignore_errors": getattr(received_file, "ignore_errors", False),
146
+ }
147
+
148
+ elif file_type == "json":
149
+ return {
150
+ "file_type": "json",
151
+ "reference": getattr(received_file, "reference", ""),
152
+ "starting_from_line": getattr(received_file, "starting_from_line", 0),
153
+ "delimiter": getattr(received_file, "delimiter", ","),
154
+ "has_headers": getattr(received_file, "has_headers", True),
155
+ "encoding": getattr(received_file, "encoding", "utf-8"),
156
+ "parquet_ref": getattr(received_file, "parquet_ref", None),
157
+ "row_delimiter": getattr(received_file, "row_delimiter", "\n"),
158
+ "quote_char": getattr(received_file, "quote_char", '"'),
159
+ "infer_schema_length": getattr(received_file, "infer_schema_length", 10_000),
160
+ "truncate_ragged_lines": getattr(received_file, "truncate_ragged_lines", False),
161
+ "ignore_errors": getattr(received_file, "ignore_errors", False),
162
+ }
163
+
164
+ elif file_type == "parquet":
165
+ return {"file_type": "parquet"}
166
+
167
+ elif file_type == "excel":
168
+ return {
169
+ "file_type": "excel",
170
+ "sheet_name": getattr(received_file, "sheet_name", None),
171
+ "start_row": getattr(received_file, "start_row", 0),
172
+ "start_column": getattr(received_file, "start_column", 0),
173
+ "end_row": getattr(received_file, "end_row", 0),
174
+ "end_column": getattr(received_file, "end_column", 0),
175
+ "has_headers": getattr(received_file, "has_headers", True),
176
+ "type_inference": getattr(received_file, "type_inference", False),
177
+ }
178
+
179
+ # Default to csv settings
180
+ return {"file_type": "csv", "delimiter": ",", "encoding": "utf-8", "has_headers": True}
9
181
 
10
182
 
11
183
  def ensure_compatibility_node_output(node_output: input_schema.NodeOutput):
12
- if hasattr(node_output, 'output_settings'):
13
- if not hasattr(node_output.output_settings, 'abs_file_path'):
14
- new_output_settings = input_schema.OutputSettings.model_validate(node_output.output_settings.model_dump())
15
- setattr(node_output, 'output_settings', new_output_settings)
184
+ """Migrate old OutputSettings structure to new table_settings format."""
185
+ if not hasattr(node_output, "output_settings") or node_output.output_settings is None:
186
+ return
187
+
188
+ output_settings = node_output.output_settings
189
+
190
+ # Check if already migrated (has table_settings as proper object, not dict)
191
+ if hasattr(output_settings, "table_settings") and output_settings.table_settings is not None:
192
+ if not isinstance(output_settings.table_settings, dict):
193
+ return
194
+
195
+ # Migrate from old separate fields to new table_settings
196
+ file_type = getattr(output_settings, "file_type", "csv")
197
+ table_settings_dict = _build_output_table_settings(output_settings, file_type)
198
+
199
+ # Re-validate the entire OutputSettings to get proper Pydantic model
200
+ output_settings_dict = output_settings.model_dump()
201
+ output_settings_dict["table_settings"] = table_settings_dict
202
+
203
+ # Remove old fields if they exist
204
+ for old_field in ["output_csv_table", "output_parquet_table", "output_excel_table"]:
205
+ output_settings_dict.pop(old_field, None)
206
+
207
+ # Create new validated OutputSettings and replace
208
+ new_output_settings = input_schema.OutputSettings.model_validate(output_settings_dict)
209
+ node_output.output_settings = new_output_settings
210
+
211
+
212
+ def _build_output_table_settings(output_settings: Any, file_type: str) -> dict:
213
+ """Build appropriate output table_settings from old separate table fields."""
214
+
215
+ if file_type == "csv":
216
+ old_csv = getattr(output_settings, "output_csv_table", None)
217
+ if old_csv is not None:
218
+ return {
219
+ "file_type": "csv",
220
+ "delimiter": getattr(old_csv, "delimiter", ","),
221
+ "encoding": getattr(old_csv, "encoding", "utf-8"),
222
+ }
223
+ return {"file_type": "csv", "delimiter": ",", "encoding": "utf-8"}
224
+
225
+ elif file_type == "parquet":
226
+ return {"file_type": "parquet"}
227
+
228
+ elif file_type == "excel":
229
+ old_excel = getattr(output_settings, "output_excel_table", None)
230
+ if old_excel is not None:
231
+ return {
232
+ "file_type": "excel",
233
+ "sheet_name": getattr(old_excel, "sheet_name", "Sheet1"),
234
+ }
235
+ return {"file_type": "excel", "sheet_name": "Sheet1"}
236
+
237
+ return {"file_type": "csv", "delimiter": ",", "encoding": "utf-8"}
238
+
239
+
240
+ def ensure_compatibility_node_groupby(node_groupby: input_schema.NodeGroupBy):
241
+ """Migrate old NodeGroupBy structure:
242
+ - GroupByInput dataclass -> BaseModel
243
+ - AggColl dataclass -> BaseModel
244
+ """
245
+ if not hasattr(node_groupby, "groupby_input") or node_groupby.groupby_input is None:
246
+ return
247
+
248
+ groupby_input = node_groupby.groupby_input
249
+
250
+ # Check if already migrated (is a Pydantic model)
251
+ if not _is_dataclass_instance(groupby_input):
252
+ return
253
+
254
+ from flowfile_core.schemas import transform_schema
255
+
256
+ # Migrate each AggColl in agg_cols
257
+ agg_cols = getattr(groupby_input, "agg_cols", []) or []
258
+ new_agg_cols = []
259
+ for agg_col in agg_cols:
260
+ if _is_dataclass_instance(agg_col):
261
+ new_agg_col = _migrate_dataclass_to_basemodel(agg_col, transform_schema.AggColl)
262
+ new_agg_cols.append(new_agg_col)
263
+ else:
264
+ new_agg_cols.append(agg_col)
265
+
266
+ # Create new validated GroupByInput and replace
267
+ new_groupby_input = transform_schema.GroupByInput(agg_cols=new_agg_cols)
268
+ node_groupby.groupby_input = new_groupby_input
269
+
270
+
271
+ def ensure_compatibility_node_filter(node_filter: input_schema.NodeFilter):
272
+ """Migrate old NodeFilter structure:
273
+ - FilterInput dataclass -> BaseModel
274
+ - filter_type -> mode
275
+ - BasicFilter.filter_type -> BasicFilter.operator
276
+ - BasicFilter.filter_value -> BasicFilter.value
277
+ """
278
+ if not hasattr(node_filter, "filter_input") or node_filter.filter_input is None:
279
+ return
280
+
281
+ filter_input = node_filter.filter_input
282
+
283
+ # Check if already migrated (is a Pydantic model)
284
+ if not _is_dataclass_instance(filter_input):
285
+ return
286
+
287
+ from flowfile_core.schemas import transform_schema
288
+
289
+ # Build the new FilterInput data with field name mappings
290
+ filter_data = {
291
+ # filter_type -> mode
292
+ "mode": getattr(filter_input, "filter_type", "basic"),
293
+ "advanced_filter": getattr(filter_input, "advanced_filter", ""),
294
+ }
295
+
296
+ # Handle BasicFilter migration
297
+ basic_filter = getattr(filter_input, "basic_filter", None)
298
+ if basic_filter is not None:
299
+ if _is_dataclass_instance(basic_filter):
300
+ # Map old field names to new ones
301
+ basic_filter_data = {
302
+ "field": getattr(basic_filter, "field", ""),
303
+ # filter_type -> operator
304
+ "operator": getattr(basic_filter, "filter_type", "equals"),
305
+ # filter_value -> value
306
+ "value": getattr(basic_filter, "filter_value", ""),
307
+ }
308
+ filter_data["basic_filter"] = transform_schema.BasicFilter.model_validate(basic_filter_data)
309
+ else:
310
+ filter_data["basic_filter"] = basic_filter
311
+
312
+ # Create new validated FilterInput and replace
313
+ new_filter_input = transform_schema.FilterInput.model_validate(filter_data)
314
+ node_filter.filter_input = new_filter_input
16
315
 
17
316
 
18
317
  def ensure_compatibility_node_select(node_select: input_schema.NodeSelect):
19
- if hasattr(node_select, 'select_input'):
20
- if any(not hasattr(select_input, 'position') for select_input in node_select.select_input):
21
- for _index, select_input in enumerate(node_select.select_input):
22
- setattr(select_input, 'position', _index)
23
- if not hasattr(node_select, 'sorted_by'):
24
- setattr(node_select, 'sorted_by', 'none')
318
+ """Ensure NodeSelect has position attributes, sorted_by field, and handle dataclass migrations."""
319
+ if not hasattr(node_select, "select_input"):
320
+ return
321
+
322
+ # Handle dataclass -> BaseModel migration for select_input items
323
+ if node_select.select_input:
324
+ from flowfile_core.schemas import transform_schema
325
+
326
+ new_select_input = []
327
+ needs_migration = any(_is_dataclass_instance(si) for si in node_select.select_input)
328
+
329
+ if needs_migration:
330
+ for si in node_select.select_input:
331
+ if _is_dataclass_instance(si):
332
+ new_si = _migrate_dataclass_to_basemodel(si, transform_schema.SelectInput)
333
+ new_select_input.append(new_si)
334
+ else:
335
+ new_select_input.append(si)
336
+ node_select.select_input = new_select_input
337
+
338
+ # Ensure position attributes exist
339
+ if any(not hasattr(select_input, "position") for select_input in node_select.select_input):
340
+ for _index, select_input in enumerate(node_select.select_input):
341
+ select_input.position = _index
342
+
343
+ if not hasattr(node_select, "sorted_by"):
344
+ node_select.sorted_by = "none"
25
345
 
26
346
 
27
347
  def ensure_compatibility_node_joins(node_settings: input_schema.NodeFuzzyMatch | input_schema.NodeJoin):
28
- if any(not hasattr(r, 'position') for r in node_settings.join_input.right_select.renames):
29
- for _index, select_input in enumerate(node_settings.join_input.right_select.renames +
30
- node_settings.join_input.left_select.renames):
31
- setattr(select_input, 'position', _index)
348
+ """Ensure join nodes have position attributes on renames and handle dataclass migrations."""
349
+ if not hasattr(node_settings, "join_input") or node_settings.join_input is None:
350
+ return
351
+
352
+ join_input = node_settings.join_input
353
+
354
+ # Check if right_select and left_select exist
355
+ if not hasattr(join_input, "right_select") or not hasattr(join_input, "left_select"):
356
+ return
357
+
358
+ from flowfile_core.schemas import transform_schema
359
+
360
+ # Handle dataclass -> BaseModel migration for join_mapping
361
+ if hasattr(join_input, "join_mapping") and join_input.join_mapping:
362
+ new_mapping = []
363
+ for jm in join_input.join_mapping:
364
+ if _is_dataclass_instance(jm):
365
+ new_jm = _migrate_dataclass_to_basemodel(jm, transform_schema.JoinMap)
366
+ new_mapping.append(new_jm)
367
+ else:
368
+ new_mapping.append(jm)
369
+ join_input.join_mapping = new_mapping
370
+
371
+ # Handle dataclass -> BaseModel migration for renames in selects
372
+ for select_attr in ["right_select", "left_select"]:
373
+ select = getattr(join_input, select_attr, None)
374
+ if select is None:
375
+ continue
376
+
377
+ renames = getattr(select, "renames", []) or []
378
+ if renames and any(_is_dataclass_instance(r) for r in renames):
379
+ new_renames = []
380
+ for r in renames:
381
+ if _is_dataclass_instance(r):
382
+ new_r = _migrate_dataclass_to_basemodel(r, transform_schema.SelectInput)
383
+ new_renames.append(new_r)
384
+ else:
385
+ new_renames.append(r)
386
+ select.renames = new_renames
387
+
388
+ right_renames = getattr(join_input.right_select, "renames", []) or []
389
+ left_renames = getattr(join_input.left_select, "renames", []) or []
390
+
391
+ # Ensure position attributes exist
392
+ if any(not hasattr(r, "position") for r in right_renames + left_renames):
393
+ for _index, select_input in enumerate(right_renames + left_renames):
394
+ select_input.position = _index
32
395
 
33
396
 
34
397
  def ensure_description(node: input_schema.NodeBase):
35
- if not hasattr(node, 'description'):
36
- setattr(node, 'description', '')
398
+ """Ensure node has description field."""
399
+ if not hasattr(node, "description"):
400
+ node.description = ""
37
401
 
38
402
 
39
403
  def ensure_compatibility_node_polars(node_polars: input_schema.NodePolarsCode):
40
- if hasattr(node_polars, 'depending_on_id'):
41
- setattr(node_polars, 'depending_on_ids', [getattr(node_polars, 'depending_on_id')])
404
+ """Migrate old NodePolarsCode structure:
405
+ - depending_on_id (single) -> depending_on_ids (list)
406
+ - PolarsCodeInput from dataclass to BaseModel
407
+ """
408
+ # Handle depending_on_id -> depending_on_ids migration
409
+ if hasattr(node_polars, "depending_on_id"):
410
+ old_id = getattr(node_polars, "depending_on_id", None)
411
+ if not hasattr(node_polars, "depending_on_ids") or node_polars.depending_on_ids is None:
412
+ if old_id is not None:
413
+ node_polars.depending_on_ids = [old_id]
414
+ else:
415
+ node_polars.depending_on_ids = []
42
416
 
417
+ # Handle PolarsCodeInput dataclass -> BaseModel migration
418
+ if hasattr(node_polars, "polars_code_input") and node_polars.polars_code_input is not None:
419
+ polars_code_input = node_polars.polars_code_input
43
420
 
44
- def ensure_compatibility(flow_storage_obj: schemas.FlowInformation, flow_path: str):
45
- if not hasattr(flow_storage_obj, 'flow_settings'):
46
- flow_settings = schemas.FlowSettings(flow_id=flow_storage_obj.flow_id, path=flow_path,
47
- name=flow_storage_obj.flow_name)
48
- setattr(flow_storage_obj, 'flow_settings', flow_settings)
421
+ if _is_dataclass_instance(polars_code_input):
422
+ from flowfile_core.schemas import transform_schema
423
+
424
+ new_polars_code_input = _migrate_dataclass_to_basemodel(polars_code_input, transform_schema.PolarsCodeInput)
425
+ node_polars.polars_code_input = new_polars_code_input
426
+
427
+
428
+ # =============================================================================
429
+ # FLOW-LEVEL COMPATIBILITY
430
+ # =============================================================================
431
+
432
+
433
+ def ensure_flow_settings(flow_storage_obj: schemas.FlowInformation, flow_path: str):
434
+ """Ensure flow_settings exists and has all required fields."""
435
+ if not hasattr(flow_storage_obj, "flow_settings") or flow_storage_obj.flow_settings is None:
436
+ flow_settings = schemas.FlowSettings(
437
+ flow_id=flow_storage_obj.flow_id, path=flow_path, name=flow_storage_obj.flow_name
438
+ )
439
+ flow_storage_obj.flow_settings = flow_settings
49
440
  flow_storage_obj = schemas.FlowInformation.model_validate(flow_storage_obj)
50
- elif not hasattr(getattr(flow_storage_obj, 'flow_settings'), 'execution_location'):
51
- setattr(getattr(flow_storage_obj, 'flow_settings'), 'execution_location', "remote")
52
- elif not hasattr(flow_storage_obj.flow_settings, 'is_running'):
53
- setattr(flow_storage_obj.flow_settings, 'is_running', False)
54
- setattr(flow_storage_obj.flow_settings, 'is_canceled', False)
55
- if not hasattr(flow_storage_obj.flow_settings, 'show_detailed_progress'):
56
- setattr(flow_storage_obj.flow_settings, 'show_detailed_progress', True)
441
+ return flow_storage_obj
442
+
443
+ fs = flow_storage_obj.flow_settings
444
+ if not hasattr(fs, "execution_location"):
445
+ fs.execution_location = "remote"
446
+ elif fs.execution_location == "auto":
447
+ fs.execution_location = "remote"
448
+ if not hasattr(fs, "is_running"):
449
+ fs.is_running = False
450
+
451
+ if not hasattr(fs, "is_canceled"):
452
+ fs.is_canceled = False
453
+
454
+ if not hasattr(fs, "show_detailed_progress"):
455
+ fs.show_detailed_progress = True
456
+
457
+ return flow_storage_obj
458
+
459
+
460
+ # =============================================================================
461
+ # MAIN ENTRY POINT
462
+ # =============================================================================
463
+
464
+
465
+ def ensure_compatibility(flow_storage_obj: schemas.FlowInformation, flow_path: str):
466
+ """
467
+ Main compatibility function - migrates old flowfile schemas to current version.
468
+
469
+ Handles migrations for:
470
+ - FlowSettings structure
471
+ - NodeRead (ReceivedTable with table_settings)
472
+ - NodeOutput (OutputSettings with table_settings)
473
+ - NodeSelect (position attributes, dataclass -> BaseModel)
474
+ - NodeJoin/NodeFuzzyMatch (join input positions, dataclass -> BaseModel)
475
+ - NodePolarsCode (depending_on_ids, dataclass -> BaseModel)
476
+ - Node descriptions
477
+ """
478
+ flow_storage_obj = ensure_flow_settings(flow_storage_obj, flow_path)
57
479
  for _id, node_information in flow_storage_obj.data.items():
58
- if not hasattr(node_information, 'setting_input'):
480
+ if not hasattr(node_information, "setting_input") or node_information.setting_input is None:
59
481
  continue
60
- if node_information.setting_input.__class__.__name__ == 'NodeRead':
61
- ensure_compatibility_node_read(node_information.setting_input)
62
- elif node_information.setting_input.__class__.__name__ == 'NodeSelect':
63
- ensure_compatibility_node_select(node_information.setting_input)
64
- elif node_information.setting_input.__class__.__name__ == 'NodeOutput':
65
- ensure_compatibility_node_output(node_information.setting_input)
66
- elif node_information.setting_input.__class__.__name__ in ('NodeJoin', 'NodeFuzzyMatch'):
67
- ensure_compatibility_node_joins(node_information.setting_input)
68
- elif node_information.setting_input.__class__.__name__ == 'NodePolarsCode':
69
- ensure_compatibility_node_polars(node_information.setting_input)
70
- ensure_description(node_information.setting_input)
482
+
483
+ setting_input = node_information.setting_input
484
+ class_name = setting_input.__class__.__name__
485
+
486
+ if class_name == "NodeRead":
487
+ ensure_compatibility_node_read(setting_input)
488
+ elif class_name == "NodeSelect":
489
+ ensure_compatibility_node_select(setting_input)
490
+ elif class_name == "NodeOutput":
491
+ ensure_compatibility_node_output(setting_input)
492
+ elif class_name in ("NodeJoin", "NodeFuzzyMatch"):
493
+ ensure_compatibility_node_joins(setting_input)
494
+ elif class_name == "NodePolarsCode":
495
+ ensure_compatibility_node_polars(setting_input)
496
+ elif class_name == "NodeFilter":
497
+ ensure_compatibility_node_filter(setting_input)
498
+ elif class_name == "NodeGroupBy":
499
+ ensure_compatibility_node_groupby(setting_input)
500
+ ensure_description(setting_input)
501
+
502
+ return flow_storage_obj
503
+
504
+
505
+ def load_and_migrate_flowfile(flow_path: str) -> schemas.FlowInformation:
506
+ """
507
+ Convenience function: Load a flowfile and apply all compatibility migrations.
508
+
509
+ Args:
510
+ flow_path: Path to the .flowfile pickle
511
+
512
+ Returns:
513
+ Fully migrated FlowInformation object
514
+ """
515
+ flow_storage_obj = load_flowfile_pickle(flow_path)
516
+ return ensure_compatibility(flow_storage_obj, flow_path)