Flowfile 0.5.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 (329) hide show
  1. build_backends/main.py +25 -22
  2. build_backends/main_prd.py +10 -19
  3. flowfile/__init__.py +178 -74
  4. flowfile/__main__.py +10 -7
  5. flowfile/api.py +51 -57
  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-0dfba9f2.js → CloudConnectionView-f13f202b.js} +11 -11
  11. flowfile/web/static/assets/{CloudStorageReader-d5b1b6c9.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-00d87aad.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-4685e75d.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-23e909da.js → ContextMenu-31ee57f0.js} +3 -3
  19. flowfile/web/static/assets/{ContextMenu-70ae0c79.js → ContextMenu-69a74055.js} +3 -3
  20. flowfile/web/static/assets/{ContextMenu-f149cf7c.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-702a3edd.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-b1519993.js → CustomNode-8479239b.js} +36 -24
  27. flowfile/web/static/assets/{DatabaseConnectionSettings-6f3e4ea5.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-d38c7295.js → DatabaseReader-c58b9552.js} +25 -15
  31. flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
  32. flowfile/web/static/assets/{DatabaseManager-cf5ef661.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-b04ef46a.js → DatabaseWriter-4d05ddc7.js} +17 -10
  35. flowfile/web/static/assets/{designer-8da3ba3a.css → DesignerView-a6d0ee84.css} +614 -546
  36. flowfile/web/static/assets/{designer-9633482a.js → DesignerView-e6f5c0e8.js} +1107 -3170
  37. flowfile/web/static/assets/{documentation-ca400224.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-5fa10ed8.js → ExploreData-7b54caca.js} +18 -9
  41. flowfile/web/static/assets/{ExternalSource-d39af878.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-6b04fb1d.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-999521f4.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-17dd2198.js → GraphSolver-c7e6780e.js} +13 -11
  51. flowfile/web/static/assets/{GroupBy-6b039e18.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-24d0f113.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-34639209.js → ManualInput-8d3374b2.js} +170 -116
  59. flowfile/web/static/assets/{MultiSelect-0e8724a3.js → MultiSelect-ad1b6243.js} +2 -2
  60. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-b0e538c2.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-3d63a470.js → NumericInput-7100234c.js} +2 -2
  64. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-e0edeccc.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
  65. flowfile/web/static/assets/{Output-283fe388.css → Output-35e97000.css} +6 -6
  66. flowfile/web/static/assets/{Output-edea9802.js → Output-f5efd2aa.js} +12 -9
  67. flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
  68. flowfile/web/static/assets/{Pivot-61d19301.js → Pivot-d981d23c.js} +11 -9
  69. flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
  70. flowfile/web/static/assets/{PivotValidation-f97fec5b.js → PivotValidation-39386e95.js} +3 -3
  71. flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
  72. flowfile/web/static/assets/{PivotValidation-de9f43fe.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-bc3c9984.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-e808b239.css → Read-36e7bd51.css} +12 -12
  78. flowfile/web/static/assets/{Read-64a3f259.js → Read-aec2e377.js} +14 -11
  79. flowfile/web/static/assets/{RecordCount-3d5039be.js → RecordCount-78ed6845.js} +6 -4
  80. flowfile/web/static/assets/{RecordId-597510e0.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-df51adbe.js → SQLQueryComponent-48c72f5b.js} +3 -3
  83. flowfile/web/static/assets/{Sample-4be0a507.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-4839be57.js → SecretsView-17df66ee.js} +35 -36
  87. flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
  88. flowfile/web/static/assets/{Select-9b72f201.js → Select-0aee4c54.js} +9 -7
  89. flowfile/web/static/assets/{SettingsSection-f0f75a42.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-e1e9c953.js → SettingsSection-cd341bb6.js} +3 -3
  94. flowfile/web/static/assets/{SettingsSection-7ded385d.js → SettingsSection-f2002a6d.js} +3 -3
  95. flowfile/web/static/assets/{SingleSelect-6c777aac.js → SingleSelect-460cc0ea.js} +2 -2
  96. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-33e3ff9b.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
  97. flowfile/web/static/assets/{SliderInput-7cb93e62.js → SliderInput-5d926864.js} +7 -4
  98. flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
  99. flowfile/web/static/assets/{Sort-6cbde21a.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-d9a40c11.js → TextInput-a2d0bfbd.js} +2 -2
  102. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-5896c375.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-c4fcbf4d.js → TextToRows-918945f7.js} +11 -10
  105. flowfile/web/static/assets/{ToggleSwitch-4ef91d19.js → ToggleSwitch-f0ef5196.js} +2 -2
  106. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-38478c20.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-a03f512c.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-bfe9b996.js → Union-e8ab8c86.js} +8 -6
  111. flowfile/web/static/assets/{Unique-5d023a27.js → Unique-8cd4f976.js} +13 -10
  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-91cc5354.js → Unpivot-8da14095.js} +10 -8
  115. flowfile/web/static/assets/{UnpivotValidation-7ee2de44.js → UnpivotValidation-6f7d89ff.js} +3 -3
  116. flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
  117. flowfile/web/static/assets/{VueGraphicWalker-e51b9924.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-cf1221f0.js → api-24483f0d.js} +1 -1
  120. flowfile/web/static/assets/{api-c1bad5ca.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-614b998d.js → dropDown-ac0fda9d.js} +3 -3
  123. flowfile/web/static/assets/{fullEditor-f7971590.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-4fe5f36b.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-5429bbf8.js → index-fb6493ae.js} +41626 -40867
  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-076b85ab.js → outputCsv-8f8ba42d.js} +3 -3
  134. flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
  135. flowfile/web/static/assets/{outputExcel-0fd17dbe.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-b61e0847.js → outputParquet-07c81f65.js} +4 -4
  138. flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
  139. flowfile/web/static/assets/{readCsv-a8bb8b61.js → readCsv-07f6d9ad.js} +3 -3
  140. flowfile/web/static/assets/{readCsv-c767cb37.css → readCsv-3bfac4c3.css} +15 -15
  141. flowfile/web/static/assets/{readExcel-806d2826.css → readExcel-3db6b763.css} +13 -13
  142. flowfile/web/static/assets/{readExcel-67b4aee0.js → readExcel-ed69bc8f.js} +5 -5
  143. flowfile/web/static/assets/{readParquet-48c81530.css → readParquet-c5244ad5.css} +4 -4
  144. flowfile/web/static/assets/{readParquet-92ce1dbc.js → readParquet-e3ed4528.js} +3 -3
  145. flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
  146. flowfile/web/static/assets/{selectDynamic-92e25ee3.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-41b0e0d7.js → vue-codemirror.esm-0965f39f.js} +31 -640
  149. flowfile/web/static/assets/{vue-content-loader.es-2c8e608f.js → vue-content-loader.es-c506ad97.js} +1 -1
  150. flowfile/web/static/index.html +2 -2
  151. {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +2 -3
  152. flowfile-0.5.3.dist-info/RECORD +402 -0
  153. flowfile_core/__init__.py +13 -6
  154. flowfile_core/auth/jwt.py +51 -16
  155. flowfile_core/auth/models.py +32 -7
  156. flowfile_core/auth/password.py +89 -0
  157. flowfile_core/auth/secrets.py +8 -6
  158. flowfile_core/configs/__init__.py +9 -7
  159. flowfile_core/configs/flow_logger.py +15 -14
  160. flowfile_core/configs/node_store/__init__.py +72 -4
  161. flowfile_core/configs/node_store/nodes.py +155 -172
  162. flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
  163. flowfile_core/configs/settings.py +28 -15
  164. flowfile_core/database/connection.py +7 -6
  165. flowfile_core/database/init_db.py +96 -2
  166. flowfile_core/database/models.py +3 -1
  167. flowfile_core/fileExplorer/__init__.py +17 -0
  168. flowfile_core/fileExplorer/funcs.py +123 -57
  169. flowfile_core/fileExplorer/utils.py +10 -11
  170. flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
  171. flowfile_core/flowfile/analytics/analytics_processor.py +26 -24
  172. flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
  173. flowfile_core/flowfile/analytics/utils.py +1 -1
  174. flowfile_core/flowfile/code_generator/code_generator.py +358 -244
  175. flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
  176. flowfile_core/flowfile/connection_manager/models.py +1 -1
  177. flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
  178. flowfile_core/flowfile/database_connection_manager/models.py +1 -1
  179. flowfile_core/flowfile/extensions.py +17 -12
  180. flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
  181. flowfile_core/flowfile/flow_data_engine/create/funcs.py +115 -83
  182. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +481 -423
  183. flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
  184. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
  185. flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
  186. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
  187. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
  188. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +31 -20
  189. flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
  190. flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
  191. flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +14 -15
  192. flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
  193. flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
  194. flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
  195. flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
  196. flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
  197. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
  198. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +190 -127
  199. flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
  200. flowfile_core/flowfile/flow_data_engine/utils.py +99 -67
  201. flowfile_core/flowfile/flow_graph.py +918 -571
  202. flowfile_core/flowfile/flow_graph_utils.py +31 -49
  203. flowfile_core/flowfile/flow_node/flow_node.py +330 -233
  204. flowfile_core/flowfile/flow_node/models.py +53 -41
  205. flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
  206. flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
  207. flowfile_core/flowfile/handler.py +80 -30
  208. flowfile_core/flowfile/manage/compatibility_enhancements.py +209 -126
  209. flowfile_core/flowfile/manage/io_flowfile.py +54 -57
  210. flowfile_core/flowfile/node_designer/__init__.py +15 -13
  211. flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
  212. flowfile_core/flowfile/node_designer/custom_node.py +162 -36
  213. flowfile_core/flowfile/node_designer/ui_components.py +135 -34
  214. flowfile_core/flowfile/schema_callbacks.py +71 -51
  215. flowfile_core/flowfile/setting_generator/__init__.py +0 -1
  216. flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
  217. flowfile_core/flowfile/setting_generator/settings.py +64 -53
  218. flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
  219. flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
  220. flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
  221. flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
  222. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
  223. flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
  224. flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
  225. flowfile_core/flowfile/util/calculate_layout.py +9 -13
  226. flowfile_core/flowfile/util/execution_orderer.py +25 -17
  227. flowfile_core/flowfile/util/node_skipper.py +4 -4
  228. flowfile_core/flowfile/utils.py +19 -21
  229. flowfile_core/main.py +26 -19
  230. flowfile_core/routes/auth.py +284 -11
  231. flowfile_core/routes/cloud_connections.py +25 -25
  232. flowfile_core/routes/logs.py +21 -29
  233. flowfile_core/routes/public.py +3 -3
  234. flowfile_core/routes/routes.py +70 -34
  235. flowfile_core/routes/secrets.py +25 -27
  236. flowfile_core/routes/user_defined_components.py +483 -4
  237. flowfile_core/run_lock.py +0 -1
  238. flowfile_core/schemas/__init__.py +4 -6
  239. flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
  240. flowfile_core/schemas/cloud_storage_schemas.py +59 -53
  241. flowfile_core/schemas/input_schema.py +231 -144
  242. flowfile_core/schemas/output_model.py +49 -34
  243. flowfile_core/schemas/schemas.py +116 -89
  244. flowfile_core/schemas/transform_schema.py +518 -263
  245. flowfile_core/schemas/yaml_types.py +21 -7
  246. flowfile_core/secret_manager/secret_manager.py +17 -13
  247. flowfile_core/types.py +29 -9
  248. flowfile_core/utils/arrow_reader.py +7 -6
  249. flowfile_core/utils/excel_file_manager.py +3 -3
  250. flowfile_core/utils/fileManager.py +7 -7
  251. flowfile_core/utils/fl_executor.py +8 -10
  252. flowfile_core/utils/utils.py +4 -4
  253. flowfile_core/utils/validate_setup.py +5 -4
  254. flowfile_frame/__init__.py +106 -51
  255. flowfile_frame/adapters.py +2 -9
  256. flowfile_frame/adding_expr.py +73 -32
  257. flowfile_frame/cloud_storage/frame_helpers.py +27 -23
  258. flowfile_frame/cloud_storage/secret_manager.py +12 -26
  259. flowfile_frame/config.py +2 -5
  260. flowfile_frame/expr.py +311 -218
  261. flowfile_frame/expr.pyi +160 -159
  262. flowfile_frame/expr_name.py +23 -23
  263. flowfile_frame/flow_frame.py +571 -476
  264. flowfile_frame/flow_frame.pyi +123 -104
  265. flowfile_frame/flow_frame_methods.py +227 -246
  266. flowfile_frame/group_frame.py +50 -20
  267. flowfile_frame/join.py +2 -2
  268. flowfile_frame/lazy.py +129 -87
  269. flowfile_frame/lazy_methods.py +83 -30
  270. flowfile_frame/list_name_space.py +55 -50
  271. flowfile_frame/selectors.py +148 -68
  272. flowfile_frame/series.py +9 -7
  273. flowfile_frame/utils.py +19 -21
  274. flowfile_worker/__init__.py +12 -7
  275. flowfile_worker/configs.py +11 -19
  276. flowfile_worker/create/__init__.py +14 -9
  277. flowfile_worker/create/funcs.py +114 -77
  278. flowfile_worker/create/models.py +46 -43
  279. flowfile_worker/create/pl_types.py +14 -15
  280. flowfile_worker/create/read_excel_tables.py +34 -41
  281. flowfile_worker/create/utils.py +22 -19
  282. flowfile_worker/external_sources/s3_source/main.py +18 -51
  283. flowfile_worker/external_sources/s3_source/models.py +34 -27
  284. flowfile_worker/external_sources/sql_source/main.py +8 -5
  285. flowfile_worker/external_sources/sql_source/models.py +13 -9
  286. flowfile_worker/flow_logger.py +10 -8
  287. flowfile_worker/funcs.py +214 -155
  288. flowfile_worker/main.py +11 -17
  289. flowfile_worker/models.py +35 -28
  290. flowfile_worker/process_manager.py +2 -3
  291. flowfile_worker/routes.py +121 -90
  292. flowfile_worker/secrets.py +9 -6
  293. flowfile_worker/spawner.py +80 -49
  294. flowfile_worker/utils.py +3 -2
  295. shared/__init__.py +2 -7
  296. shared/storage_config.py +25 -13
  297. test_utils/postgres/commands.py +3 -2
  298. test_utils/postgres/fixtures.py +9 -9
  299. test_utils/s3/commands.py +1 -1
  300. test_utils/s3/data_generator.py +3 -4
  301. test_utils/s3/demo_data_generator.py +4 -7
  302. test_utils/s3/fixtures.py +7 -5
  303. tools/migrate/__init__.py +1 -1
  304. tools/migrate/__main__.py +16 -29
  305. tools/migrate/legacy_schemas.py +251 -190
  306. tools/migrate/migrate.py +193 -181
  307. tools/migrate/tests/conftest.py +1 -3
  308. tools/migrate/tests/test_migrate.py +36 -41
  309. tools/migrate/tests/test_migration_e2e.py +28 -29
  310. tools/migrate/tests/test_node_migrations.py +50 -20
  311. flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
  312. flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
  313. flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
  314. flowfile/web/static/assets/Filter-9b6d08db.js +0 -164
  315. flowfile/web/static/assets/Filter-f62091b3.css +0 -20
  316. flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
  317. flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
  318. flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
  319. flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
  320. flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
  321. flowfile/web/static/assets/nodeInput-5d0d6b79.js +0 -41
  322. flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
  323. flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
  324. flowfile/web/static/assets/secretApi-68435402.js +0 -46
  325. flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
  326. flowfile-0.5.1.dist-info/RECORD +0 -388
  327. {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +0 -0
  328. {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +0 -0
  329. {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
tools/migrate/migrate.py CHANGED
@@ -3,10 +3,9 @@ Migration logic for converting old flowfile pickles to new YAML format.
3
3
  """
4
4
 
5
5
  import pickle
6
- from dataclasses import fields, is_dataclass, asdict
6
+ from dataclasses import asdict, fields, is_dataclass
7
7
  from pathlib import Path
8
- from typing import Any, Dict, List, Optional
9
- import sys
8
+ from typing import Any
10
9
 
11
10
  try:
12
11
  import yaml
@@ -27,28 +26,28 @@ class LegacyUnpickler(pickle.Unpickler):
27
26
  # ONLY these classes changed from @dataclass to BaseModel
28
27
  # These are all from flowfile_core/schemas/transform_schema.py
29
28
  DATACLASS_TO_PYDANTIC = {
30
- 'SelectInput',
31
- 'FieldInput',
32
- 'FunctionInput',
33
- 'BasicFilter',
34
- 'FilterInput',
35
- 'SelectInputs',
36
- 'JoinInputs',
37
- 'JoinMap',
38
- 'CrossJoinInput',
39
- 'JoinInput',
40
- 'FuzzyMatchInput',
41
- 'AggColl',
42
- 'GroupByInput',
43
- 'PivotInput',
44
- 'SortByInput',
45
- 'RecordIdInput',
46
- 'TextToRowsInput',
47
- 'UnpivotInput',
48
- 'UnionInput',
49
- 'UniqueInput',
50
- 'GraphSolverInput',
51
- 'PolarsCodeInput',
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",
52
51
  }
53
52
 
54
53
  def find_class(self, module: str, name: str):
@@ -71,7 +70,7 @@ def load_legacy_flowfile(path: Path) -> Any:
71
70
  Returns:
72
71
  The deserialized FlowInformation object (as legacy dataclass)
73
72
  """
74
- with open(path, 'rb') as f:
73
+ with open(path, "rb") as f:
75
74
  return LegacyUnpickler(f).load()
76
75
 
77
76
 
@@ -111,7 +110,7 @@ def convert_to_dict(obj: Any, _seen: set = None) -> Any:
111
110
 
112
111
  try:
113
112
  # Handle Pydantic models FIRST (check for model_dump method)
114
- if hasattr(obj, 'model_dump') and callable(obj.model_dump):
113
+ if hasattr(obj, "model_dump") and callable(obj.model_dump):
115
114
  try:
116
115
  data = obj.model_dump()
117
116
  # Recursively convert any nested structures
@@ -150,9 +149,8 @@ def convert_to_dict(obj: Any, _seen: set = None) -> Any:
150
149
  return str(obj)
151
150
 
152
151
  # Handle objects with __dict__ (generic fallback)
153
- if hasattr(obj, '__dict__'):
154
- return {k: convert_to_dict(v, _seen) for k, v in obj.__dict__.items()
155
- if not k.startswith('_')}
152
+ if hasattr(obj, "__dict__"):
153
+ return {k: convert_to_dict(v, _seen) for k, v in obj.__dict__.items() if not k.startswith("_")}
156
154
 
157
155
  # Fallback: try to convert to string
158
156
  return str(obj)
@@ -161,7 +159,7 @@ def convert_to_dict(obj: Any, _seen: set = None) -> Any:
161
159
  _seen.discard(obj_id)
162
160
 
163
161
 
164
- def transform_to_new_schema(data: Dict) -> Dict:
162
+ def transform_to_new_schema(data: dict) -> dict:
165
163
  """
166
164
  Transform the legacy schema structure to the new FlowfileData format.
167
165
 
@@ -176,39 +174,39 @@ def transform_to_new_schema(data: Dict) -> Dict:
176
174
  Returns:
177
175
  Transformed dict ready for YAML serialization (FlowfileData format)
178
176
  """
179
- node_starts = set(data.get('node_starts', []))
177
+ node_starts = set(data.get("node_starts", []))
180
178
 
181
179
  result = {
182
- 'flowfile_version': '2.0',
183
- 'flowfile_id': data.get('flow_id', 1),
184
- 'flowfile_name': data.get('flow_name', ''),
185
- 'flowfile_settings': _transform_flow_settings(data.get('flow_settings', {})),
186
- 'nodes': _transform_nodes(data.get('data', {}), node_starts),
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),
187
185
  }
188
186
 
189
187
  return result
190
188
 
191
189
 
192
- def _transform_flow_settings(settings: Dict) -> Dict:
190
+ def _transform_flow_settings(settings: dict) -> dict:
193
191
  """Transform flow settings to FlowfileSettings format."""
194
192
  if not settings:
195
193
  return {
196
- 'execution_mode': 'Development',
197
- 'execution_location': 'local',
198
- 'auto_save': False,
199
- 'show_detailed_progress': True,
194
+ "execution_mode": "Development",
195
+ "execution_location": "local",
196
+ "auto_save": False,
197
+ "show_detailed_progress": True,
200
198
  }
201
199
 
202
200
  return {
203
- 'description': settings.get('description'),
204
- 'execution_mode': settings.get('execution_mode', 'Development'),
205
- 'execution_location': settings.get('execution_location', 'local'),
206
- 'auto_save': settings.get('auto_save', False),
207
- 'show_detailed_progress': settings.get('show_detailed_progress', True),
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),
208
206
  }
209
207
 
210
208
 
211
- def _transform_nodes(nodes_data: Dict, node_starts: set) -> List[Dict]:
209
+ def _transform_nodes(nodes_data: dict, node_starts: set) -> list[dict]:
212
210
  """Transform nodes dict to FlowfileNode list format."""
213
211
  nodes = []
214
212
 
@@ -216,34 +214,34 @@ def _transform_nodes(nodes_data: Dict, node_starts: set) -> List[Dict]:
216
214
  if not isinstance(node_info, dict):
217
215
  node_info = convert_to_dict(node_info)
218
216
 
219
- actual_node_id = node_info.get('id', node_id)
217
+ actual_node_id = node_info.get("id", node_id)
220
218
 
221
219
  node = {
222
- 'id': actual_node_id,
223
- 'type': node_info.get('type', ''),
224
- 'is_start_node': actual_node_id in node_starts,
225
- 'description': node_info.get('description', ''),
226
- 'x_position': int(node_info.get('x_position', 0) or 0),
227
- 'y_position': int(node_info.get('y_position', 0) or 0),
228
- 'left_input_id': node_info.get('left_input_id'),
229
- 'right_input_id': node_info.get('right_input_id'),
230
- 'input_ids': node_info.get('input_ids', []),
231
- 'outputs': node_info.get('outputs', []),
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", []),
232
230
  }
233
231
 
234
232
  # Transform settings based on node type
235
- setting_input = node_info.get('setting_input', {})
233
+ setting_input = node_info.get("setting_input", {})
236
234
  if setting_input:
237
235
  if not isinstance(setting_input, dict):
238
236
  setting_input = convert_to_dict(setting_input)
239
- node['setting_input'] = _transform_node_settings(node['type'], setting_input)
237
+ node["setting_input"] = _transform_node_settings(node["type"], setting_input)
240
238
 
241
239
  nodes.append(node)
242
240
 
243
241
  return nodes
244
242
 
245
243
 
246
- def _transform_node_settings(node_type: str, settings: Dict) -> Dict:
244
+ def _transform_node_settings(node_type: str, settings: dict) -> dict:
247
245
  """Transform node-specific settings to new format.
248
246
 
249
247
  Handles structural changes for various node types:
@@ -254,72 +252,85 @@ def _transform_node_settings(node_type: str, settings: Dict) -> Dict:
254
252
  - join/fuzzy_match: Handle JoinInput/FuzzyMatchInput changes
255
253
  """
256
254
  # Remove common fields that are stored elsewhere
257
- settings = {k: v for k, v in settings.items()
258
- if k not in ('flow_id', 'node_id', 'pos_x', 'pos_y', 'is_setup',
259
- 'description', 'cache_results', 'user_id', 'is_flow_output',
260
- 'is_user_defined')}
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
+ }
261
272
 
262
273
  # Handle specific node types
263
- if node_type == 'read':
274
+ if node_type == "read":
264
275
  return _transform_read_settings(settings)
265
- elif node_type == 'output':
276
+ elif node_type == "output":
266
277
  return _transform_output_settings(settings)
267
- elif node_type == 'polars_code':
278
+ elif node_type == "polars_code":
268
279
  return _transform_polars_code_settings(settings)
269
- elif node_type == 'select':
280
+ elif node_type == "select":
270
281
  return _transform_select_settings(settings)
271
- elif node_type in ('join', 'fuzzy_match', 'cross_join'):
282
+ elif node_type in ("join", "fuzzy_match", "cross_join"):
272
283
  return _transform_join_settings(settings)
273
284
 
274
285
  return settings
275
286
 
276
287
 
277
- def _transform_select_settings(settings: Dict) -> Dict:
288
+ def _transform_select_settings(settings: dict) -> dict:
278
289
  """Transform NodeSelect settings - ensure all fields exist."""
279
290
  # Ensure sorted_by field exists (added in new version)
280
- if 'sorted_by' not in settings:
281
- settings['sorted_by'] = 'none'
291
+ if "sorted_by" not in settings:
292
+ settings["sorted_by"] = "none"
282
293
 
283
294
  # Ensure select_input items have position field
284
- select_input = settings.get('select_input', [])
295
+ select_input = settings.get("select_input", [])
285
296
  if isinstance(select_input, list):
286
297
  for i, item in enumerate(select_input):
287
- if isinstance(item, dict) and item.get('position') is None:
288
- item['position'] = i
298
+ if isinstance(item, dict) and item.get("position") is None:
299
+ item["position"] = i
289
300
 
290
301
  return settings
291
302
 
292
303
 
293
- def _transform_join_settings(settings: Dict) -> Dict:
304
+ def _transform_join_settings(settings: dict) -> dict:
294
305
  """Transform join-related node settings.
295
306
 
296
307
  Handles migration of old JoinInput where left_select/right_select could be None.
297
308
  New schema requires these to be JoinInputs with renames list.
298
309
  """
299
310
  # Handle join_input transformation
300
- join_input = settings.get('join_input') or settings.get('cross_join_input')
311
+ join_input = settings.get("join_input") or settings.get("cross_join_input")
301
312
  if join_input and isinstance(join_input, dict):
302
313
  # ADD DEFAULT EMPTY JoinInputs IF MISSING (required in new schema)
303
- for side in ['left_select', 'right_select']:
314
+ for side in ["left_select", "right_select"]:
304
315
  if join_input.get(side) is None:
305
- join_input[side] = {'renames': []}
316
+ join_input[side] = {"renames": []}
306
317
 
307
318
  select = join_input.get(side)
308
319
  if select and isinstance(select, dict):
309
320
  # Ensure renames key exists
310
- if 'renames' not in select:
311
- select['renames'] = []
321
+ if "renames" not in select:
322
+ select["renames"] = []
312
323
 
313
- renames = select.get('renames', [])
324
+ renames = select.get("renames", [])
314
325
  if isinstance(renames, list):
315
326
  for i, item in enumerate(renames):
316
- if isinstance(item, dict) and item.get('position') is None:
317
- item['position'] = i
327
+ if isinstance(item, dict) and item.get("position") is None:
328
+ item["position"] = i
318
329
 
319
330
  return settings
320
331
 
321
332
 
322
- def _transform_read_settings(settings: Dict) -> Dict:
333
+ def _transform_read_settings(settings: dict) -> dict:
323
334
  """Transform NodeRead settings - extract table_settings from old flat structure.
324
335
 
325
336
  OLD structure (flat):
@@ -338,85 +349,85 @@ def _transform_read_settings(settings: Dict) -> Dict:
338
349
  delimiter: ","
339
350
  encoding: "utf-8"
340
351
  """
341
- received_file = settings.get('received_file', {})
352
+ received_file = settings.get("received_file", {})
342
353
  if not received_file:
343
354
  return settings
344
355
 
345
356
  # Check if already transformed (has table_settings)
346
- if 'table_settings' in received_file and isinstance(received_file['table_settings'], dict):
357
+ if "table_settings" in received_file and isinstance(received_file["table_settings"], dict):
347
358
  return settings
348
359
 
349
- file_type = received_file.get('file_type', 'csv')
360
+ file_type = received_file.get("file_type", "csv")
350
361
 
351
362
  # Build table_settings based on file_type, extracting from flat structure
352
- if file_type == 'csv':
363
+ if file_type == "csv":
353
364
  table_settings = {
354
- 'file_type': 'csv',
355
- 'reference': received_file.get('reference', ''),
356
- 'starting_from_line': received_file.get('starting_from_line', 0),
357
- 'delimiter': received_file.get('delimiter', ','),
358
- 'has_headers': received_file.get('has_headers', True),
359
- 'encoding': received_file.get('encoding', 'utf-8') or 'utf-8',
360
- 'parquet_ref': received_file.get('parquet_ref'),
361
- 'row_delimiter': received_file.get('row_delimiter', '\n'),
362
- 'quote_char': received_file.get('quote_char', '"'),
363
- 'infer_schema_length': received_file.get('infer_schema_length', 10000),
364
- 'truncate_ragged_lines': received_file.get('truncate_ragged_lines', False),
365
- 'ignore_errors': received_file.get('ignore_errors', False),
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),
366
377
  }
367
- elif file_type == 'json':
378
+ elif file_type == "json":
368
379
  table_settings = {
369
- 'file_type': 'json',
370
- 'reference': received_file.get('reference', ''),
371
- 'starting_from_line': received_file.get('starting_from_line', 0),
372
- 'delimiter': received_file.get('delimiter', ','),
373
- 'has_headers': received_file.get('has_headers', True),
374
- 'encoding': received_file.get('encoding', 'utf-8') or 'utf-8',
375
- 'parquet_ref': received_file.get('parquet_ref'),
376
- 'row_delimiter': received_file.get('row_delimiter', '\n'),
377
- 'quote_char': received_file.get('quote_char', '"'),
378
- 'infer_schema_length': received_file.get('infer_schema_length', 10000),
379
- 'truncate_ragged_lines': received_file.get('truncate_ragged_lines', False),
380
- 'ignore_errors': received_file.get('ignore_errors', False),
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),
381
392
  }
382
- elif file_type == 'excel':
393
+ elif file_type == "excel":
383
394
  table_settings = {
384
- 'file_type': 'excel',
385
- 'sheet_name': received_file.get('sheet_name'),
386
- 'start_row': received_file.get('start_row', 0),
387
- 'start_column': received_file.get('start_column', 0),
388
- 'end_row': received_file.get('end_row', 0),
389
- 'end_column': received_file.get('end_column', 0),
390
- 'has_headers': received_file.get('has_headers', True),
391
- 'type_inference': received_file.get('type_inference', False),
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),
392
403
  }
393
- elif file_type == 'parquet':
394
- table_settings = {'file_type': 'parquet'}
404
+ elif file_type == "parquet":
405
+ table_settings = {"file_type": "parquet"}
395
406
  else:
396
407
  # Unknown file type - try to preserve what we can
397
- table_settings = {'file_type': file_type or 'csv'}
408
+ table_settings = {"file_type": file_type or "csv"}
398
409
 
399
410
  # Build new structure with metadata + nested table_settings
400
411
  return {
401
- 'received_file': {
412
+ "received_file": {
402
413
  # Metadata fields (preserved from old structure)
403
- 'id': received_file.get('id'),
404
- 'name': received_file.get('name'),
405
- 'path': received_file.get('path', ''),
406
- 'directory': received_file.get('directory'),
407
- 'analysis_file_available': received_file.get('analysis_file_available', False),
408
- 'status': received_file.get('status'),
409
- 'fields': received_file.get('fields', []),
410
- 'abs_file_path': received_file.get('abs_file_path'),
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"),
411
422
  # New discriminator field
412
- 'file_type': file_type,
423
+ "file_type": file_type,
413
424
  # Nested table settings
414
- 'table_settings': table_settings,
425
+ "table_settings": table_settings,
415
426
  }
416
427
  }
417
428
 
418
429
 
419
- def _transform_output_settings(settings: Dict) -> Dict:
430
+ def _transform_output_settings(settings: dict) -> dict:
420
431
  """Transform NodeOutput settings - consolidate separate table settings into single field.
421
432
 
422
433
  OLD structure:
@@ -434,80 +445,80 @@ def _transform_output_settings(settings: Dict) -> Dict:
434
445
  delimiter: ","
435
446
  encoding: "utf-8"
436
447
  """
437
- output_settings = settings.get('output_settings', {})
448
+ output_settings = settings.get("output_settings", {})
438
449
  if not output_settings:
439
450
  return settings
440
451
 
441
452
  # Check if already transformed
442
- if 'table_settings' in output_settings and isinstance(output_settings['table_settings'], dict):
453
+ if "table_settings" in output_settings and isinstance(output_settings["table_settings"], dict):
443
454
  return settings
444
455
 
445
- file_type = output_settings.get('file_type', 'csv')
456
+ file_type = output_settings.get("file_type", "csv")
446
457
 
447
458
  # Build table_settings from old separate fields
448
- if file_type == 'csv':
449
- old_csv = output_settings.get('output_csv_table', {}) or {}
459
+ if file_type == "csv":
460
+ old_csv = output_settings.get("output_csv_table", {}) or {}
450
461
  table_settings = {
451
- 'file_type': 'csv',
452
- 'delimiter': old_csv.get('delimiter', ','),
453
- 'encoding': old_csv.get('encoding', 'utf-8'),
462
+ "file_type": "csv",
463
+ "delimiter": old_csv.get("delimiter", ","),
464
+ "encoding": old_csv.get("encoding", "utf-8"),
454
465
  }
455
- elif file_type == 'excel':
456
- old_excel = output_settings.get('output_excel_table', {}) or {}
466
+ elif file_type == "excel":
467
+ old_excel = output_settings.get("output_excel_table", {}) or {}
457
468
  table_settings = {
458
- 'file_type': 'excel',
459
- 'sheet_name': old_excel.get('sheet_name', 'Sheet1'),
469
+ "file_type": "excel",
470
+ "sheet_name": old_excel.get("sheet_name", "Sheet1"),
460
471
  }
461
- elif file_type == 'parquet':
462
- table_settings = {'file_type': 'parquet'}
472
+ elif file_type == "parquet":
473
+ table_settings = {"file_type": "parquet"}
463
474
  else:
464
- table_settings = {'file_type': file_type or 'csv'}
475
+ table_settings = {"file_type": file_type or "csv"}
465
476
 
466
477
  return {
467
- 'output_settings': {
468
- 'name': output_settings.get('name', ''),
469
- 'directory': output_settings.get('directory', ''),
470
- 'file_type': file_type,
471
- 'fields': output_settings.get('fields', []),
472
- 'write_mode': output_settings.get('write_mode', 'overwrite'),
473
- 'abs_file_path': output_settings.get('abs_file_path'),
474
- 'table_settings': table_settings,
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,
475
486
  }
476
487
  }
477
488
 
478
489
 
479
- def _transform_polars_code_settings(settings: Dict) -> Dict:
490
+ def _transform_polars_code_settings(settings: dict) -> dict:
480
491
  """Transform NodePolarsCode settings.
481
492
 
482
493
  Extracts polars_code from PolarsCodeInput and handles depending_on_id → depending_on_ids.
483
494
  """
484
- polars_code_input = settings.get('polars_code_input', {})
495
+ polars_code_input = settings.get("polars_code_input", {})
485
496
 
486
497
  # Extract the actual code
487
- polars_code = ''
498
+ polars_code = ""
488
499
  if isinstance(polars_code_input, dict):
489
- polars_code = polars_code_input.get('polars_code', '')
490
- elif hasattr(polars_code_input, 'polars_code'):
500
+ polars_code = polars_code_input.get("polars_code", "")
501
+ elif hasattr(polars_code_input, "polars_code"):
491
502
  polars_code = polars_code_input.polars_code
492
503
 
493
504
  # Handle depending_on_id → depending_on_ids migration
494
- depending_on_ids = settings.get('depending_on_ids', [])
505
+ depending_on_ids = settings.get("depending_on_ids", [])
495
506
  if not depending_on_ids or depending_on_ids == [-1]:
496
- old_id = settings.get('depending_on_id')
507
+ old_id = settings.get("depending_on_id")
497
508
  if old_id is not None and old_id != -1:
498
509
  depending_on_ids = [old_id]
499
510
  else:
500
511
  depending_on_ids = []
501
512
 
502
513
  return {
503
- 'polars_code_input': {
504
- 'polars_code': polars_code,
514
+ "polars_code_input": {
515
+ "polars_code": polars_code,
505
516
  },
506
- 'depending_on_ids': depending_on_ids,
517
+ "depending_on_ids": depending_on_ids,
507
518
  }
508
519
 
509
520
 
510
- def migrate_flowfile(input_path: Path, output_path: Path = None, format: str = 'yaml') -> Path:
521
+ def migrate_flowfile(input_path: Path, output_path: Path = None, format: str = "yaml") -> Path:
511
522
  """
512
523
  Migrate a single flowfile from pickle to YAML format.
513
524
 
@@ -519,12 +530,12 @@ def migrate_flowfile(input_path: Path, output_path: Path = None, format: str = '
519
530
  Returns:
520
531
  Path to the created output file
521
532
  """
522
- if format == 'yaml' and yaml is None:
533
+ if format == "yaml" and yaml is None:
523
534
  raise ImportError("PyYAML is required for YAML output. Install with: pip install pyyaml")
524
535
 
525
536
  # Determine output path
526
537
  if output_path is None:
527
- suffix = '.yaml' if format == 'yaml' else '.json'
538
+ suffix = ".yaml" if format == "yaml" else ".json"
528
539
  output_path = input_path.with_suffix(suffix)
529
540
 
530
541
  print(f"Loading: {input_path}")
@@ -541,18 +552,19 @@ def migrate_flowfile(input_path: Path, output_path: Path = None, format: str = '
541
552
  # Write output
542
553
  print(f"Writing: {output_path}")
543
554
 
544
- with open(output_path, 'w', encoding='utf-8') as f:
545
- if format == 'yaml':
555
+ with open(output_path, "w", encoding="utf-8") as f:
556
+ if format == "yaml":
546
557
  yaml.dump(transformed, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
547
558
  else:
548
559
  import json
560
+
549
561
  json.dump(transformed, f, indent=2, ensure_ascii=False)
550
562
 
551
563
  print(f"✓ Migrated: {input_path.name} → {output_path.name}")
552
564
  return output_path
553
565
 
554
566
 
555
- def migrate_directory(dir_path: Path, output_dir: Path = None, format: str = 'yaml') -> List[Path]:
567
+ def migrate_directory(dir_path: Path, output_dir: Path = None, format: str = "yaml") -> list[Path]:
556
568
  """
557
569
  Migrate all flowfiles in a directory.
558
570
 
@@ -567,7 +579,7 @@ def migrate_directory(dir_path: Path, output_dir: Path = None, format: str = 'ya
567
579
  output_dir = output_dir or dir_path
568
580
  output_dir.mkdir(parents=True, exist_ok=True)
569
581
 
570
- flowfiles = list(dir_path.glob('**/*.flowfile'))
582
+ flowfiles = list(dir_path.glob("**/*.flowfile"))
571
583
 
572
584
  if not flowfiles:
573
585
  print(f"No .flowfile files found in {dir_path}")
@@ -581,7 +593,7 @@ def migrate_directory(dir_path: Path, output_dir: Path = None, format: str = 'ya
581
593
  for flowfile in flowfiles:
582
594
  # Preserve directory structure
583
595
  relative = flowfile.relative_to(dir_path)
584
- suffix = '.yaml' if format == 'yaml' else '.json'
596
+ suffix = ".yaml" if format == "yaml" else ".json"
585
597
  output_path = output_dir / relative.with_suffix(suffix)
586
598
  output_path.parent.mkdir(parents=True, exist_ok=True)
587
599