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
@@ -0,0 +1,622 @@
1
+ """
2
+ Tests for schema compatibility and migration validation.
3
+
4
+ These tests verify that:
5
+ 1. Old flat ReceivedTable can be migrated to new nested table_settings
6
+ 2. Old separate OutputSettings tables can be migrated to unified table_settings
7
+ 3. All node types are handled correctly in migration
8
+ """
9
+
10
+ import json
11
+ import pickle
12
+ import tempfile
13
+ from pathlib import Path
14
+
15
+ import pytest
16
+
17
+ # =============================================================================
18
+ # FIXTURES
19
+ # =============================================================================
20
+
21
+ @pytest.fixture
22
+ def temp_dir():
23
+ """Create a temporary directory for test files."""
24
+ with tempfile.TemporaryDirectory() as tmpdir:
25
+ yield Path(tmpdir)
26
+
27
+
28
+ # =============================================================================
29
+ # OLD -> NEW SCHEMA TRANSFORMATION TESTS
30
+ # =============================================================================
31
+
32
+ class TestReceivedTableTransformation:
33
+ """Test transformation of OLD flat ReceivedTable to NEW nested table_settings."""
34
+
35
+ def test_csv_flat_to_nested(self, temp_dir):
36
+ """Test that flat CSV fields become nested in table_settings."""
37
+ from tools.migrate.legacy_schemas import FlowInformation, FlowSettings, NodeInformation, NodeRead, ReceivedTable
38
+ from tools.migrate.migrate import migrate_flowfile
39
+
40
+ # OLD format: flat fields
41
+ received = ReceivedTable(
42
+ name='data.csv',
43
+ path='/path/to/data.csv',
44
+ file_type='csv',
45
+ delimiter=';',
46
+ encoding='latin-1',
47
+ has_headers=True,
48
+ starting_from_line=1,
49
+ infer_schema_length=5000,
50
+ quote_char="'",
51
+ row_delimiter='\n',
52
+ truncate_ragged_lines=True,
53
+ ignore_errors=True,
54
+ )
55
+
56
+ node = NodeRead(flow_id=1, node_id=1, received_file=received)
57
+ flow = FlowInformation(
58
+ flow_id=1,
59
+ flow_name='test',
60
+ flow_settings=FlowSettings(flow_id=1, name='test'),
61
+ data={1: NodeInformation(id=1, type='read', setting_input=node)},
62
+ node_starts=[1],
63
+ node_connections=[],
64
+ )
65
+
66
+ pickle_path = temp_dir / 'test.flowfile'
67
+ with open(pickle_path, 'wb') as f:
68
+ pickle.dump(flow, f)
69
+
70
+ output_path = migrate_flowfile(pickle_path, format='json')
71
+
72
+ with open(output_path) as f:
73
+ data = json.load(f)
74
+
75
+ read_node = data['nodes'][0]
76
+ received_file = read_node['setting_input']['received_file']
77
+
78
+ # Verify NEW structure has table_settings
79
+ assert 'table_settings' in received_file
80
+ ts = received_file['table_settings']
81
+
82
+ # Verify values migrated correctly
83
+ assert ts['file_type'] == 'csv'
84
+ assert ts['delimiter'] == ';'
85
+ assert ts['encoding'] == 'latin-1'
86
+ assert ts['has_headers'] == True
87
+ assert ts['starting_from_line'] == 1
88
+ assert ts['infer_schema_length'] == 5000
89
+ assert ts['quote_char'] == "'"
90
+ assert ts['truncate_ragged_lines'] == True
91
+ assert ts['ignore_errors'] == True
92
+
93
+ def test_excel_flat_to_nested(self, temp_dir):
94
+ """Test that flat Excel fields become nested in table_settings."""
95
+ from tools.migrate.legacy_schemas import FlowInformation, FlowSettings, NodeInformation, NodeRead, ReceivedTable
96
+ from tools.migrate.migrate import migrate_flowfile
97
+
98
+ # OLD format: flat fields
99
+ received = ReceivedTable(
100
+ name='data.xlsx',
101
+ path='/path/to/data.xlsx',
102
+ file_type='excel',
103
+ sheet_name='Sales Data',
104
+ start_row=2,
105
+ start_column=1,
106
+ end_row=100,
107
+ end_column=10,
108
+ has_headers=True,
109
+ type_inference=True,
110
+ )
111
+
112
+ node = NodeRead(flow_id=1, node_id=1, received_file=received)
113
+ flow = FlowInformation(
114
+ flow_id=1,
115
+ flow_name='test',
116
+ flow_settings=FlowSettings(flow_id=1, name='test'),
117
+ data={1: NodeInformation(id=1, type='read', setting_input=node)},
118
+ node_starts=[1],
119
+ node_connections=[],
120
+ )
121
+
122
+ pickle_path = temp_dir / 'test.flowfile'
123
+ with open(pickle_path, 'wb') as f:
124
+ pickle.dump(flow, f)
125
+
126
+ output_path = migrate_flowfile(pickle_path, format='json')
127
+
128
+ with open(output_path) as f:
129
+ data = json.load(f)
130
+
131
+ read_node = data['nodes'][0]
132
+ received_file = read_node['setting_input']['received_file']
133
+
134
+ # Verify NEW structure
135
+ assert received_file['file_type'] == 'excel'
136
+ assert 'table_settings' in received_file
137
+ ts = received_file['table_settings']
138
+
139
+ assert ts['file_type'] == 'excel'
140
+ assert ts['sheet_name'] == 'Sales Data'
141
+ assert ts['start_row'] == 2
142
+ assert ts['start_column'] == 1
143
+ assert ts['end_row'] == 100
144
+ assert ts['end_column'] == 10
145
+ assert ts['has_headers'] == True
146
+ assert ts['type_inference'] == True
147
+
148
+ def test_parquet_flat_to_nested(self, temp_dir):
149
+ """Test that parquet file type gets table_settings."""
150
+ from tools.migrate.legacy_schemas import FlowInformation, FlowSettings, NodeInformation, NodeRead, ReceivedTable
151
+ from tools.migrate.migrate import migrate_flowfile
152
+
153
+ received = ReceivedTable(
154
+ name='data.parquet',
155
+ path='/path/to/data.parquet',
156
+ file_type='parquet',
157
+ )
158
+
159
+ node = NodeRead(flow_id=1, node_id=1, received_file=received)
160
+ flow = FlowInformation(
161
+ flow_id=1,
162
+ flow_name='test',
163
+ flow_settings=FlowSettings(flow_id=1, name='test'),
164
+ data={1: NodeInformation(id=1, type='read', setting_input=node)},
165
+ node_starts=[1],
166
+ node_connections=[],
167
+ )
168
+
169
+ pickle_path = temp_dir / 'test.flowfile'
170
+ with open(pickle_path, 'wb') as f:
171
+ pickle.dump(flow, f)
172
+
173
+ output_path = migrate_flowfile(pickle_path, format='json')
174
+
175
+ with open(output_path) as f:
176
+ data = json.load(f)
177
+
178
+ read_node = data['nodes'][0]
179
+ received_file = read_node['setting_input']['received_file']
180
+
181
+ assert received_file['file_type'] == 'parquet'
182
+ assert 'table_settings' in received_file
183
+ assert received_file['table_settings']['file_type'] == 'parquet'
184
+
185
+
186
+ class TestOutputSettingsTransformation:
187
+ """Test transformation of OLD separate output tables to NEW unified table_settings."""
188
+
189
+ def test_csv_output_consolidation(self, temp_dir):
190
+ """Test that separate output_csv_table becomes table_settings."""
191
+ from tools.migrate.legacy_schemas import (
192
+ FlowInformation,
193
+ FlowSettings,
194
+ NodeInformation,
195
+ NodeOutput,
196
+ OutputCsvTable,
197
+ OutputSettings,
198
+ )
199
+ from tools.migrate.migrate import migrate_flowfile
200
+
201
+ # OLD format: separate table objects
202
+ output_settings = OutputSettings(
203
+ name='result.csv',
204
+ directory='/output',
205
+ file_type='csv',
206
+ write_mode='overwrite',
207
+ output_csv_table=OutputCsvTable(delimiter='|', encoding='utf-16'),
208
+ )
209
+
210
+ node = NodeOutput(flow_id=1, node_id=1, output_settings=output_settings)
211
+ flow = FlowInformation(
212
+ flow_id=1,
213
+ flow_name='test',
214
+ flow_settings=FlowSettings(flow_id=1, name='test'),
215
+ data={1: NodeInformation(id=1, type='output', setting_input=node)},
216
+ node_starts=[1],
217
+ node_connections=[],
218
+ )
219
+
220
+ pickle_path = temp_dir / 'test.flowfile'
221
+ with open(pickle_path, 'wb') as f:
222
+ pickle.dump(flow, f)
223
+
224
+ output_path = migrate_flowfile(pickle_path, format='json')
225
+
226
+ with open(output_path) as f:
227
+ data = json.load(f)
228
+
229
+ output_node = data['nodes'][0]
230
+ os = output_node['setting_input']['output_settings']
231
+
232
+ # Verify NEW structure
233
+ assert 'table_settings' in os
234
+ assert os['table_settings']['file_type'] == 'csv'
235
+ assert os['table_settings']['delimiter'] == '|'
236
+ assert os['table_settings']['encoding'] == 'utf-16'
237
+
238
+ # Verify OLD fields removed
239
+ assert 'output_csv_table' not in os
240
+ assert 'output_parquet_table' not in os
241
+ assert 'output_excel_table' not in os
242
+
243
+ def test_excel_output_consolidation(self, temp_dir):
244
+ """Test that separate output_excel_table becomes table_settings."""
245
+ from tools.migrate.legacy_schemas import (
246
+ FlowInformation,
247
+ FlowSettings,
248
+ NodeInformation,
249
+ NodeOutput,
250
+ OutputExcelTable,
251
+ OutputSettings,
252
+ )
253
+ from tools.migrate.migrate import migrate_flowfile
254
+
255
+ output_settings = OutputSettings(
256
+ name='result.xlsx',
257
+ directory='/output',
258
+ file_type='excel',
259
+ output_excel_table=OutputExcelTable(sheet_name='Results'),
260
+ )
261
+
262
+ node = NodeOutput(flow_id=1, node_id=1, output_settings=output_settings)
263
+ flow = FlowInformation(
264
+ flow_id=1,
265
+ flow_name='test',
266
+ flow_settings=FlowSettings(flow_id=1, name='test'),
267
+ data={1: NodeInformation(id=1, type='output', setting_input=node)},
268
+ node_starts=[1],
269
+ node_connections=[],
270
+ )
271
+
272
+ pickle_path = temp_dir / 'test.flowfile'
273
+ with open(pickle_path, 'wb') as f:
274
+ pickle.dump(flow, f)
275
+
276
+ output_path = migrate_flowfile(pickle_path, format='json')
277
+
278
+ with open(output_path) as f:
279
+ data = json.load(f)
280
+
281
+ output_node = data['nodes'][0]
282
+ os = output_node['setting_input']['output_settings']
283
+
284
+ assert os['table_settings']['file_type'] == 'excel'
285
+ assert os['table_settings']['sheet_name'] == 'Results'
286
+
287
+
288
+ # =============================================================================
289
+ # NODE TYPE MIGRATION TESTS
290
+ # =============================================================================
291
+
292
+ class TestNodeTypeMigration:
293
+ """Test that all node types can be migrated correctly."""
294
+
295
+ def _create_and_migrate(self, temp_dir, node_type: str, setting_input) -> dict:
296
+ """Helper to create a flow with one node and migrate it."""
297
+ from tools.migrate.legacy_schemas import FlowInformation, FlowSettings, NodeInformation
298
+ from tools.migrate.migrate import migrate_flowfile
299
+
300
+ flow = FlowInformation(
301
+ flow_id=1,
302
+ flow_name='test',
303
+ flow_settings=FlowSettings(flow_id=1, name='test'),
304
+ data={1: NodeInformation(id=1, type=node_type, setting_input=setting_input)},
305
+ node_starts=[1],
306
+ node_connections=[],
307
+ )
308
+
309
+ pickle_path = temp_dir / 'test.flowfile'
310
+ with open(pickle_path, 'wb') as f:
311
+ pickle.dump(flow, f)
312
+
313
+ output_path = migrate_flowfile(pickle_path, format='json')
314
+
315
+ with open(output_path) as f:
316
+ return json.load(f)
317
+
318
+ def test_migrate_select_node(self, temp_dir):
319
+ """Test select node migration."""
320
+ from tools.migrate.legacy_schemas import NodeSelect, SelectInput
321
+
322
+ node = NodeSelect(
323
+ flow_id=1,
324
+ node_id=1,
325
+ select_input=[
326
+ SelectInput(old_name='a', new_name='b', keep=True),
327
+ SelectInput(old_name='c', keep=False),
328
+ ]
329
+ )
330
+
331
+ data = self._create_and_migrate(temp_dir, 'select', node)
332
+ assert data['nodes'][0]['type'] == 'select'
333
+ assert 'select_input' in data['nodes'][0]['setting_input']
334
+
335
+ def test_migrate_filter_node(self, temp_dir):
336
+ """Test filter node migration."""
337
+ from tools.migrate.legacy_schemas import BasicFilter, FilterInput, NodeFilter
338
+
339
+ node = NodeFilter(
340
+ flow_id=1,
341
+ node_id=1,
342
+ filter_input=FilterInput(
343
+ filter_type='basic',
344
+ basic_filter=BasicFilter(field='x', filter_type='>', filter_value='5')
345
+ )
346
+ )
347
+
348
+ data = self._create_and_migrate(temp_dir, 'filter', node)
349
+ assert data['nodes'][0]['type'] == 'filter'
350
+ assert 'filter_input' in data['nodes'][0]['setting_input']
351
+
352
+ def test_migrate_formula_node(self, temp_dir):
353
+ """Test formula node migration."""
354
+ from tools.migrate.legacy_schemas import FieldInput, FunctionInput, NodeFormula
355
+
356
+ node = NodeFormula(
357
+ flow_id=1,
358
+ node_id=1,
359
+ function=FunctionInput(
360
+ field=FieldInput(name='result'),
361
+ function='[x] + [y]'
362
+ )
363
+ )
364
+
365
+ data = self._create_and_migrate(temp_dir, 'formula', node)
366
+ assert data['nodes'][0]['type'] == 'formula'
367
+ assert 'function' in data['nodes'][0]['setting_input']
368
+
369
+ def test_migrate_join_node(self, temp_dir):
370
+ """Test join node migration."""
371
+ from tools.migrate.legacy_schemas import JoinInput, JoinInputs, JoinMap, NodeJoin, SelectInput
372
+
373
+ node = NodeJoin(
374
+ flow_id=1,
375
+ node_id=1,
376
+ join_input=JoinInput(
377
+ join_mapping=[JoinMap(left_col='id', right_col='id')],
378
+ left_select=JoinInputs(renames=[SelectInput(old_name='id')]),
379
+ right_select=JoinInputs(renames=[SelectInput(old_name='id')]),
380
+ how='left'
381
+ )
382
+ )
383
+
384
+ data = self._create_and_migrate(temp_dir, 'join', node)
385
+ assert data['nodes'][0]['type'] == 'join'
386
+ assert 'join_input' in data['nodes'][0]['setting_input']
387
+
388
+ def test_migrate_join_node_with_none_selects(self, temp_dir):
389
+ """Test join node migration when left_select/right_select are None (old format)."""
390
+ from tools.migrate.legacy_schemas import JoinInput, JoinMap, NodeJoin
391
+
392
+ # OLD format: left_select and right_select could be None
393
+ node = NodeJoin(
394
+ flow_id=1,
395
+ node_id=1,
396
+ join_input=JoinInput(
397
+ join_mapping=[JoinMap(left_col='id', right_col='id')],
398
+ left_select=None,
399
+ right_select=None,
400
+ how='inner'
401
+ )
402
+ )
403
+
404
+ data = self._create_and_migrate(temp_dir, 'join', node)
405
+ join_input = data['nodes'][0]['setting_input']['join_input']
406
+
407
+ # Verify migration added empty renames lists
408
+ assert join_input['left_select'] == {'renames': []}
409
+ assert join_input['right_select'] == {'renames': []}
410
+
411
+ def test_migrate_groupby_node(self, temp_dir):
412
+ """Test group by node migration."""
413
+ from tools.migrate.legacy_schemas import AggColl, GroupByInput, NodeGroupBy
414
+
415
+ node = NodeGroupBy(
416
+ flow_id=1,
417
+ node_id=1,
418
+ groupby_input=GroupByInput(
419
+ agg_cols=[
420
+ AggColl(old_name='category', agg='groupby'),
421
+ AggColl(old_name='amount', agg='sum', new_name='total'),
422
+ ]
423
+ )
424
+ )
425
+
426
+ data = self._create_and_migrate(temp_dir, 'group_by', node)
427
+ assert data['nodes'][0]['type'] == 'group_by'
428
+ assert 'groupby_input' in data['nodes'][0]['setting_input']
429
+
430
+ def test_migrate_polars_code_node(self, temp_dir):
431
+ """Test polars code node migration."""
432
+ from tools.migrate.legacy_schemas import NodePolarsCode, PolarsCodeInput
433
+
434
+ node = NodePolarsCode(
435
+ flow_id=1,
436
+ node_id=1,
437
+ polars_code_input=PolarsCodeInput(
438
+ polars_code='output_df = input_df.with_columns(pl.col("x") * 2)'
439
+ ),
440
+ depending_on_ids=[0]
441
+ )
442
+
443
+ data = self._create_and_migrate(temp_dir, 'polars_code', node)
444
+
445
+ polars_node = data['nodes'][0]
446
+ assert polars_node['type'] == 'polars_code'
447
+ assert 'output_df' in polars_node['setting_input']['polars_code_input']['polars_code']
448
+
449
+
450
+ # =============================================================================
451
+ # LEGACY SCHEMA VALIDATION TESTS
452
+ # =============================================================================
453
+
454
+ class TestLegacySchemas:
455
+ """Test that legacy schemas can be instantiated correctly."""
456
+
457
+ def test_received_table_has_flat_fields(self):
458
+ """Verify OLD ReceivedTable has flat structure."""
459
+ from tools.migrate.legacy_schemas import ReceivedTable
460
+
461
+ rt = ReceivedTable(
462
+ name='test.csv',
463
+ path='/path/test.csv',
464
+ file_type='csv',
465
+ delimiter=';',
466
+ encoding='latin-1',
467
+ sheet_name='Sheet1', # Excel field at top level (OLD style)
468
+ )
469
+
470
+ # OLD style: all fields at top level
471
+ assert rt.delimiter == ';'
472
+ assert rt.encoding == 'latin-1'
473
+ assert rt.sheet_name == 'Sheet1'
474
+
475
+ # Verify no table_settings (OLD style)
476
+ assert not hasattr(rt, 'table_settings')
477
+
478
+ def test_output_settings_has_separate_tables(self):
479
+ """Verify OLD OutputSettings has separate table fields."""
480
+ from tools.migrate.legacy_schemas import OutputCsvTable, OutputExcelTable, OutputSettings
481
+
482
+ os = OutputSettings(
483
+ name='out.csv',
484
+ directory='/out',
485
+ file_type='csv',
486
+ output_csv_table=OutputCsvTable(delimiter='|'),
487
+ output_excel_table=OutputExcelTable(sheet_name='Data'),
488
+ )
489
+
490
+ # OLD style: separate table objects
491
+ assert os.output_csv_table.delimiter == '|'
492
+ assert os.output_excel_table.sheet_name == 'Data'
493
+
494
+ # Verify no unified table_settings (OLD style)
495
+ assert not hasattr(os, 'table_settings')
496
+
497
+ def test_legacy_class_map_completeness(self):
498
+ """Test that LEGACY_CLASS_MAP has all needed classes."""
499
+ from tools.migrate.legacy_schemas import LEGACY_CLASS_MAP
500
+
501
+ required_classes = [
502
+ # Transform schemas
503
+ 'SelectInput', 'JoinInput', 'JoinMap', 'PolarsCodeInput',
504
+ 'GroupByInput', 'AggColl', 'FilterInput', 'BasicFilter',
505
+
506
+ # Input/Output schemas
507
+ 'ReceivedTable', 'OutputSettings', 'OutputCsvTable',
508
+
509
+ # Node schemas
510
+ 'NodeRead', 'NodeSelect', 'NodeOutput', 'NodeJoin',
511
+ 'NodePolarsCode', 'NodeGroupBy',
512
+
513
+ # Flow schemas
514
+ 'FlowInformation', 'FlowSettings', 'NodeInformation',
515
+ ]
516
+
517
+ for cls_name in required_classes:
518
+ assert cls_name in LEGACY_CLASS_MAP, f"Missing {cls_name}"
519
+
520
+
521
+ # =============================================================================
522
+ # ROUND TRIP TESTS
523
+ # =============================================================================
524
+
525
+ class TestRoundTrip:
526
+ """Test complete pickle -> YAML -> validation round trips."""
527
+
528
+ def test_complex_flow_roundtrip(self, temp_dir):
529
+ """Test migration of a flow with multiple node types."""
530
+ yaml = pytest.importorskip('yaml')
531
+
532
+ from tools.migrate.legacy_schemas import (
533
+ FlowInformation,
534
+ FlowSettings,
535
+ NodeInformation,
536
+ NodeOutput,
537
+ NodeRead,
538
+ NodeSelect,
539
+ OutputCsvTable,
540
+ OutputSettings,
541
+ ReceivedTable,
542
+ SelectInput,
543
+ )
544
+ from tools.migrate.migrate import migrate_flowfile
545
+
546
+ flow = FlowInformation(
547
+ flow_id=1,
548
+ flow_name='complex_flow',
549
+ flow_settings=FlowSettings(
550
+ flow_id=1,
551
+ name='complex_flow',
552
+ description='A complex flow for testing'
553
+ ),
554
+ data={
555
+ 1: NodeInformation(
556
+ id=1, type='read',
557
+ setting_input=NodeRead(
558
+ flow_id=1, node_id=1,
559
+ received_file=ReceivedTable(
560
+ name='input.csv',
561
+ path='/data/input.csv',
562
+ file_type='csv',
563
+ delimiter=','
564
+ )
565
+ )
566
+ ),
567
+ 2: NodeInformation(
568
+ id=2, type='select',
569
+ setting_input=NodeSelect(
570
+ flow_id=1, node_id=2,
571
+ select_input=[SelectInput(old_name='a')]
572
+ )
573
+ ),
574
+ 3: NodeInformation(
575
+ id=3, type='output',
576
+ setting_input=NodeOutput(
577
+ flow_id=1, node_id=3,
578
+ output_settings=OutputSettings(
579
+ name='output.csv',
580
+ directory='/out',
581
+ file_type='csv',
582
+ output_csv_table=OutputCsvTable(delimiter=';')
583
+ )
584
+ )
585
+ ),
586
+ },
587
+ node_starts=[1],
588
+ node_connections=[(1, 2), (2, 3)],
589
+ )
590
+
591
+ pickle_path = temp_dir / 'complex.flowfile'
592
+ with open(pickle_path, 'wb') as f:
593
+ pickle.dump(flow, f)
594
+
595
+ output_path = migrate_flowfile(pickle_path, format='yaml')
596
+
597
+ # Load and validate YAML
598
+ with open(output_path) as f:
599
+ data = yaml.safe_load(f)
600
+
601
+ # Verify FlowfileData format
602
+ assert data['flowfile_version'] == '2.0'
603
+ assert data['flowfile_name'] == 'complex_flow'
604
+ assert data['flowfile_id'] == 1
605
+ assert len(data['nodes']) == 3
606
+
607
+ # Verify transformations applied
608
+ read_node = next(n for n in data['nodes'] if n['type'] == 'read')
609
+ assert 'table_settings' in read_node['setting_input']['received_file']
610
+
611
+ output_node = next(n for n in data['nodes'] if n['type'] == 'output')
612
+ assert 'table_settings' in output_node['setting_input']['output_settings']
613
+ assert output_node['setting_input']['output_settings']['table_settings']['delimiter'] == ';'
614
+
615
+ # Verify start node is marked
616
+ start_nodes = [n for n in data['nodes'] if n.get('is_start_node')]
617
+ assert len(start_nodes) == 1
618
+ assert start_nodes[0]['id'] == 1
619
+
620
+
621
+ if __name__ == '__main__':
622
+ pytest.main([__file__, '-v'])