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,610 @@
1
+ """
2
+ Migration logic for converting old flowfile pickles to new YAML format.
3
+ """
4
+
5
+ import pickle
6
+ from dataclasses import asdict, fields, is_dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ try:
11
+ import yaml
12
+ except ImportError:
13
+ yaml = None
14
+
15
+ from tools.migrate.legacy_schemas import LEGACY_CLASS_MAP
16
+
17
+
18
+ class LegacyUnpickler(pickle.Unpickler):
19
+ """
20
+ Custom unpickler that redirects class lookups to legacy dataclass definitions.
21
+
22
+ ONLY intercepts classes from transform_schema.py that changed from @dataclass to BaseModel.
23
+ All other classes (schemas.py, input_schema.py) were already Pydantic and load normally.
24
+ """
25
+
26
+ # ONLY these classes changed from @dataclass to BaseModel
27
+ # These are all from flowfile_core/schemas/transform_schema.py
28
+ DATACLASS_TO_PYDANTIC = {
29
+ "SelectInput",
30
+ "FieldInput",
31
+ "FunctionInput",
32
+ "BasicFilter",
33
+ "FilterInput",
34
+ "SelectInputs",
35
+ "JoinInputs",
36
+ "JoinMap",
37
+ "CrossJoinInput",
38
+ "JoinInput",
39
+ "FuzzyMatchInput",
40
+ "AggColl",
41
+ "GroupByInput",
42
+ "PivotInput",
43
+ "SortByInput",
44
+ "RecordIdInput",
45
+ "TextToRowsInput",
46
+ "UnpivotInput",
47
+ "UnionInput",
48
+ "UniqueInput",
49
+ "GraphSolverInput",
50
+ "PolarsCodeInput",
51
+ }
52
+
53
+ def find_class(self, module: str, name: str):
54
+ """Override to redirect ONLY transform_schema dataclasses to legacy definitions."""
55
+ # Only intercept classes that changed from dataclass to Pydantic
56
+ if name in self.DATACLASS_TO_PYDANTIC and name in LEGACY_CLASS_MAP:
57
+ return LEGACY_CLASS_MAP[name]
58
+
59
+ # Everything else (schemas.py, input_schema.py) loads with actual Pydantic classes
60
+ return super().find_class(module, name)
61
+
62
+
63
+ def load_legacy_flowfile(path: Path) -> Any:
64
+ """
65
+ Load an old flowfile using legacy class definitions.
66
+
67
+ Args:
68
+ path: Path to the .flowfile pickle
69
+
70
+ Returns:
71
+ The deserialized FlowInformation object (as legacy dataclass)
72
+ """
73
+ with open(path, "rb") as f:
74
+ return LegacyUnpickler(f).load()
75
+
76
+
77
+ def convert_to_dict(obj: Any, _seen: set = None) -> Any:
78
+ """
79
+ Recursively convert dataclasses, Pydantic models, and complex objects to plain dicts.
80
+
81
+ Handles:
82
+ - Pydantic BaseModel instances (via model_dump)
83
+ - Dataclasses (via asdict or manual conversion)
84
+ - Lists, dicts, tuples
85
+ - Primitive types
86
+
87
+ Args:
88
+ obj: Object to convert
89
+ _seen: Set of seen object IDs (for cycle detection)
90
+
91
+ Returns:
92
+ Plain dict/list/primitive representation
93
+ """
94
+ if _seen is None:
95
+ _seen = set()
96
+
97
+ # Handle None
98
+ if obj is None:
99
+ return None
100
+
101
+ # Handle primitives
102
+ if isinstance(obj, (str, int, float, bool)):
103
+ return obj
104
+
105
+ # Cycle detection
106
+ obj_id = id(obj)
107
+ if obj_id in _seen:
108
+ return f"<circular reference to {type(obj).__name__}>"
109
+ _seen.add(obj_id)
110
+
111
+ try:
112
+ # Handle Pydantic models FIRST (check for model_dump method)
113
+ if hasattr(obj, "model_dump") and callable(obj.model_dump):
114
+ try:
115
+ data = obj.model_dump()
116
+ # Recursively convert any nested structures
117
+ return convert_to_dict(data, _seen)
118
+ except Exception:
119
+ # Fall through to other methods if model_dump fails
120
+ pass
121
+
122
+ # Handle dataclasses
123
+ if is_dataclass(obj) and not isinstance(obj, type):
124
+ try:
125
+ # Try asdict first (handles nested dataclasses)
126
+ return asdict(obj)
127
+ except Exception:
128
+ # Fall back to manual conversion
129
+ result = {}
130
+ for f in fields(obj):
131
+ value = getattr(obj, f.name, None)
132
+ result[f.name] = convert_to_dict(value, _seen)
133
+ return result
134
+
135
+ # Handle dicts
136
+ if isinstance(obj, dict):
137
+ return {k: convert_to_dict(v, _seen) for k, v in obj.items()}
138
+
139
+ # Handle lists and tuples - convert both to lists for clean YAML
140
+ if isinstance(obj, (list, tuple)):
141
+ return [convert_to_dict(item, _seen) for item in obj]
142
+
143
+ # Handle sets
144
+ if isinstance(obj, set):
145
+ return [convert_to_dict(item, _seen) for item in obj]
146
+
147
+ # Handle Path objects
148
+ if isinstance(obj, Path):
149
+ return str(obj)
150
+
151
+ # Handle objects with __dict__ (generic fallback)
152
+ if hasattr(obj, "__dict__"):
153
+ return {k: convert_to_dict(v, _seen) for k, v in obj.__dict__.items() if not k.startswith("_")}
154
+
155
+ # Fallback: try to convert to string
156
+ return str(obj)
157
+
158
+ finally:
159
+ _seen.discard(obj_id)
160
+
161
+
162
+ def transform_to_new_schema(data: dict) -> dict:
163
+ """
164
+ Transform the legacy schema structure to the new FlowfileData format.
165
+
166
+ This handles:
167
+ - ReceivedTable: flat fields -> nested table_settings
168
+ - OutputSettings: separate table fields -> unified table_settings
169
+ - Field name changes (flow_id -> flowfile_id, etc.)
170
+
171
+ Args:
172
+ data: Dict representation of legacy FlowInformation
173
+
174
+ Returns:
175
+ Transformed dict ready for YAML serialization (FlowfileData format)
176
+ """
177
+ node_starts = set(data.get("node_starts", []))
178
+
179
+ result = {
180
+ "flowfile_version": "2.0",
181
+ "flowfile_id": data.get("flow_id", 1),
182
+ "flowfile_name": data.get("flow_name", ""),
183
+ "flowfile_settings": _transform_flow_settings(data.get("flow_settings", {})),
184
+ "nodes": _transform_nodes(data.get("data", {}), node_starts),
185
+ }
186
+
187
+ return result
188
+
189
+
190
+ def _transform_flow_settings(settings: dict) -> dict:
191
+ """Transform flow settings to FlowfileSettings format."""
192
+ if not settings:
193
+ return {
194
+ "execution_mode": "Development",
195
+ "execution_location": "local",
196
+ "auto_save": False,
197
+ "show_detailed_progress": True,
198
+ }
199
+
200
+ return {
201
+ "description": settings.get("description"),
202
+ "execution_mode": settings.get("execution_mode", "Development"),
203
+ "execution_location": settings.get("execution_location", "local"),
204
+ "auto_save": settings.get("auto_save", False),
205
+ "show_detailed_progress": settings.get("show_detailed_progress", True),
206
+ }
207
+
208
+
209
+ def _transform_nodes(nodes_data: dict, node_starts: set) -> list[dict]:
210
+ """Transform nodes dict to FlowfileNode list format."""
211
+ nodes = []
212
+
213
+ for node_id, node_info in nodes_data.items():
214
+ if not isinstance(node_info, dict):
215
+ node_info = convert_to_dict(node_info)
216
+
217
+ actual_node_id = node_info.get("id", node_id)
218
+
219
+ node = {
220
+ "id": actual_node_id,
221
+ "type": node_info.get("type", ""),
222
+ "is_start_node": actual_node_id in node_starts,
223
+ "description": node_info.get("description", ""),
224
+ "x_position": int(node_info.get("x_position", 0) or 0),
225
+ "y_position": int(node_info.get("y_position", 0) or 0),
226
+ "left_input_id": node_info.get("left_input_id"),
227
+ "right_input_id": node_info.get("right_input_id"),
228
+ "input_ids": node_info.get("input_ids", []),
229
+ "outputs": node_info.get("outputs", []),
230
+ }
231
+
232
+ # Transform settings based on node type
233
+ setting_input = node_info.get("setting_input", {})
234
+ if setting_input:
235
+ if not isinstance(setting_input, dict):
236
+ setting_input = convert_to_dict(setting_input)
237
+ node["setting_input"] = _transform_node_settings(node["type"], setting_input)
238
+
239
+ nodes.append(node)
240
+
241
+ return nodes
242
+
243
+
244
+ def _transform_node_settings(node_type: str, settings: dict) -> dict:
245
+ """Transform node-specific settings to new format.
246
+
247
+ Handles structural changes for various node types:
248
+ - read: ReceivedTable flat → nested table_settings
249
+ - output: OutputSettings separate tables → unified table_settings
250
+ - polars_code: PolarsCodeInput extraction
251
+ - select: Ensure sorted_by field exists
252
+ - join/fuzzy_match: Handle JoinInput/FuzzyMatchInput changes
253
+ """
254
+ # Remove common fields that are stored elsewhere
255
+ settings = {
256
+ k: v
257
+ for k, v in settings.items()
258
+ if k
259
+ not in (
260
+ "flow_id",
261
+ "node_id",
262
+ "pos_x",
263
+ "pos_y",
264
+ "is_setup",
265
+ "description",
266
+ "cache_results",
267
+ "user_id",
268
+ "is_flow_output",
269
+ "is_user_defined",
270
+ )
271
+ }
272
+
273
+ # Handle specific node types
274
+ if node_type == "read":
275
+ return _transform_read_settings(settings)
276
+ elif node_type == "output":
277
+ return _transform_output_settings(settings)
278
+ elif node_type == "polars_code":
279
+ return _transform_polars_code_settings(settings)
280
+ elif node_type == "select":
281
+ return _transform_select_settings(settings)
282
+ elif node_type in ("join", "fuzzy_match", "cross_join"):
283
+ return _transform_join_settings(settings)
284
+
285
+ return settings
286
+
287
+
288
+ def _transform_select_settings(settings: dict) -> dict:
289
+ """Transform NodeSelect settings - ensure all fields exist."""
290
+ # Ensure sorted_by field exists (added in new version)
291
+ if "sorted_by" not in settings:
292
+ settings["sorted_by"] = "none"
293
+
294
+ # Ensure select_input items have position field
295
+ select_input = settings.get("select_input", [])
296
+ if isinstance(select_input, list):
297
+ for i, item in enumerate(select_input):
298
+ if isinstance(item, dict) and item.get("position") is None:
299
+ item["position"] = i
300
+
301
+ return settings
302
+
303
+
304
+ def _transform_join_settings(settings: dict) -> dict:
305
+ """Transform join-related node settings.
306
+
307
+ Handles migration of old JoinInput where left_select/right_select could be None.
308
+ New schema requires these to be JoinInputs with renames list.
309
+ """
310
+ # Handle join_input transformation
311
+ join_input = settings.get("join_input") or settings.get("cross_join_input")
312
+ if join_input and isinstance(join_input, dict):
313
+ # ADD DEFAULT EMPTY JoinInputs IF MISSING (required in new schema)
314
+ for side in ["left_select", "right_select"]:
315
+ if join_input.get(side) is None:
316
+ join_input[side] = {"renames": []}
317
+
318
+ select = join_input.get(side)
319
+ if select and isinstance(select, dict):
320
+ # Ensure renames key exists
321
+ if "renames" not in select:
322
+ select["renames"] = []
323
+
324
+ renames = select.get("renames", [])
325
+ if isinstance(renames, list):
326
+ for i, item in enumerate(renames):
327
+ if isinstance(item, dict) and item.get("position") is None:
328
+ item["position"] = i
329
+
330
+ return settings
331
+
332
+
333
+ def _transform_read_settings(settings: dict) -> dict:
334
+ """Transform NodeRead settings - extract table_settings from old flat structure.
335
+
336
+ OLD structure (flat):
337
+ received_file:
338
+ file_type: csv
339
+ delimiter: ","
340
+ encoding: "utf-8"
341
+ sheet_name: null # Excel fields mixed in
342
+ ...
343
+
344
+ NEW structure (nested):
345
+ received_file:
346
+ file_type: csv
347
+ table_settings:
348
+ file_type: csv
349
+ delimiter: ","
350
+ encoding: "utf-8"
351
+ """
352
+ received_file = settings.get("received_file", {})
353
+ if not received_file:
354
+ return settings
355
+
356
+ # Check if already transformed (has table_settings)
357
+ if "table_settings" in received_file and isinstance(received_file["table_settings"], dict):
358
+ return settings
359
+
360
+ file_type = received_file.get("file_type", "csv")
361
+
362
+ # Build table_settings based on file_type, extracting from flat structure
363
+ if file_type == "csv":
364
+ table_settings = {
365
+ "file_type": "csv",
366
+ "reference": received_file.get("reference", ""),
367
+ "starting_from_line": received_file.get("starting_from_line", 0),
368
+ "delimiter": received_file.get("delimiter", ","),
369
+ "has_headers": received_file.get("has_headers", True),
370
+ "encoding": received_file.get("encoding", "utf-8") or "utf-8",
371
+ "parquet_ref": received_file.get("parquet_ref"),
372
+ "row_delimiter": received_file.get("row_delimiter", "\n"),
373
+ "quote_char": received_file.get("quote_char", '"'),
374
+ "infer_schema_length": received_file.get("infer_schema_length", 10000),
375
+ "truncate_ragged_lines": received_file.get("truncate_ragged_lines", False),
376
+ "ignore_errors": received_file.get("ignore_errors", False),
377
+ }
378
+ elif file_type == "json":
379
+ table_settings = {
380
+ "file_type": "json",
381
+ "reference": received_file.get("reference", ""),
382
+ "starting_from_line": received_file.get("starting_from_line", 0),
383
+ "delimiter": received_file.get("delimiter", ","),
384
+ "has_headers": received_file.get("has_headers", True),
385
+ "encoding": received_file.get("encoding", "utf-8") or "utf-8",
386
+ "parquet_ref": received_file.get("parquet_ref"),
387
+ "row_delimiter": received_file.get("row_delimiter", "\n"),
388
+ "quote_char": received_file.get("quote_char", '"'),
389
+ "infer_schema_length": received_file.get("infer_schema_length", 10000),
390
+ "truncate_ragged_lines": received_file.get("truncate_ragged_lines", False),
391
+ "ignore_errors": received_file.get("ignore_errors", False),
392
+ }
393
+ elif file_type == "excel":
394
+ table_settings = {
395
+ "file_type": "excel",
396
+ "sheet_name": received_file.get("sheet_name"),
397
+ "start_row": received_file.get("start_row", 0),
398
+ "start_column": received_file.get("start_column", 0),
399
+ "end_row": received_file.get("end_row", 0),
400
+ "end_column": received_file.get("end_column", 0),
401
+ "has_headers": received_file.get("has_headers", True),
402
+ "type_inference": received_file.get("type_inference", False),
403
+ }
404
+ elif file_type == "parquet":
405
+ table_settings = {"file_type": "parquet"}
406
+ else:
407
+ # Unknown file type - try to preserve what we can
408
+ table_settings = {"file_type": file_type or "csv"}
409
+
410
+ # Build new structure with metadata + nested table_settings
411
+ return {
412
+ "received_file": {
413
+ # Metadata fields (preserved from old structure)
414
+ "id": received_file.get("id"),
415
+ "name": received_file.get("name"),
416
+ "path": received_file.get("path", ""),
417
+ "directory": received_file.get("directory"),
418
+ "analysis_file_available": received_file.get("analysis_file_available", False),
419
+ "status": received_file.get("status"),
420
+ "fields": received_file.get("fields", []),
421
+ "abs_file_path": received_file.get("abs_file_path"),
422
+ # New discriminator field
423
+ "file_type": file_type,
424
+ # Nested table settings
425
+ "table_settings": table_settings,
426
+ }
427
+ }
428
+
429
+
430
+ def _transform_output_settings(settings: dict) -> dict:
431
+ """Transform NodeOutput settings - consolidate separate table settings into single field.
432
+
433
+ OLD structure:
434
+ output_settings:
435
+ file_type: csv
436
+ output_csv_table: {delimiter: ",", encoding: "utf-8"}
437
+ output_parquet_table: {}
438
+ output_excel_table: {sheet_name: "Sheet1"}
439
+
440
+ NEW structure:
441
+ output_settings:
442
+ file_type: csv
443
+ table_settings:
444
+ file_type: csv
445
+ delimiter: ","
446
+ encoding: "utf-8"
447
+ """
448
+ output_settings = settings.get("output_settings", {})
449
+ if not output_settings:
450
+ return settings
451
+
452
+ # Check if already transformed
453
+ if "table_settings" in output_settings and isinstance(output_settings["table_settings"], dict):
454
+ return settings
455
+
456
+ file_type = output_settings.get("file_type", "csv")
457
+
458
+ # Build table_settings from old separate fields
459
+ if file_type == "csv":
460
+ old_csv = output_settings.get("output_csv_table", {}) or {}
461
+ table_settings = {
462
+ "file_type": "csv",
463
+ "delimiter": old_csv.get("delimiter", ","),
464
+ "encoding": old_csv.get("encoding", "utf-8"),
465
+ }
466
+ elif file_type == "excel":
467
+ old_excel = output_settings.get("output_excel_table", {}) or {}
468
+ table_settings = {
469
+ "file_type": "excel",
470
+ "sheet_name": old_excel.get("sheet_name", "Sheet1"),
471
+ }
472
+ elif file_type == "parquet":
473
+ table_settings = {"file_type": "parquet"}
474
+ else:
475
+ table_settings = {"file_type": file_type or "csv"}
476
+
477
+ return {
478
+ "output_settings": {
479
+ "name": output_settings.get("name", ""),
480
+ "directory": output_settings.get("directory", ""),
481
+ "file_type": file_type,
482
+ "fields": output_settings.get("fields", []),
483
+ "write_mode": output_settings.get("write_mode", "overwrite"),
484
+ "abs_file_path": output_settings.get("abs_file_path"),
485
+ "table_settings": table_settings,
486
+ }
487
+ }
488
+
489
+
490
+ def _transform_polars_code_settings(settings: dict) -> dict:
491
+ """Transform NodePolarsCode settings.
492
+
493
+ Extracts polars_code from PolarsCodeInput and handles depending_on_id → depending_on_ids.
494
+ """
495
+ polars_code_input = settings.get("polars_code_input", {})
496
+
497
+ # Extract the actual code
498
+ polars_code = ""
499
+ if isinstance(polars_code_input, dict):
500
+ polars_code = polars_code_input.get("polars_code", "")
501
+ elif hasattr(polars_code_input, "polars_code"):
502
+ polars_code = polars_code_input.polars_code
503
+
504
+ # Handle depending_on_id → depending_on_ids migration
505
+ depending_on_ids = settings.get("depending_on_ids", [])
506
+ if not depending_on_ids or depending_on_ids == [-1]:
507
+ old_id = settings.get("depending_on_id")
508
+ if old_id is not None and old_id != -1:
509
+ depending_on_ids = [old_id]
510
+ else:
511
+ depending_on_ids = []
512
+
513
+ return {
514
+ "polars_code_input": {
515
+ "polars_code": polars_code,
516
+ },
517
+ "depending_on_ids": depending_on_ids,
518
+ }
519
+
520
+
521
+ def migrate_flowfile(input_path: Path, output_path: Path = None, format: str = "yaml") -> Path:
522
+ """
523
+ Migrate a single flowfile from pickle to YAML format.
524
+
525
+ Args:
526
+ input_path: Path to the .flowfile pickle
527
+ output_path: Output path (default: same name with .yaml extension)
528
+ format: Output format ('yaml' or 'json')
529
+
530
+ Returns:
531
+ Path to the created output file
532
+ """
533
+ if format == "yaml" and yaml is None:
534
+ raise ImportError("PyYAML is required for YAML output. Install with: pip install pyyaml")
535
+
536
+ # Determine output path
537
+ if output_path is None:
538
+ suffix = ".yaml" if format == "yaml" else ".json"
539
+ output_path = input_path.with_suffix(suffix)
540
+
541
+ print(f"Loading: {input_path}")
542
+
543
+ # Load legacy flowfile
544
+ legacy_data = load_legacy_flowfile(input_path)
545
+
546
+ # Convert to dict
547
+ data_dict = convert_to_dict(legacy_data)
548
+
549
+ # Transform to new schema
550
+ transformed = transform_to_new_schema(data_dict)
551
+
552
+ # Write output
553
+ print(f"Writing: {output_path}")
554
+
555
+ with open(output_path, "w", encoding="utf-8") as f:
556
+ if format == "yaml":
557
+ yaml.dump(transformed, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
558
+ else:
559
+ import json
560
+
561
+ json.dump(transformed, f, indent=2, ensure_ascii=False)
562
+
563
+ print(f"✓ Migrated: {input_path.name} → {output_path.name}")
564
+ return output_path
565
+
566
+
567
+ def migrate_directory(dir_path: Path, output_dir: Path = None, format: str = "yaml") -> list[Path]:
568
+ """
569
+ Migrate all flowfiles in a directory.
570
+
571
+ Args:
572
+ dir_path: Directory containing .flowfile pickles
573
+ output_dir: Output directory (default: same as input)
574
+ format: Output format ('yaml' or 'json')
575
+
576
+ Returns:
577
+ List of created output file paths
578
+ """
579
+ output_dir = output_dir or dir_path
580
+ output_dir.mkdir(parents=True, exist_ok=True)
581
+
582
+ flowfiles = list(dir_path.glob("**/*.flowfile"))
583
+
584
+ if not flowfiles:
585
+ print(f"No .flowfile files found in {dir_path}")
586
+ return []
587
+
588
+ print(f"Found {len(flowfiles)} flowfile(s) to migrate\n")
589
+
590
+ migrated = []
591
+ failed = []
592
+
593
+ for flowfile in flowfiles:
594
+ # Preserve directory structure
595
+ relative = flowfile.relative_to(dir_path)
596
+ suffix = ".yaml" if format == "yaml" else ".json"
597
+ output_path = output_dir / relative.with_suffix(suffix)
598
+ output_path.parent.mkdir(parents=True, exist_ok=True)
599
+
600
+ try:
601
+ migrate_flowfile(flowfile, output_path, format)
602
+ migrated.append(output_path)
603
+ except Exception as e:
604
+ print(f"✗ Failed: {flowfile.name} - {e}")
605
+ failed.append((flowfile, e))
606
+
607
+ print(f"\n{'='*50}")
608
+ print(f"Migration complete: {len(migrated)} succeeded, {len(failed)} failed")
609
+
610
+ return migrated
File without changes
@@ -0,0 +1,21 @@
1
+ """
2
+ Pytest configuration and shared fixtures for migration tool tests.
3
+ """
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ # Ensure tools package is importable
9
+ REPO_ROOT = Path(__file__).parent.parent.parent.parent
10
+ if str(REPO_ROOT) not in sys.path:
11
+ sys.path.insert(0, str(REPO_ROOT))
12
+
13
+
14
+ def pytest_configure(config):
15
+ """Configure pytest markers."""
16
+ config.addinivalue_line(
17
+ "markers", "slow: marks tests as slow (deselect with '-m \"not slow\"')"
18
+ )
19
+ config.addinivalue_line(
20
+ "markers", "requires_yaml: marks tests that require PyYAML"
21
+ )