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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. build_backends/main.py +25 -22
  2. build_backends/main_prd.py +10 -19
  3. flowfile/__init__.py +179 -73
  4. flowfile/__main__.py +10 -7
  5. flowfile/api.py +52 -59
  6. flowfile/web/__init__.py +14 -9
  7. flowfile/web/static/assets/AdminView-49392a9a.js +713 -0
  8. flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
  9. flowfile/web/static/assets/CloudConnectionView-36bcd6df.css +72 -0
  10. flowfile/web/static/assets/{CloudConnectionManager-d3248f8d.js → CloudConnectionView-f13f202b.js} +11 -11
  11. flowfile/web/static/assets/{CloudStorageReader-d65bf041.js → CloudStorageReader-0023d4a5.js} +10 -8
  12. flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
  13. flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
  14. flowfile/web/static/assets/{CloudStorageWriter-e83be3ed.js → CloudStorageWriter-8e781e11.js} +10 -8
  15. flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
  16. flowfile/web/static/assets/{ColumnSelector-cce661cf.js → ColumnSelector-8ad68ea9.js} +3 -5
  17. flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
  18. flowfile/web/static/assets/{ContextMenu-11a4652a.js → ContextMenu-31ee57f0.js} +3 -3
  19. flowfile/web/static/assets/{ContextMenu-160afb08.js → ContextMenu-69a74055.js} +3 -3
  20. flowfile/web/static/assets/{ContextMenu-cf18d2cc.js → ContextMenu-8e2051c6.js} +3 -3
  21. flowfile/web/static/assets/{ContextMenu-4c74eef1.css → ContextMenu-8ec1729e.css} +6 -6
  22. flowfile/web/static/assets/{ContextMenu-63cfa99b.css → ContextMenu-9b310c60.css} +6 -6
  23. flowfile/web/static/assets/{CrossJoin-d395d38c.js → CrossJoin-03df6938.js} +12 -10
  24. flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
  25. flowfile/web/static/assets/CustomNode-59e99a86.css +32 -0
  26. flowfile/web/static/assets/{CustomNode-b812dc0b.js → CustomNode-8479239b.js} +36 -24
  27. flowfile/web/static/assets/{DatabaseConnectionSettings-7000bf2c.js → DatabaseConnectionSettings-869e3efd.js} +5 -4
  28. flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-e91df89a.css} +13 -13
  29. flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-36898a00.css} +24 -24
  30. flowfile/web/static/assets/{DatabaseReader-4f035d0c.js → DatabaseReader-c58b9552.js} +25 -15
  31. flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
  32. flowfile/web/static/assets/{DatabaseManager-9662ec5b.js → DatabaseView-d26a9140.js} +11 -11
  33. flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
  34. flowfile/web/static/assets/{DatabaseWriter-f65dcd54.js → DatabaseWriter-4d05ddc7.js} +17 -10
  35. flowfile/web/static/assets/{designer-e3c150ec.css → DesignerView-a6d0ee84.css} +629 -538
  36. flowfile/web/static/assets/{designer-f3656d8c.js → DesignerView-e6f5c0e8.js} +1214 -3209
  37. flowfile/web/static/assets/{documentation-52b241e7.js → DocumentationView-2e78ef1b.js} +5 -5
  38. flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-fd46c656.css} +7 -7
  39. flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
  40. flowfile/web/static/assets/{ExploreData-94c43dfc.js → ExploreData-7b54caca.js} +18 -9
  41. flowfile/web/static/assets/{ExternalSource-ac04b3cc.js → ExternalSource-3fa399b2.js} +9 -7
  42. flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-47ab05a3.css} +17 -17
  43. flowfile/web/static/assets/Filter-7494ea97.css +48 -0
  44. flowfile/web/static/assets/Filter-8cbbdbf3.js +287 -0
  45. flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
  46. flowfile/web/static/assets/{Formula-71472193.js → Formula-aac42b1e.js} +13 -11
  47. flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
  48. flowfile/web/static/assets/{FuzzyMatch-b317f631.js → FuzzyMatch-cd9bbfca.js} +12 -10
  49. flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
  50. flowfile/web/static/assets/{GraphSolver-754a234f.js → GraphSolver-c7e6780e.js} +13 -11
  51. flowfile/web/static/assets/{GroupBy-6c6f9802.js → GroupBy-93c5d22b.js} +9 -7
  52. flowfile/web/static/assets/{GroupBy-b9505323.css → GroupBy-be7ac0bf.css} +10 -10
  53. flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
  54. flowfile/web/static/assets/{Join-a1b800be.js → Join-a19b2de2.js} +13 -11
  55. flowfile/web/static/assets/LoginView-0df4ed0a.js +134 -0
  56. flowfile/web/static/assets/LoginView-d325d632.css +172 -0
  57. flowfile/web/static/assets/ManualInput-3702e677.css +293 -0
  58. flowfile/web/static/assets/{ManualInput-a9640276.js → ManualInput-8d3374b2.js} +170 -116
  59. flowfile/web/static/assets/{MultiSelect-97213888.js → MultiSelect-ad1b6243.js} +2 -2
  60. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-6ffe088a.js → MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js} +1 -1
  61. flowfile/web/static/assets/NodeDesigner-40b647c9.js +2610 -0
  62. flowfile/web/static/assets/NodeDesigner-5f53be3f.css +1429 -0
  63. flowfile/web/static/assets/{NumericInput-e638088a.js → NumericInput-7100234c.js} +2 -2
  64. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-90eb2cba.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
  65. flowfile/web/static/assets/{Output-ddc9079f.css → Output-35e97000.css} +6 -6
  66. flowfile/web/static/assets/{Output-76750610.js → Output-f5efd2aa.js} +60 -38
  67. flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
  68. flowfile/web/static/assets/{Pivot-7814803f.js → Pivot-d981d23c.js} +11 -9
  69. flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
  70. flowfile/web/static/assets/{PivotValidation-f92137d2.js → PivotValidation-39386e95.js} +3 -3
  71. flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
  72. flowfile/web/static/assets/{PivotValidation-76dd431a.js → PivotValidation-63de1f73.js} +3 -3
  73. flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
  74. flowfile/web/static/assets/{PolarsCode-889c3008.js → PolarsCode-f9d69217.js} +18 -9
  75. flowfile/web/static/assets/PopOver-b22f049e.js +939 -0
  76. flowfile/web/static/assets/PopOver-d96599db.css +33 -0
  77. flowfile/web/static/assets/{Read-6b17491f.css → Read-36e7bd51.css} +12 -12
  78. flowfile/web/static/assets/{Read-637b72a7.js → Read-aec2e377.js} +83 -105
  79. flowfile/web/static/assets/{RecordCount-2b050c41.js → RecordCount-78ed6845.js} +6 -4
  80. flowfile/web/static/assets/{RecordId-81df7784.js → RecordId-2156e890.js} +8 -6
  81. flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
  82. flowfile/web/static/assets/{SQLQueryComponent-88dcfe53.js → SQLQueryComponent-48c72f5b.js} +3 -3
  83. flowfile/web/static/assets/{Sample-258ad2a9.js → Sample-1352ca74.js} +6 -4
  84. flowfile/web/static/assets/SecretSelector-22b5ff89.js +113 -0
  85. flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
  86. flowfile/web/static/assets/{SecretManager-2a2cb7e2.js → SecretsView-17df66ee.js} +35 -36
  87. flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
  88. flowfile/web/static/assets/{Select-850215fd.js → Select-0aee4c54.js} +9 -7
  89. flowfile/web/static/assets/{SettingsSection-55bae608.js → SettingsSection-0784e157.js} +3 -3
  90. flowfile/web/static/assets/{SettingsSection-71e6b7e3.css → SettingsSection-07fbbc39.css} +4 -4
  91. flowfile/web/static/assets/{SettingsSection-5c696bee.css → SettingsSection-26fe48d4.css} +4 -4
  92. flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
  93. flowfile/web/static/assets/{SettingsSection-0e8d9123.js → SettingsSection-cd341bb6.js} +3 -3
  94. flowfile/web/static/assets/{SettingsSection-29b4fa6b.js → SettingsSection-f2002a6d.js} +3 -3
  95. flowfile/web/static/assets/{SingleSelect-bebd408b.js → SingleSelect-460cc0ea.js} +2 -2
  96. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-6093741c.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
  97. flowfile/web/static/assets/{SliderInput-6a05ab61.js → SliderInput-5d926864.js} +7 -4
  98. flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
  99. flowfile/web/static/assets/{Sort-10ab48ed.js → Sort-3cdc971b.js} +9 -7
  100. flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
  101. flowfile/web/static/assets/{TextInput-df9d6259.js → TextInput-a2d0bfbd.js} +2 -2
  102. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-000e1178.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
  103. flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
  104. flowfile/web/static/assets/{TextToRows-6c2d93d8.js → TextToRows-918945f7.js} +11 -10
  105. flowfile/web/static/assets/{ToggleSwitch-0ff7ac52.js → ToggleSwitch-f0ef5196.js} +2 -2
  106. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-c6dc3029.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
  107. flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
  108. flowfile/web/static/assets/{UnavailableFields-1bab97cb.js → UnavailableFields-bdad6144.js} +4 -4
  109. flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
  110. flowfile/web/static/assets/{Union-b563478a.js → Union-e8ab8c86.js} +8 -6
  111. flowfile/web/static/assets/{Unique-f90db5db.js → Unique-8cd4f976.js} +13 -22
  112. flowfile/web/static/assets/{Sort-3643d625.css → Unique-9fb2f567.css} +10 -10
  113. flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-710a2948.css} +7 -7
  114. flowfile/web/static/assets/{Unpivot-bcb0025f.js → Unpivot-8da14095.js} +10 -8
  115. flowfile/web/static/assets/{UnpivotValidation-c4e73b04.js → UnpivotValidation-6f7d89ff.js} +3 -3
  116. flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
  117. flowfile/web/static/assets/{VueGraphicWalker-bb8535e2.js → VueGraphicWalker-3fb312e1.js} +4 -4
  118. flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
  119. flowfile/web/static/assets/{api-4c8e3822.js → api-24483f0d.js} +1 -1
  120. flowfile/web/static/assets/{api-2d6adc4f.js → api-8b81fa73.js} +1 -1
  121. flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
  122. flowfile/web/static/assets/{dropDown-1bca8a74.js → dropDown-ac0fda9d.js} +3 -3
  123. flowfile/web/static/assets/{fullEditor-2985687e.js → fullEditor-5497a84a.js} +11 -10
  124. flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-a0be62b3.css} +74 -62
  125. flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
  126. flowfile/web/static/assets/{genericNodeSettings-0476ba4e.js → genericNodeSettings-99014e1d.js} +5 -5
  127. flowfile/web/static/assets/index-07dda503.js +38 -0
  128. flowfile/web/static/assets/index-3ba44389.js +2696 -0
  129. flowfile/web/static/assets/{index-50508d4d.css → index-e6289dd0.css} +1945 -569
  130. flowfile/web/static/assets/{index-246f201c.js → index-fb6493ae.js} +41626 -40869
  131. flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
  132. flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
  133. flowfile/web/static/assets/{outputCsv-d686eeaf.js → outputCsv-8f8ba42d.js} +3 -3
  134. flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
  135. flowfile/web/static/assets/{outputExcel-8809ea2f.js → outputExcel-393f4fef.js} +3 -3
  136. flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
  137. flowfile/web/static/assets/{outputParquet-53ba645a.js → outputParquet-07c81f65.js} +4 -4
  138. flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
  139. flowfile/web/static/assets/{readCsv-053bf97b.js → readCsv-07f6d9ad.js} +21 -20
  140. flowfile/web/static/assets/{readCsv-bca3ed53.css → readCsv-3bfac4c3.css} +15 -15
  141. flowfile/web/static/assets/{readExcel-e1b381ea.css → readExcel-3db6b763.css} +13 -13
  142. flowfile/web/static/assets/{readExcel-ad531eab.js → readExcel-ed69bc8f.js} +10 -12
  143. flowfile/web/static/assets/{readParquet-cee068e2.css → readParquet-c5244ad5.css} +4 -4
  144. flowfile/web/static/assets/{readParquet-58e899a1.js → readParquet-e3ed4528.js} +4 -7
  145. flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
  146. flowfile/web/static/assets/{selectDynamic-b38de2ba.js → selectDynamic-80b92899.js} +5 -5
  147. flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
  148. flowfile/web/static/assets/{vue-codemirror.esm-db9b8936.js → vue-codemirror.esm-0965f39f.js} +31 -637
  149. flowfile/web/static/assets/{vue-content-loader.es-b5f3ac30.js → vue-content-loader.es-c506ad97.js} +1 -1
  150. flowfile/web/static/index.html +2 -2
  151. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +4 -4
  152. flowfile-0.5.3.dist-info/RECORD +402 -0
  153. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +1 -1
  154. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +1 -0
  155. flowfile_core/__init__.py +13 -3
  156. flowfile_core/auth/jwt.py +51 -16
  157. flowfile_core/auth/models.py +32 -7
  158. flowfile_core/auth/password.py +89 -0
  159. flowfile_core/auth/secrets.py +8 -6
  160. flowfile_core/configs/__init__.py +9 -7
  161. flowfile_core/configs/flow_logger.py +15 -14
  162. flowfile_core/configs/node_store/__init__.py +72 -4
  163. flowfile_core/configs/node_store/nodes.py +155 -172
  164. flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
  165. flowfile_core/configs/settings.py +28 -15
  166. flowfile_core/database/connection.py +7 -6
  167. flowfile_core/database/init_db.py +96 -2
  168. flowfile_core/database/models.py +3 -1
  169. flowfile_core/fileExplorer/__init__.py +17 -0
  170. flowfile_core/fileExplorer/funcs.py +123 -57
  171. flowfile_core/fileExplorer/utils.py +10 -11
  172. flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
  173. flowfile_core/flowfile/analytics/analytics_processor.py +27 -24
  174. flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
  175. flowfile_core/flowfile/analytics/utils.py +1 -1
  176. flowfile_core/flowfile/code_generator/code_generator.py +391 -279
  177. flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
  178. flowfile_core/flowfile/connection_manager/models.py +1 -1
  179. flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
  180. flowfile_core/flowfile/database_connection_manager/models.py +1 -1
  181. flowfile_core/flowfile/extensions.py +17 -12
  182. flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
  183. flowfile_core/flowfile/flow_data_engine/create/funcs.py +152 -103
  184. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +526 -477
  185. flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
  186. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
  187. flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
  188. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
  189. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
  190. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +43 -32
  191. flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
  192. flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
  193. flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +15 -11
  194. flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
  195. flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
  196. flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
  197. flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
  198. flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
  199. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
  200. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +360 -191
  201. flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
  202. flowfile_core/flowfile/flow_data_engine/utils.py +101 -67
  203. flowfile_core/flowfile/flow_graph.py +1011 -561
  204. flowfile_core/flowfile/flow_graph_utils.py +31 -49
  205. flowfile_core/flowfile/flow_node/flow_node.py +332 -232
  206. flowfile_core/flowfile/flow_node/models.py +54 -41
  207. flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
  208. flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
  209. flowfile_core/flowfile/handler.py +82 -32
  210. flowfile_core/flowfile/manage/compatibility_enhancements.py +493 -47
  211. flowfile_core/flowfile/manage/io_flowfile.py +391 -0
  212. flowfile_core/flowfile/node_designer/__init__.py +15 -13
  213. flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
  214. flowfile_core/flowfile/node_designer/custom_node.py +162 -36
  215. flowfile_core/flowfile/node_designer/ui_components.py +136 -35
  216. flowfile_core/flowfile/schema_callbacks.py +77 -54
  217. flowfile_core/flowfile/setting_generator/__init__.py +0 -1
  218. flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
  219. flowfile_core/flowfile/setting_generator/settings.py +72 -55
  220. flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
  221. flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
  222. flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
  223. flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
  224. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
  225. flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
  226. flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
  227. flowfile_core/flowfile/util/calculate_layout.py +9 -13
  228. flowfile_core/flowfile/util/execution_orderer.py +25 -17
  229. flowfile_core/flowfile/util/node_skipper.py +4 -4
  230. flowfile_core/flowfile/utils.py +19 -21
  231. flowfile_core/main.py +26 -19
  232. flowfile_core/routes/auth.py +284 -11
  233. flowfile_core/routes/cloud_connections.py +25 -25
  234. flowfile_core/routes/logs.py +21 -29
  235. flowfile_core/routes/public.py +3 -3
  236. flowfile_core/routes/routes.py +77 -43
  237. flowfile_core/routes/secrets.py +25 -27
  238. flowfile_core/routes/user_defined_components.py +483 -4
  239. flowfile_core/run_lock.py +0 -1
  240. flowfile_core/schemas/__init__.py +4 -6
  241. flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
  242. flowfile_core/schemas/cloud_storage_schemas.py +59 -55
  243. flowfile_core/schemas/input_schema.py +398 -154
  244. flowfile_core/schemas/output_model.py +50 -35
  245. flowfile_core/schemas/schemas.py +207 -67
  246. flowfile_core/schemas/transform_schema.py +1360 -435
  247. flowfile_core/schemas/yaml_types.py +117 -0
  248. flowfile_core/secret_manager/secret_manager.py +17 -13
  249. flowfile_core/{flowfile/node_designer/data_types.py → types.py} +33 -3
  250. flowfile_core/utils/arrow_reader.py +7 -6
  251. flowfile_core/utils/excel_file_manager.py +3 -3
  252. flowfile_core/utils/fileManager.py +7 -7
  253. flowfile_core/utils/fl_executor.py +8 -10
  254. flowfile_core/utils/utils.py +4 -4
  255. flowfile_core/utils/validate_setup.py +5 -4
  256. flowfile_frame/__init__.py +107 -50
  257. flowfile_frame/adapters.py +2 -9
  258. flowfile_frame/adding_expr.py +73 -32
  259. flowfile_frame/cloud_storage/frame_helpers.py +27 -23
  260. flowfile_frame/cloud_storage/secret_manager.py +12 -26
  261. flowfile_frame/config.py +2 -5
  262. flowfile_frame/expr.py +311 -218
  263. flowfile_frame/expr.pyi +160 -159
  264. flowfile_frame/expr_name.py +23 -23
  265. flowfile_frame/flow_frame.py +581 -489
  266. flowfile_frame/flow_frame.pyi +123 -104
  267. flowfile_frame/flow_frame_methods.py +236 -252
  268. flowfile_frame/group_frame.py +50 -20
  269. flowfile_frame/join.py +2 -2
  270. flowfile_frame/lazy.py +129 -87
  271. flowfile_frame/lazy_methods.py +83 -30
  272. flowfile_frame/list_name_space.py +55 -50
  273. flowfile_frame/selectors.py +148 -68
  274. flowfile_frame/series.py +9 -7
  275. flowfile_frame/utils.py +19 -21
  276. flowfile_worker/__init__.py +12 -4
  277. flowfile_worker/configs.py +11 -19
  278. flowfile_worker/create/__init__.py +14 -27
  279. flowfile_worker/create/funcs.py +143 -94
  280. flowfile_worker/create/models.py +139 -68
  281. flowfile_worker/create/pl_types.py +14 -15
  282. flowfile_worker/create/read_excel_tables.py +34 -41
  283. flowfile_worker/create/utils.py +22 -19
  284. flowfile_worker/external_sources/s3_source/main.py +18 -51
  285. flowfile_worker/external_sources/s3_source/models.py +34 -27
  286. flowfile_worker/external_sources/sql_source/main.py +8 -5
  287. flowfile_worker/external_sources/sql_source/models.py +13 -9
  288. flowfile_worker/flow_logger.py +10 -8
  289. flowfile_worker/funcs.py +214 -155
  290. flowfile_worker/main.py +11 -17
  291. flowfile_worker/models.py +35 -28
  292. flowfile_worker/process_manager.py +2 -3
  293. flowfile_worker/routes.py +121 -93
  294. flowfile_worker/secrets.py +9 -6
  295. flowfile_worker/spawner.py +80 -49
  296. flowfile_worker/utils.py +3 -2
  297. shared/__init__.py +2 -7
  298. shared/storage_config.py +25 -13
  299. test_utils/postgres/commands.py +3 -2
  300. test_utils/postgres/fixtures.py +9 -9
  301. test_utils/s3/commands.py +1 -1
  302. test_utils/s3/data_generator.py +3 -4
  303. test_utils/s3/demo_data_generator.py +4 -7
  304. test_utils/s3/fixtures.py +7 -5
  305. tools/migrate/README.md +56 -0
  306. tools/migrate/__init__.py +12 -0
  307. tools/migrate/__main__.py +118 -0
  308. tools/migrate/legacy_schemas.py +682 -0
  309. tools/migrate/migrate.py +610 -0
  310. tools/migrate/tests/__init__.py +0 -0
  311. tools/migrate/tests/conftest.py +21 -0
  312. tools/migrate/tests/test_migrate.py +622 -0
  313. tools/migrate/tests/test_migration_e2e.py +1009 -0
  314. tools/migrate/tests/test_node_migrations.py +843 -0
  315. flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
  316. flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
  317. flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
  318. flowfile/web/static/assets/Filter-812dcbca.js +0 -164
  319. flowfile/web/static/assets/Filter-f62091b3.css +0 -20
  320. flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
  321. flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
  322. flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
  323. flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
  324. flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
  325. flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
  326. flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
  327. flowfile/web/static/assets/secretApi-538058f3.js +0 -46
  328. flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
  329. flowfile-0.4.1.dist-info/RECORD +0 -376
  330. flowfile_core/flowfile/manage/open_flowfile.py +0 -143
  331. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
  332. /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
@@ -1,22 +1,34 @@
1
- from typing import List, Union, Callable, Any, Optional, Generator, Literal
2
- from flowfile_core.configs import logger
3
- from flowfile_core.flowfile.flow_data_engine.flow_file_column.main import FlowfileColumn
4
- from flowfile_core.flowfile.flow_data_engine.flow_data_engine import FlowDataEngine
5
- from flowfile_core.utils.arrow_reader import get_read_top_n
6
- from flowfile_core.schemas import input_schema, schemas
7
- from flowfile_core.configs.flow_logger import NodeLogger
8
-
9
- from flowfile_core.schemas.output_model import TableExample, FileColumn, NodeData
10
- from flowfile_core.flowfile.utils import get_hash
11
- from flowfile_core.configs import node_store
12
- from flowfile_core.flowfile.setting_generator import setting_generator, setting_updator
1
+ from collections.abc import Callable, Generator
13
2
  from time import sleep
3
+ from typing import Any, Literal, Optional
4
+
5
+ from flowfile_core.configs import logger, node_store
6
+ from flowfile_core.configs.flow_logger import NodeLogger
7
+ from flowfile_core.flowfile.flow_data_engine.flow_data_engine import FlowDataEngine
8
+ from flowfile_core.flowfile.flow_data_engine.flow_file_column.main import FlowfileColumn
14
9
  from flowfile_core.flowfile.flow_data_engine.subprocess_operations import (
15
- ExternalDfFetcher, ExternalSampler, clear_task_from_worker, results_exists, get_external_df_result,
16
- ExternalDatabaseFetcher, ExternalDatabaseWriter, ExternalCloudWriter)
17
- from flowfile_core.flowfile.flow_node.models import (NodeStepSettings, NodeStepInputs, NodeSchemaInformation,
18
- NodeStepStats, NodeResults)
10
+ ExternalCloudWriter,
11
+ ExternalDatabaseFetcher,
12
+ ExternalDatabaseWriter,
13
+ ExternalDfFetcher,
14
+ ExternalSampler,
15
+ clear_task_from_worker,
16
+ get_external_df_result,
17
+ results_exists,
18
+ )
19
+ from flowfile_core.flowfile.flow_node.models import (
20
+ NodeResults,
21
+ NodeSchemaInformation,
22
+ NodeStepInputs,
23
+ NodeStepSettings,
24
+ NodeStepStats,
25
+ )
19
26
  from flowfile_core.flowfile.flow_node.schema_callback import SingleExecutionFuture
27
+ from flowfile_core.flowfile.setting_generator import setting_generator, setting_updator
28
+ from flowfile_core.flowfile.utils import get_hash
29
+ from flowfile_core.schemas import input_schema, schemas
30
+ from flowfile_core.schemas.output_model import FileColumn, NodeData, TableExample
31
+ from flowfile_core.utils.arrow_reader import get_read_top_n
20
32
 
21
33
 
22
34
  class FlowNode:
@@ -25,6 +37,7 @@ class FlowNode:
25
37
  This class manages the node's state, its data processing function,
26
38
  and its connections to other nodes within the graph.
27
39
  """
40
+
28
41
  parent_uuid: str
29
42
  node_type: str
30
43
  node_template: node_store.NodeTemplate
@@ -34,31 +47,38 @@ class FlowNode:
34
47
  node_stats: NodeStepStats
35
48
  node_settings: NodeStepSettings
36
49
  results: NodeResults
37
- node_information: Optional[schemas.NodeInformation] = None
38
- leads_to_nodes: List["FlowNode"] = [] # list with target flows, after execution the step will trigger those step(s)
39
- user_provided_schema_callback: Optional[Callable] = None # user provided callback function for schema calculation
50
+ node_information: schemas.NodeInformation | None = None
51
+ leads_to_nodes: list["FlowNode"] = [] # list with target flows, after execution the step will trigger those step(s)
52
+ user_provided_schema_callback: Callable | None = None # user provided callback function for schema calculation
40
53
  _setting_input: Any = None
41
- _hash: Optional[str] = None # host this for caching results
54
+ _hash: str | None = None # host this for caching results
42
55
  _function: Callable = None # the function that needs to be executed when triggered
43
56
  _name: str = None # name of the node, used for display
44
- _schema_callback: Optional[SingleExecutionFuture] = None # Function that calculates the schema without executing
57
+ _schema_callback: SingleExecutionFuture | None = None # Function that calculates the schema without executing
45
58
  _state_needs_reset: bool = False
46
- _fetch_cached_df: Optional[ExternalDfFetcher | ExternalDatabaseFetcher | ExternalDatabaseWriter | ExternalCloudWriter] = None
47
- _cache_progress: Optional[ExternalDfFetcher | ExternalDatabaseFetcher | ExternalDatabaseWriter | ExternalCloudWriter] = None
48
-
49
- def __init__(self, node_id: Union[str, int], function: Callable,
50
- parent_uuid: str,
51
- setting_input: Any,
52
- name: str,
53
- node_type: str,
54
- input_columns: List[str] = None,
55
- output_schema: List[FlowfileColumn] = None,
56
- drop_columns: List[str] = None,
57
- renew_schema: bool = True,
58
- pos_x: float = 0,
59
- pos_y: float = 0,
60
- schema_callback: Callable = None,
61
- ):
59
+ _fetch_cached_df: (
60
+ ExternalDfFetcher | ExternalDatabaseFetcher | ExternalDatabaseWriter | ExternalCloudWriter | None
61
+ ) = None
62
+ _cache_progress: (
63
+ ExternalDfFetcher | ExternalDatabaseFetcher | ExternalDatabaseWriter | ExternalCloudWriter | None
64
+ ) = None
65
+
66
+ def __init__(
67
+ self,
68
+ node_id: str | int,
69
+ function: Callable,
70
+ parent_uuid: str,
71
+ setting_input: Any,
72
+ name: str,
73
+ node_type: str,
74
+ input_columns: list[str] = None,
75
+ output_schema: list[FlowfileColumn] = None,
76
+ drop_columns: list[str] = None,
77
+ renew_schema: bool = True,
78
+ pos_x: float = 0,
79
+ pos_y: float = 0,
80
+ schema_callback: Callable = None,
81
+ ):
62
82
  """Initializes a FlowNode instance.
63
83
 
64
84
  Args:
@@ -83,16 +103,17 @@ class FlowNode:
83
103
  self.node_information.id = node_id
84
104
  self.node_type = node_type
85
105
  self.node_settings.renew_schema = renew_schema
86
- self.update_node(function=function,
87
- input_columns=input_columns,
88
- output_schema=output_schema,
89
- drop_columns=drop_columns,
90
- setting_input=setting_input,
91
- name=name,
92
- pos_x=pos_x,
93
- pos_y=pos_y,
94
- schema_callback=schema_callback,
95
- )
106
+ self.update_node(
107
+ function=function,
108
+ input_columns=input_columns,
109
+ output_schema=output_schema,
110
+ drop_columns=drop_columns,
111
+ setting_input=setting_input,
112
+ name=name,
113
+ pos_x=pos_x,
114
+ pos_y=pos_y,
115
+ schema_callback=schema_callback,
116
+ )
96
117
 
97
118
  def post_init(self):
98
119
  """Initializes or resets the node's attributes to their default states."""
@@ -127,7 +148,7 @@ class FlowNode:
127
148
  self._state_needs_reset = v
128
149
 
129
150
  @staticmethod
130
- def create_schema_callback_from_function(f: Callable) -> Callable[[], List[FlowfileColumn]]:
151
+ def create_schema_callback_from_function(f: Callable) -> Callable[[], list[FlowfileColumn]]:
131
152
  """Wraps a node's function to create a schema callback that extracts the schema.
132
153
 
133
154
  Args:
@@ -136,13 +157,15 @@ class FlowNode:
136
157
  Returns:
137
158
  A callable that, when executed, returns the output schema.
138
159
  """
139
- def schema_callback() -> List[FlowfileColumn]:
160
+
161
+ def schema_callback() -> list[FlowfileColumn]:
140
162
  try:
141
- logger.info('Executing the schema callback function based on the node function')
163
+ logger.info("Executing the schema callback function based on the node function")
142
164
  return f().schema
143
165
  except Exception as e:
144
- logger.warning(f'Error with the schema callback: {e}')
166
+ logger.warning(f"Error with the schema callback: {e}")
145
167
  return []
168
+
146
169
  return schema_callback
147
170
 
148
171
  @property
@@ -171,7 +194,7 @@ class FlowNode:
171
194
  if f is None:
172
195
  return
173
196
 
174
- def error_callback(e: Exception) -> List:
197
+ def error_callback(e: Exception) -> list:
175
198
  logger.warning(e)
176
199
 
177
200
  self.node_settings.setup_errors = True
@@ -190,7 +213,7 @@ class FlowNode:
190
213
  """
191
214
  return not self.has_input and self.node_template.input == 0
192
215
 
193
- def get_input_type(self, node_id: int) -> List:
216
+ def get_input_type(self, node_id: int) -> list:
194
217
  """Gets the type of connection ('main', 'left', 'right') for a given input node ID.
195
218
 
196
219
  Args:
@@ -201,24 +224,25 @@ class FlowNode:
201
224
  """
202
225
  relation_type = []
203
226
  if node_id in [n.node_id for n in self.node_inputs.main_inputs]:
204
- relation_type.append('main')
227
+ relation_type.append("main")
205
228
  if self.node_inputs.left_input is not None and node_id == self.node_inputs.left_input.node_id:
206
- relation_type.append('left')
229
+ relation_type.append("left")
207
230
  if self.node_inputs.right_input is not None and node_id == self.node_inputs.right_input.node_id:
208
- relation_type.append('right')
231
+ relation_type.append("right")
209
232
  return list(set(relation_type))
210
233
 
211
- def update_node(self,
212
- function: Callable,
213
- input_columns: List[str] = None,
214
- output_schema: List[FlowfileColumn] = None,
215
- drop_columns: List[str] = None,
216
- name: str = None,
217
- setting_input: Any = None,
218
- pos_x: float = 0,
219
- pos_y: float = 0,
220
- schema_callback: Callable = None,
221
- ):
234
+ def update_node(
235
+ self,
236
+ function: Callable,
237
+ input_columns: list[str] = None,
238
+ output_schema: list[FlowfileColumn] = None,
239
+ drop_columns: list[str] = None,
240
+ name: str = None,
241
+ setting_input: Any = None,
242
+ pos_x: float = 0,
243
+ pos_y: float = 0,
244
+ schema_callback: Callable = None,
245
+ ):
222
246
  """Updates the properties of the node.
223
247
 
224
248
  This is called during initialization and when settings are changed.
@@ -245,7 +269,7 @@ class FlowNode:
245
269
  self.node_schema.output_columns = [] if output_schema is None else output_schema
246
270
  self.node_schema.drop_columns = [] if drop_columns is None else drop_columns
247
271
  self.node_settings.renew_schema = True
248
- if hasattr(setting_input, 'cache_results'):
272
+ if hasattr(setting_input, "cache_results"):
249
273
  self.node_settings.cache_results = setting_input.cache_results
250
274
 
251
275
  self.results.errors = None
@@ -253,7 +277,7 @@ class FlowNode:
253
277
  _ = self.hash
254
278
  self.node_template = node_store.node_dict.get(self.node_type)
255
279
  if self.node_template is None:
256
- raise Exception(f'Node template {self.node_type} not found')
280
+ raise Exception(f"Node template {self.node_type} not found")
257
281
  self.node_default = node_store.node_defaults.get(self.node_type)
258
282
  self.setting_input = setting_input # wait until the end so that the hash is calculated correctly
259
283
 
@@ -292,10 +316,11 @@ class FlowNode:
292
316
  Args:
293
317
  setting_input: The new settings object.
294
318
  """
295
- is_manual_input = (self.node_type == 'manual_input' and
296
- isinstance(setting_input, input_schema.NodeManualInput) and
297
- isinstance(self._setting_input, input_schema.NodeManualInput)
298
- )
319
+ is_manual_input = (
320
+ self.node_type == "manual_input"
321
+ and isinstance(setting_input, input_schema.NodeManualInput)
322
+ and isinstance(self._setting_input, input_schema.NodeManualInput)
323
+ )
299
324
  if is_manual_input:
300
325
  _ = self.hash
301
326
  self._setting_input = setting_input
@@ -309,7 +334,7 @@ class FlowNode:
309
334
  self.reset()
310
335
 
311
336
  @property
312
- def node_id(self) -> Union[str, int]:
337
+ def node_id(self) -> str | int:
313
338
  """Gets the unique identifier of the node.
314
339
 
315
340
  Returns:
@@ -336,7 +361,7 @@ class FlowNode:
336
361
  return self.node_inputs.right_input
337
362
 
338
363
  @property
339
- def main_input(self) -> List["FlowNode"]:
364
+ def main_input(self) -> list["FlowNode"]:
340
365
  """Gets the list of nodes connected to the main input port(s).
341
366
 
342
367
  Returns:
@@ -353,23 +378,29 @@ class FlowNode:
353
378
  """
354
379
  if isinstance(self.setting_input, input_schema.NodePromise):
355
380
  return False
356
- return (self.node_template.input == len(self.node_inputs.get_all_inputs()) or
357
- (self.node_template.multi and len(self.node_inputs.get_all_inputs()) > 0) or
358
- (self.node_template.multi and self.node_template.can_be_start))
381
+ return (
382
+ self.node_template.input == len(self.node_inputs.get_all_inputs())
383
+ or (self.node_template.multi and len(self.node_inputs.get_all_inputs()) > 0)
384
+ or (self.node_template.multi and self.node_template.can_be_start)
385
+ )
359
386
 
360
387
  def set_node_information(self):
361
388
  """Populates the `node_information` attribute with the current state.
362
389
 
363
390
  This includes the node's connections, settings, and position.
364
391
  """
365
- logger.info('setting node information')
392
+ logger.info("setting node information")
366
393
  node_information = self.node_information
367
394
  node_information.left_input_id = self.node_inputs.left_input.node_id if self.left_input else None
368
395
  node_information.right_input_id = self.node_inputs.right_input.node_id if self.right_input else None
369
- node_information.input_ids = [mi.node_id for mi in
370
- self.node_inputs.main_inputs] if self.node_inputs.main_inputs is not None else None
396
+ node_information.input_ids = (
397
+ [mi.node_id for mi in self.node_inputs.main_inputs] if self.node_inputs.main_inputs is not None else None
398
+ )
371
399
  node_information.setting_input = self.setting_input
372
400
  node_information.outputs = [n.node_id for n in self.leads_to_nodes]
401
+ node_information.description = (
402
+ self.setting_input.description if hasattr(self.setting_input, "description") else ""
403
+ )
373
404
  node_information.is_setup = self.is_setup
374
405
  node_information.x_position = self.setting_input.pos_x
375
406
  node_information.y_position = self.setting_input.pos_y
@@ -403,7 +434,7 @@ class FlowNode:
403
434
  self._function = function
404
435
 
405
436
  @property
406
- def all_inputs(self) -> List["FlowNode"]:
437
+ def all_inputs(self) -> list["FlowNode"]:
407
438
  """Gets a list of all nodes connected to any input port.
408
439
 
409
440
  Returns:
@@ -435,8 +466,9 @@ class FlowNode:
435
466
  self._hash = self.calculate_hash(self.setting_input)
436
467
  return self._hash
437
468
 
438
- def add_node_connection(self, from_node: "FlowNode",
439
- insert_type: Literal['main', 'left', 'right'] = 'main') -> None:
469
+ def add_node_connection(
470
+ self, from_node: "FlowNode", insert_type: Literal["main", "left", "right"] = "main"
471
+ ) -> None:
440
472
  """Adds a connection from a source node to this node.
441
473
 
442
474
  Args:
@@ -447,19 +479,19 @@ class FlowNode:
447
479
  Exception: If the insert_type is invalid.
448
480
  """
449
481
  from_node.leads_to_nodes.append(self)
450
- if insert_type == 'main':
482
+ if insert_type == "main":
451
483
  if self.node_template.input <= 2 or self.node_inputs.main_inputs is None:
452
484
  self.node_inputs.main_inputs = [from_node]
453
485
  else:
454
486
  self.node_inputs.main_inputs.append(from_node)
455
- elif insert_type == 'right':
487
+ elif insert_type == "right":
456
488
  self.node_inputs.right_input = from_node
457
- elif insert_type == 'left':
489
+ elif insert_type == "left":
458
490
  self.node_inputs.left_input = from_node
459
491
  else:
460
- raise Exception('Cannot find the connection')
492
+ raise Exception("Cannot find the connection")
461
493
  if self.setting_input.is_setup:
462
- if hasattr(self.setting_input, 'depending_on_id') and insert_type == 'main':
494
+ if hasattr(self.setting_input, "depending_on_id") and insert_type == "main":
463
495
  self.setting_input.depending_on_id = from_node.node_id
464
496
  self.reset()
465
497
  from_node.reset()
@@ -471,7 +503,7 @@ class FlowNode:
471
503
  deep: If True, the reset propagates recursively through the entire downstream graph.
472
504
  """
473
505
  for node in self.leads_to_nodes:
474
- self.print(f'resetting node: {node.node_id}')
506
+ self.print(f"resetting node: {node.node_id}")
475
507
  node.reset(deep)
476
508
 
477
509
  def get_flow_file_column_schema(self, col_name: str) -> FlowfileColumn | None:
@@ -487,7 +519,7 @@ class FlowNode:
487
519
  if s.column_name == col_name:
488
520
  return s
489
521
 
490
- def get_predicted_schema(self, force: bool = False) -> List[FlowfileColumn] | None:
522
+ def get_predicted_schema(self, force: bool = False) -> list[FlowfileColumn] | None:
491
523
  """Predicts the output schema of the node without full execution.
492
524
 
493
525
  It uses the schema_callback or infers from predicted data.
@@ -498,22 +530,24 @@ class FlowNode:
498
530
  Returns:
499
531
  A list of FlowfileColumn objects representing the predicted schema.
500
532
  """
533
+
501
534
  if self.node_schema.predicted_schema and not force:
502
535
  return self.node_schema.predicted_schema
503
536
  if self.schema_callback is not None and (self.node_schema.predicted_schema is None or force):
504
- self.print('Getting the data from a schema callback')
537
+ self.print("Getting the data from a schema callback")
505
538
  if force:
506
539
  # Force the schema callback to reset, so that it will be executed again
507
540
  self.schema_callback.reset()
508
541
  schema = self.schema_callback()
509
542
  if schema is not None and len(schema) > 0:
510
- self.print('Calculating the schema based on the schema callback')
543
+ self.print("Calculating the schema based on the schema callback")
511
544
  self.node_schema.predicted_schema = schema
512
545
  return self.node_schema.predicted_schema
513
546
  predicted_data = self._predicted_data_getter()
514
547
  if predicted_data is not None and predicted_data.schema is not None:
515
- self.print('Calculating the schema based on the predicted resulting data')
548
+ self.print("Calculating the schema based on the predicted resulting data")
516
549
  self.node_schema.predicted_schema = self._predicted_data_getter().schema
550
+
517
551
  return self.node_schema.predicted_schema
518
552
 
519
553
  @property
@@ -524,7 +558,7 @@ class FlowNode:
524
558
  True if the node is set up, False otherwise.
525
559
  """
526
560
  if not self.node_information.is_setup:
527
- if self.function.__name__ != 'placeholder':
561
+ if self.function.__name__ != "placeholder":
528
562
  self.node_information.is_setup = True
529
563
  self.setting_input.is_setup = True
530
564
  return self.node_information.is_setup
@@ -535,7 +569,7 @@ class FlowNode:
535
569
  Args:
536
570
  v: The message or value to log.
537
571
  """
538
- logger.info(f'{self.node_type}, node_id: {self.node_id}: {v}')
572
+ logger.info(f"{self.node_type}, node_id: {self.node_id}: {v}")
539
573
 
540
574
  def get_resulting_data(self) -> FlowDataEngine | None:
541
575
  """Executes the node's function to produce the actual output data.
@@ -550,11 +584,11 @@ class FlowNode:
550
584
  """
551
585
  if self.is_setup:
552
586
  if self.results.resulting_data is None and self.results.errors is None:
553
- self.print('getting resulting data')
587
+ self.print("getting resulting data")
554
588
  try:
555
589
  if isinstance(self.function, FlowDataEngine):
556
590
  fl: FlowDataEngine = self.function
557
- elif self.node_type == 'external_source':
591
+ elif self.node_type == "external_source":
558
592
  fl: FlowDataEngine = self.function()
559
593
  fl.collect_external()
560
594
  self.node_settings.streamable = False
@@ -587,14 +621,14 @@ class FlowNode:
587
621
  return fl
588
622
  except ValueError as e:
589
623
  if str(e) == "generator already executing":
590
- logger.info('Generator already executing, waiting for the result')
624
+ logger.info("Generator already executing, waiting for the result")
591
625
  sleep(1)
592
626
  return self._predicted_data_getter()
593
627
  fl = FlowDataEngine()
594
628
  return fl
595
629
 
596
630
  except Exception as e:
597
- logger.warning('there was an issue with the function, returning an empty Flowfile')
631
+ logger.warning("there was an issue with the function, returning an empty Flowfile")
598
632
  logger.warning(e)
599
633
 
600
634
  def get_predicted_resulting_data(self) -> FlowDataEngine:
@@ -606,7 +640,7 @@ class FlowNode:
606
640
  A FlowDataEngine instance with a schema but no data.
607
641
  """
608
642
  if self.needs_run(False) and self.schema_callback is not None or self.node_schema.result_schema is not None:
609
- self.print('Getting data based on the schema')
643
+ self.print("Getting data based on the schema")
610
644
 
611
645
  _s = self.schema_callback() if self.node_schema.result_schema is None else self.node_schema.result_schema
612
646
  return FlowDataEngine.create_from_schema(_s)
@@ -646,7 +680,7 @@ class FlowNode:
646
680
  yield n
647
681
 
648
682
  @property
649
- def schema(self) -> List[FlowfileColumn]:
683
+ def schema(self) -> list[FlowfileColumn]:
650
684
  """Gets the definitive output schema of the node.
651
685
 
652
686
  If not already run, it falls back to the predicted schema.
@@ -658,7 +692,7 @@ class FlowNode:
658
692
  if self.is_setup and self.results.errors is None:
659
693
  if self.node_schema.result_schema is not None and len(self.node_schema.result_schema) > 0:
660
694
  return self.node_schema.result_schema
661
- elif self.node_type == 'output':
695
+ elif self.node_type == "output":
662
696
  if len(self.node_inputs.main_inputs) > 0:
663
697
  self.node_schema.result_schema = self.node_inputs.main_inputs[0].schema
664
698
  else:
@@ -677,11 +711,15 @@ class FlowNode:
677
711
  """
678
712
 
679
713
  if results_exists(self.hash):
680
- logger.warning('Not implemented')
714
+ logger.warning("Not implemented")
681
715
  clear_task_from_worker(self.hash)
682
716
 
683
- def needs_run(self, performance_mode: bool, node_logger: NodeLogger = None,
684
- execution_location: schemas.ExecutionLocationsLiteral = "remote") -> bool:
717
+ def needs_run(
718
+ self,
719
+ performance_mode: bool,
720
+ node_logger: NodeLogger = None,
721
+ execution_location: schemas.ExecutionLocationsLiteral = "remote",
722
+ ) -> bool:
685
723
  """Determines if the node needs to be executed.
686
724
 
687
725
  The decision is based on its run state, caching settings, and execution mode.
@@ -700,7 +738,7 @@ class FlowNode:
700
738
  flow_logger = logger if node_logger is None else node_logger
701
739
  cache_result_exists = results_exists(self.hash)
702
740
  if not self.node_stats.has_run_with_current_setup:
703
- flow_logger.info('Node has not run, needs to run')
741
+ flow_logger.info("Node has not run, needs to run")
704
742
  return True
705
743
  if self.node_settings.cache_results and cache_result_exists:
706
744
  return False
@@ -734,7 +772,9 @@ class FlowNode:
734
772
  if example_data is None:
735
773
  example_data = resulting_data.get_sample(100).to_arrow()
736
774
  return example_data
775
+
737
776
  return get_example_data
777
+
738
778
  resulting_data = self.get_resulting_data()
739
779
 
740
780
  if not performance_mode:
@@ -756,8 +796,13 @@ class FlowNode:
756
796
  try:
757
797
  resulting_data = self.get_resulting_data()
758
798
  if not performance_mode:
759
- external_sampler = ExternalSampler(lf=resulting_data.data_frame, file_ref=self.hash,
760
- wait_on_completion=True, node_id=self.node_id, flow_id=flow_id)
799
+ external_sampler = ExternalSampler(
800
+ lf=resulting_data.data_frame,
801
+ file_ref=self.hash,
802
+ wait_on_completion=True,
803
+ node_id=self.node_id,
804
+ flow_id=flow_id,
805
+ )
761
806
  self.store_example_data_generator(external_sampler)
762
807
  if self.results.errors is None and not self.node_stats.is_canceled:
763
808
  self.node_stats.has_run_with_current_setup = True
@@ -787,48 +832,58 @@ class FlowNode:
787
832
  Exception: If the node_logger is not provided or if execution fails.
788
833
  """
789
834
  if node_logger is None:
790
- raise Exception('Node logger is not defined')
835
+ raise Exception("Node logger is not defined")
791
836
  if self.node_settings.cache_results and results_exists(self.hash):
792
837
  try:
793
838
  self.results.resulting_data = get_external_df_result(self.hash)
794
839
  self._cache_progress = None
795
840
  return
796
- except Exception as e:
797
- node_logger.warning('Failed to read the cache, rerunning the code')
798
- if self.node_type == 'output':
841
+ except Exception:
842
+ node_logger.warning("Failed to read the cache, rerunning the code")
843
+ if self.node_type == "output":
799
844
  self.results.resulting_data = self.get_resulting_data()
800
845
  self.node_stats.has_run_with_current_setup = True
801
846
  return
802
847
  try:
803
848
  self.get_resulting_data()
804
849
  except Exception as e:
805
- self.results.errors = 'Error with creating the lazy frame, most likely due to invalid graph'
850
+ self.results.errors = "Error with creating the lazy frame, most likely due to invalid graph"
806
851
  raise e
807
852
  if not performance_mode:
808
- external_df_fetcher = ExternalDfFetcher(lf=self.get_resulting_data().data_frame,
809
- file_ref=self.hash, wait_on_completion=False,
810
- flow_id=node_logger.flow_id,
811
- node_id=self.node_id)
853
+ external_df_fetcher = ExternalDfFetcher(
854
+ lf=self.get_resulting_data().data_frame,
855
+ file_ref=self.hash,
856
+ wait_on_completion=False,
857
+ flow_id=node_logger.flow_id,
858
+ node_id=self.node_id,
859
+ )
812
860
  self._fetch_cached_df = external_df_fetcher
813
861
  try:
814
862
  lf = external_df_fetcher.get_result()
815
863
  self.results.resulting_data = FlowDataEngine(
816
- lf, number_of_records=ExternalDfFetcher(lf=lf, operation_type='calculate_number_of_records',
817
- flow_id=node_logger.flow_id, node_id=self.node_id).result
864
+ lf,
865
+ number_of_records=ExternalDfFetcher(
866
+ lf=lf,
867
+ operation_type="calculate_number_of_records",
868
+ flow_id=node_logger.flow_id,
869
+ node_id=self.node_id,
870
+ ).result,
818
871
  )
819
872
  if not performance_mode:
820
873
  self.store_example_data_generator(external_df_fetcher)
821
874
  self.node_stats.has_run_with_current_setup = True
822
875
 
823
876
  except Exception as e:
824
- node_logger.error('Error with external process')
877
+ node_logger.error("Error with external process")
825
878
  if external_df_fetcher.error_code == -1:
826
879
  try:
827
880
  self.results.resulting_data = self.get_resulting_data()
828
- self.results.warnings = ('Error with external process (unknown error), '
829
- 'likely the process was killed by the server because of memory constraints, '
830
- 'continue with the process. '
831
- 'We cannot display example data...')
881
+ self.results.warnings = (
882
+ "Error with external process (unknown error), "
883
+ "likely the process was killed by the server because of memory constraints, "
884
+ "continue with the process. "
885
+ "We cannot display example data..."
886
+ )
832
887
  except Exception as e:
833
888
  self.results.errors = str(e)
834
889
  raise e
@@ -855,15 +910,18 @@ class FlowNode:
855
910
  self._fetch_cached_df.cancel()
856
911
  self.node_stats.is_canceled = True
857
912
  else:
858
- logger.warning('No external process to cancel')
913
+ logger.warning("No external process to cancel")
859
914
  self.node_stats.is_canceled = True
860
915
 
861
- def execute_node(self, run_location: schemas.ExecutionLocationsLiteral,
862
- reset_cache: bool = False,
863
- performance_mode: bool = False,
864
- retry: bool = True,
865
- node_logger: NodeLogger = None,
866
- optimize_for_downstream: bool = True):
916
+ def execute_node(
917
+ self,
918
+ run_location: schemas.ExecutionLocationsLiteral,
919
+ reset_cache: bool = False,
920
+ performance_mode: bool = False,
921
+ retry: bool = True,
922
+ node_logger: NodeLogger = None,
923
+ optimize_for_downstream: bool = True,
924
+ ):
867
925
  """Orchestrates the execution, handling location, caching, and retries.
868
926
 
869
927
  Args:
@@ -879,7 +937,7 @@ class FlowNode:
879
937
  Exception: If the node_logger is not defined.
880
938
  """
881
939
  if node_logger is None:
882
- raise Exception('Flow logger is not defined')
940
+ raise Exception("Flow logger is not defined")
883
941
  # TODO: Simplify which route is being picked there are many duplicate checks
884
942
 
885
943
  if reset_cache:
@@ -888,63 +946,78 @@ class FlowNode:
888
946
  self.node_stats.has_completed_last_run = False
889
947
 
890
948
  if self.is_setup:
891
- node_logger.info(f'Starting to run {self.__name__}')
892
- if (self.needs_run(performance_mode, node_logger, run_location) or self.node_template.node_group == "output"
893
- and not (run_location == 'local')):
949
+ node_logger.info(f"Starting to run {self.__name__}")
950
+ if (
951
+ self.needs_run(performance_mode, node_logger, run_location)
952
+ or self.node_template.node_group == "output"
953
+ and not (run_location == "local")
954
+ ):
894
955
  self.clear_table_example()
895
956
  self.prepare_before_run()
896
957
  self.reset()
897
958
  try:
898
- if (((run_location == 'remote' or
899
- (self.node_default.transform_type == 'wide' and optimize_for_downstream) and
900
- not run_location == 'local'))
901
- or self.node_settings.cache_results):
902
- node_logger.info('Running the node remotely')
959
+ if (
960
+ run_location == "remote"
961
+ or (self.node_default.transform_type == "wide" and optimize_for_downstream)
962
+ and not run_location == "local"
963
+ ) or self.node_settings.cache_results:
964
+ node_logger.info("Running the node remotely")
903
965
  if self.node_settings.cache_results:
904
966
  performance_mode = False
905
- self.execute_remote(performance_mode=(performance_mode if not self.node_settings.cache_results
906
- else False),
907
- node_logger=node_logger
908
- )
967
+ self.execute_remote(
968
+ performance_mode=(performance_mode if not self.node_settings.cache_results else False),
969
+ node_logger=node_logger,
970
+ )
909
971
  else:
910
- node_logger.info('Running the node locally')
972
+ node_logger.info("Running the node locally")
911
973
  self.execute_local(performance_mode=performance_mode, flow_id=node_logger.flow_id)
912
974
  except Exception as e:
913
- if 'No such file or directory (os error' in str(e) and retry:
914
- logger.warning('Error with the input node, starting to rerun the input node...')
915
- all_inputs: List[FlowNode] = self.node_inputs.get_all_inputs()
975
+ if "No such file or directory (os error" in str(e) and retry:
976
+ logger.warning("Error with the input node, starting to rerun the input node...")
977
+ all_inputs: list[FlowNode] = self.node_inputs.get_all_inputs()
916
978
  for node_input in all_inputs:
917
- node_input.execute_node(run_location=run_location,
918
- performance_mode=performance_mode, retry=True,
919
- reset_cache=True,
920
- node_logger=node_logger)
921
- self.execute_node(run_location=run_location,
922
- performance_mode=performance_mode, retry=False,
923
- node_logger=node_logger)
979
+ node_input.execute_node(
980
+ run_location=run_location,
981
+ performance_mode=performance_mode,
982
+ retry=True,
983
+ reset_cache=True,
984
+ node_logger=node_logger,
985
+ )
986
+ self.execute_node(
987
+ run_location=run_location,
988
+ performance_mode=performance_mode,
989
+ retry=False,
990
+ node_logger=node_logger,
991
+ )
924
992
  else:
925
993
  self.results.errors = str(e)
926
994
  if "Connection refused" in str(e) and "/submit_query/" in str(e):
927
- node_logger.warning("There was an issue connecting to the remote worker, "
928
- "ensure the worker process is running, "
929
- "or change the settings to, so it executes locally")
930
- node_logger.error("Could not execute in the remote worker. (Re)start the worker service, or change settings to local settings.")
995
+ node_logger.warning(
996
+ "There was an issue connecting to the remote worker, "
997
+ "ensure the worker process is running, "
998
+ "or change the settings to, so it executes locally"
999
+ )
1000
+ node_logger.error(
1001
+ "Could not execute in the remote worker. (Re)start the worker service, or change settings to local settings."
1002
+ )
931
1003
  else:
932
- node_logger.error(f'Error with running the node: {e}')
933
- elif ((run_location == 'local') and
934
- (not self.node_stats.has_run_with_current_setup or self.node_template.node_group == "output")):
1004
+ node_logger.error(f"Error with running the node: {e}")
1005
+ elif (run_location == "local") and (
1006
+ not self.node_stats.has_run_with_current_setup or self.node_template.node_group == "output"
1007
+ ):
935
1008
  try:
936
- node_logger.info('Executing fully locally')
1009
+ node_logger.info("Executing fully locally")
937
1010
  self.execute_full_local(performance_mode)
938
1011
  except Exception as e:
939
1012
  self.results.errors = str(e)
940
- node_logger.error(f'Error with running the node: {e}')
1013
+ node_logger.error(f"Error with running the node: {e}")
941
1014
  self.node_stats.error = str(e)
942
1015
  self.node_stats.has_completed_last_run = False
943
1016
 
944
1017
  else:
945
- node_logger.info('Node has already run, not running the node')
1018
+ node_logger.info("Node has already run, not running the node")
946
1019
  else:
947
- node_logger.warning(f'Node {self.__name__} is not setup, cannot run the node')
1020
+ node_logger.warning(f"Node {self.__name__} is not setup, cannot run the node")
948
1021
 
949
1022
  def store_example_data_generator(self, external_df_fetcher: ExternalDfFetcher | ExternalSampler):
950
1023
  """Stores a generator function for fetching a sample of the result data.
@@ -957,7 +1030,7 @@ class FlowNode:
957
1030
  self.results.example_data_path = file_ref
958
1031
  self.results.example_data_generator = get_read_top_n(file_path=file_ref, n=100)
959
1032
  else:
960
- logger.error('Could not get the sample data, the external process is not ready')
1033
+ logger.error("Could not get the sample data, the external process is not ready")
961
1034
 
962
1035
  def needs_reset(self) -> bool:
963
1036
  """Checks if the node's hash has changed, indicating an outdated state.
@@ -977,7 +1050,7 @@ class FlowNode:
977
1050
  """
978
1051
  needs_reset = self.needs_reset() or deep
979
1052
  if needs_reset:
980
- logger.info(f'{self.node_id}: Node needs reset')
1053
+ logger.info(f"{self.node_id}: Node needs reset")
981
1054
  self.node_stats.has_run_with_current_setup = False
982
1055
  self.results.reset()
983
1056
  self.node_schema.result_schema = None
@@ -988,7 +1061,7 @@ class FlowNode:
988
1061
  if self.is_correct:
989
1062
  self._schema_callback = None # Ensure the schema callback is reset
990
1063
  if self.schema_callback:
991
- logger.info(f'{self.node_id}: Resetting the schema callback')
1064
+ logger.info(f"{self.node_id}: Resetting the schema callback")
992
1065
  self.schema_callback.start()
993
1066
  self.evaluate_nodes()
994
1067
  _ = self.hash # Recalculate the hash after reset
@@ -1002,17 +1075,18 @@ class FlowNode:
1002
1075
  Returns:
1003
1076
  True if the connection was found and removed, False otherwise.
1004
1077
  """
1005
- logger.info(f'Deleting lead to node: {node_id}')
1078
+ logger.info(f"Deleting lead to node: {node_id}")
1006
1079
  for i, lead_to_node in enumerate(self.leads_to_nodes):
1007
- logger.info(f'Checking lead to node: {lead_to_node.node_id}')
1080
+ logger.info(f"Checking lead to node: {lead_to_node.node_id}")
1008
1081
  if lead_to_node.node_id == node_id:
1009
- logger.info(f'Found the node to delete: {node_id}')
1082
+ logger.info(f"Found the node to delete: {node_id}")
1010
1083
  self.leads_to_nodes.pop(i)
1011
1084
  return True
1012
1085
  return False
1013
1086
 
1014
- def delete_input_node(self, node_id: int, connection_type: input_schema.InputConnectionClass = 'input-0',
1015
- complete: bool = False) -> bool:
1087
+ def delete_input_node(
1088
+ self, node_id: int, connection_type: input_schema.InputConnectionClass = "input-0", complete: bool = False
1089
+ ) -> bool:
1016
1090
  """Removes a connection from a specific input node.
1017
1091
 
1018
1092
  Args:
@@ -1024,23 +1098,23 @@ class FlowNode:
1024
1098
  True if a connection was found and removed, False otherwise.
1025
1099
  """
1026
1100
  deleted: bool = False
1027
- if connection_type == 'input-0':
1101
+ if connection_type == "input-0":
1028
1102
  for i, node in enumerate(self.node_inputs.main_inputs):
1029
1103
  if node.node_id == node_id:
1030
1104
  self.node_inputs.main_inputs.pop(i)
1031
1105
  deleted = True
1032
1106
  if not complete:
1033
1107
  continue
1034
- elif connection_type == 'input-1' or complete:
1108
+ elif connection_type == "input-1" or complete:
1035
1109
  if self.node_inputs.right_input is not None and self.node_inputs.right_input.node_id == node_id:
1036
1110
  self.node_inputs.right_input = None
1037
1111
  deleted = True
1038
- elif connection_type == 'input-2' or complete:
1112
+ elif connection_type == "input-2" or complete:
1039
1113
  if self.node_inputs.left_input is not None and self.node_inputs.right_input.node_id == node_id:
1040
1114
  self.node_inputs.left_input = None
1041
1115
  deleted = True
1042
1116
  else:
1043
- logger.warning('Could not find the connection to delete...')
1117
+ logger.warning("Could not find the connection to delete...")
1044
1118
  if deleted:
1045
1119
  self.reset()
1046
1120
  return deleted
@@ -1053,7 +1127,7 @@ class FlowNode:
1053
1127
  """
1054
1128
  return f"Node id: {self.node_id} ({self.node_type})"
1055
1129
 
1056
- def _get_readable_schema(self) -> List[dict] | None:
1130
+ def _get_readable_schema(self) -> list[dict] | None:
1057
1131
  """Helper to get a simplified, dictionary representation of the output schema.
1058
1132
 
1059
1133
  Returns:
@@ -1071,11 +1145,14 @@ class FlowNode:
1071
1145
  Returns:
1072
1146
  A dictionary containing key information about the node.
1073
1147
  """
1074
- return dict(FlowNode=
1075
- dict(node_id=self.node_id,
1076
- step_name=self.__name__,
1077
- output_columns=self.node_schema.output_columns,
1078
- output_schema=self._get_readable_schema()))
1148
+ return dict(
1149
+ FlowNode=dict(
1150
+ node_id=self.node_id,
1151
+ step_name=self.__name__,
1152
+ output_columns=self.node_schema.output_columns,
1153
+ output_schema=self._get_readable_schema(),
1154
+ )
1155
+ )
1079
1156
 
1080
1157
  @property
1081
1158
  def number_of_leads_to_nodes(self) -> int | None:
@@ -1146,14 +1223,13 @@ class FlowNode:
1146
1223
  Returns:
1147
1224
  A `TableExample` object, or None if the node is not set up.
1148
1225
  """
1149
- self.print('Getting a table example')
1226
+ self.print("Getting a table example")
1150
1227
  if self.is_setup and include_data and self.node_stats.has_completed_last_run:
1151
-
1152
- if self.node_template.node_group == 'output':
1153
- self.print('getting the table example')
1228
+ if self.node_template.node_group == "output":
1229
+ self.print("getting the table example")
1154
1230
  return self.main_input[0].get_table_example(include_data)
1155
1231
 
1156
- logger.info('getting the table example since the node has run')
1232
+ logger.info("getting the table example since the node has run")
1157
1233
  example_data_getter = self.results.example_data_generator
1158
1234
  if example_data_getter is not None:
1159
1235
  data = example_data_getter().to_pylist()
@@ -1165,26 +1241,34 @@ class FlowNode:
1165
1241
  fl = self.get_resulting_data()
1166
1242
  has_example_data = self.results.example_data_generator is not None
1167
1243
 
1168
- return TableExample(node_id=self.node_id,
1169
- name=str(self.node_id), number_of_records=999,
1170
- number_of_columns=fl.number_of_fields,
1171
- table_schema=schema, columns=fl.columns, data=data,
1172
- has_example_data=has_example_data,
1173
- has_run_with_current_setup=self.node_stats.has_run_with_current_setup
1174
- )
1244
+ return TableExample(
1245
+ node_id=self.node_id,
1246
+ name=str(self.node_id),
1247
+ number_of_records=999,
1248
+ number_of_columns=fl.number_of_fields,
1249
+ table_schema=schema,
1250
+ columns=fl.columns,
1251
+ data=data,
1252
+ has_example_data=has_example_data,
1253
+ has_run_with_current_setup=self.node_stats.has_run_with_current_setup,
1254
+ )
1175
1255
  else:
1176
- logger.warning('getting the table example but the node has not run')
1256
+ logger.warning("getting the table example but the node has not run")
1177
1257
  try:
1178
1258
  schema = [FileColumn.model_validate(c.get_column_repr()) for c in self.schema]
1179
1259
  except Exception as e:
1180
1260
  logger.warning(e)
1181
1261
  schema = []
1182
1262
  columns = [s.name for s in schema]
1183
- return TableExample(node_id=self.node_id,
1184
- name=str(self.node_id), number_of_records=0,
1185
- number_of_columns=len(columns),
1186
- table_schema=schema, columns=columns,
1187
- data=[])
1263
+ return TableExample(
1264
+ node_id=self.node_id,
1265
+ name=str(self.node_id),
1266
+ number_of_records=0,
1267
+ number_of_columns=len(columns),
1268
+ table_schema=schema,
1269
+ columns=columns,
1270
+ data=[],
1271
+ )
1188
1272
 
1189
1273
  def get_node_data(self, flow_id: int, include_example: bool = False) -> NodeData:
1190
1274
  """Gathers all necessary data for representing the node in the UI.
@@ -1196,11 +1280,13 @@ class FlowNode:
1196
1280
  Returns:
1197
1281
  A `NodeData` object.
1198
1282
  """
1199
- node = NodeData(flow_id=flow_id,
1200
- node_id=self.node_id,
1201
- has_run=self.node_stats.has_run_with_current_setup,
1202
- setting_input=self.setting_input,
1203
- flow_type=self.node_type)
1283
+ node = NodeData(
1284
+ flow_id=flow_id,
1285
+ node_id=self.node_id,
1286
+ has_run=self.node_stats.has_run_with_current_setup,
1287
+ setting_input=self.setting_input,
1288
+ flow_type=self.node_type,
1289
+ )
1204
1290
  if self.main_input:
1205
1291
  node.main_input = self.main_input[0].get_table_example()
1206
1292
  if self.left_input:
@@ -1212,6 +1298,9 @@ class FlowNode:
1212
1298
  node = setting_generator.get_setting_generator(self.node_type)(node)
1213
1299
 
1214
1300
  node = setting_updator.get_setting_updator(self.node_type)(node)
1301
+ # Save the updated settings back to the node so they persist across calls
1302
+ if node.setting_input is not None and not isinstance(node.setting_input, input_schema.NodePromise):
1303
+ self.setting_input = node.setting_input
1215
1304
  return node
1216
1305
 
1217
1306
  def get_output_data(self) -> TableExample:
@@ -1228,12 +1317,14 @@ class FlowNode:
1228
1317
  Returns:
1229
1318
  A `NodeInput` object.
1230
1319
  """
1231
- return schemas.NodeInput(pos_y=self.setting_input.pos_y,
1232
- pos_x=self.setting_input.pos_x,
1233
- id=self.node_id,
1234
- **self.node_template.__dict__)
1235
-
1236
- def get_edge_input(self) -> List[schemas.NodeEdge]:
1320
+ return schemas.NodeInput(
1321
+ pos_y=self.setting_input.pos_y,
1322
+ pos_x=self.setting_input.pos_x,
1323
+ id=self.node_id,
1324
+ **self.node_template.__dict__,
1325
+ )
1326
+
1327
+ def get_edge_input(self) -> list[schemas.NodeEdge]:
1237
1328
  """Generates `NodeEdge` objects for all input connections to this node.
1238
1329
 
1239
1330
  Returns:
@@ -1242,24 +1333,33 @@ class FlowNode:
1242
1333
  edges = []
1243
1334
  if self.node_inputs.main_inputs is not None:
1244
1335
  for i, main_input in enumerate(self.node_inputs.main_inputs):
1245
- edges.append(schemas.NodeEdge(id=f'{main_input.node_id}-{self.node_id}-{i}',
1246
- source=main_input.node_id,
1247
- target=self.node_id,
1248
- sourceHandle='output-0',
1249
- targetHandle='input-0',
1250
- ))
1336
+ edges.append(
1337
+ schemas.NodeEdge(
1338
+ id=f"{main_input.node_id}-{self.node_id}-{i}",
1339
+ source=main_input.node_id,
1340
+ target=self.node_id,
1341
+ sourceHandle="output-0",
1342
+ targetHandle="input-0",
1343
+ )
1344
+ )
1251
1345
  if self.node_inputs.left_input is not None:
1252
- edges.append(schemas.NodeEdge(id=f'{self.node_inputs.left_input.node_id}-{self.node_id}-right',
1253
- source=self.node_inputs.left_input.node_id,
1254
- target=self.node_id,
1255
- sourceHandle='output-0',
1256
- targetHandle='input-2',
1257
- ))
1346
+ edges.append(
1347
+ schemas.NodeEdge(
1348
+ id=f"{self.node_inputs.left_input.node_id}-{self.node_id}-right",
1349
+ source=self.node_inputs.left_input.node_id,
1350
+ target=self.node_id,
1351
+ sourceHandle="output-0",
1352
+ targetHandle="input-2",
1353
+ )
1354
+ )
1258
1355
  if self.node_inputs.right_input is not None:
1259
- edges.append(schemas.NodeEdge(id=f'{self.node_inputs.right_input.node_id}-{self.node_id}-left',
1260
- source=self.node_inputs.right_input.node_id,
1261
- target=self.node_id,
1262
- sourceHandle='output-0',
1263
- targetHandle='input-1',
1264
- ))
1356
+ edges.append(
1357
+ schemas.NodeEdge(
1358
+ id=f"{self.node_inputs.right_input.node_id}-{self.node_id}-left",
1359
+ source=self.node_inputs.right_input.node_id,
1360
+ target=self.node_id,
1361
+ sourceHandle="output-0",
1362
+ targetHandle="input-1",
1363
+ )
1364
+ )
1265
1365
  return edges