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
@@ -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,24 +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]
373
- node_information.description = self.setting_input.description if hasattr(self.setting_input, 'description') else ''
401
+ node_information.description = (
402
+ self.setting_input.description if hasattr(self.setting_input, "description") else ""
403
+ )
374
404
  node_information.is_setup = self.is_setup
375
405
  node_information.x_position = self.setting_input.pos_x
376
406
  node_information.y_position = self.setting_input.pos_y
@@ -404,7 +434,7 @@ class FlowNode:
404
434
  self._function = function
405
435
 
406
436
  @property
407
- def all_inputs(self) -> List["FlowNode"]:
437
+ def all_inputs(self) -> list["FlowNode"]:
408
438
  """Gets a list of all nodes connected to any input port.
409
439
 
410
440
  Returns:
@@ -436,8 +466,9 @@ class FlowNode:
436
466
  self._hash = self.calculate_hash(self.setting_input)
437
467
  return self._hash
438
468
 
439
- def add_node_connection(self, from_node: "FlowNode",
440
- 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:
441
472
  """Adds a connection from a source node to this node.
442
473
 
443
474
  Args:
@@ -448,19 +479,19 @@ class FlowNode:
448
479
  Exception: If the insert_type is invalid.
449
480
  """
450
481
  from_node.leads_to_nodes.append(self)
451
- if insert_type == 'main':
482
+ if insert_type == "main":
452
483
  if self.node_template.input <= 2 or self.node_inputs.main_inputs is None:
453
484
  self.node_inputs.main_inputs = [from_node]
454
485
  else:
455
486
  self.node_inputs.main_inputs.append(from_node)
456
- elif insert_type == 'right':
487
+ elif insert_type == "right":
457
488
  self.node_inputs.right_input = from_node
458
- elif insert_type == 'left':
489
+ elif insert_type == "left":
459
490
  self.node_inputs.left_input = from_node
460
491
  else:
461
- raise Exception('Cannot find the connection')
492
+ raise Exception("Cannot find the connection")
462
493
  if self.setting_input.is_setup:
463
- 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":
464
495
  self.setting_input.depending_on_id = from_node.node_id
465
496
  self.reset()
466
497
  from_node.reset()
@@ -472,7 +503,7 @@ class FlowNode:
472
503
  deep: If True, the reset propagates recursively through the entire downstream graph.
473
504
  """
474
505
  for node in self.leads_to_nodes:
475
- self.print(f'resetting node: {node.node_id}')
506
+ self.print(f"resetting node: {node.node_id}")
476
507
  node.reset(deep)
477
508
 
478
509
  def get_flow_file_column_schema(self, col_name: str) -> FlowfileColumn | None:
@@ -488,7 +519,7 @@ class FlowNode:
488
519
  if s.column_name == col_name:
489
520
  return s
490
521
 
491
- def get_predicted_schema(self, force: bool = False) -> List[FlowfileColumn] | None:
522
+ def get_predicted_schema(self, force: bool = False) -> list[FlowfileColumn] | None:
492
523
  """Predicts the output schema of the node without full execution.
493
524
 
494
525
  It uses the schema_callback or infers from predicted data.
@@ -503,18 +534,18 @@ class FlowNode:
503
534
  if self.node_schema.predicted_schema and not force:
504
535
  return self.node_schema.predicted_schema
505
536
  if self.schema_callback is not None and (self.node_schema.predicted_schema is None or force):
506
- self.print('Getting the data from a schema callback')
537
+ self.print("Getting the data from a schema callback")
507
538
  if force:
508
539
  # Force the schema callback to reset, so that it will be executed again
509
540
  self.schema_callback.reset()
510
541
  schema = self.schema_callback()
511
542
  if schema is not None and len(schema) > 0:
512
- self.print('Calculating the schema based on the schema callback')
543
+ self.print("Calculating the schema based on the schema callback")
513
544
  self.node_schema.predicted_schema = schema
514
545
  return self.node_schema.predicted_schema
515
546
  predicted_data = self._predicted_data_getter()
516
547
  if predicted_data is not None and predicted_data.schema is not None:
517
- self.print('Calculating the schema based on the predicted resulting data')
548
+ self.print("Calculating the schema based on the predicted resulting data")
518
549
  self.node_schema.predicted_schema = self._predicted_data_getter().schema
519
550
 
520
551
  return self.node_schema.predicted_schema
@@ -527,7 +558,7 @@ class FlowNode:
527
558
  True if the node is set up, False otherwise.
528
559
  """
529
560
  if not self.node_information.is_setup:
530
- if self.function.__name__ != 'placeholder':
561
+ if self.function.__name__ != "placeholder":
531
562
  self.node_information.is_setup = True
532
563
  self.setting_input.is_setup = True
533
564
  return self.node_information.is_setup
@@ -538,7 +569,7 @@ class FlowNode:
538
569
  Args:
539
570
  v: The message or value to log.
540
571
  """
541
- 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}")
542
573
 
543
574
  def get_resulting_data(self) -> FlowDataEngine | None:
544
575
  """Executes the node's function to produce the actual output data.
@@ -553,11 +584,11 @@ class FlowNode:
553
584
  """
554
585
  if self.is_setup:
555
586
  if self.results.resulting_data is None and self.results.errors is None:
556
- self.print('getting resulting data')
587
+ self.print("getting resulting data")
557
588
  try:
558
589
  if isinstance(self.function, FlowDataEngine):
559
590
  fl: FlowDataEngine = self.function
560
- elif self.node_type == 'external_source':
591
+ elif self.node_type == "external_source":
561
592
  fl: FlowDataEngine = self.function()
562
593
  fl.collect_external()
563
594
  self.node_settings.streamable = False
@@ -590,14 +621,14 @@ class FlowNode:
590
621
  return fl
591
622
  except ValueError as e:
592
623
  if str(e) == "generator already executing":
593
- logger.info('Generator already executing, waiting for the result')
624
+ logger.info("Generator already executing, waiting for the result")
594
625
  sleep(1)
595
626
  return self._predicted_data_getter()
596
627
  fl = FlowDataEngine()
597
628
  return fl
598
629
 
599
630
  except Exception as e:
600
- 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")
601
632
  logger.warning(e)
602
633
 
603
634
  def get_predicted_resulting_data(self) -> FlowDataEngine:
@@ -609,7 +640,7 @@ class FlowNode:
609
640
  A FlowDataEngine instance with a schema but no data.
610
641
  """
611
642
  if self.needs_run(False) and self.schema_callback is not None or self.node_schema.result_schema is not None:
612
- self.print('Getting data based on the schema')
643
+ self.print("Getting data based on the schema")
613
644
 
614
645
  _s = self.schema_callback() if self.node_schema.result_schema is None else self.node_schema.result_schema
615
646
  return FlowDataEngine.create_from_schema(_s)
@@ -649,7 +680,7 @@ class FlowNode:
649
680
  yield n
650
681
 
651
682
  @property
652
- def schema(self) -> List[FlowfileColumn]:
683
+ def schema(self) -> list[FlowfileColumn]:
653
684
  """Gets the definitive output schema of the node.
654
685
 
655
686
  If not already run, it falls back to the predicted schema.
@@ -661,7 +692,7 @@ class FlowNode:
661
692
  if self.is_setup and self.results.errors is None:
662
693
  if self.node_schema.result_schema is not None and len(self.node_schema.result_schema) > 0:
663
694
  return self.node_schema.result_schema
664
- elif self.node_type == 'output':
695
+ elif self.node_type == "output":
665
696
  if len(self.node_inputs.main_inputs) > 0:
666
697
  self.node_schema.result_schema = self.node_inputs.main_inputs[0].schema
667
698
  else:
@@ -680,11 +711,15 @@ class FlowNode:
680
711
  """
681
712
 
682
713
  if results_exists(self.hash):
683
- logger.warning('Not implemented')
714
+ logger.warning("Not implemented")
684
715
  clear_task_from_worker(self.hash)
685
716
 
686
- def needs_run(self, performance_mode: bool, node_logger: NodeLogger = None,
687
- 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:
688
723
  """Determines if the node needs to be executed.
689
724
 
690
725
  The decision is based on its run state, caching settings, and execution mode.
@@ -703,7 +738,7 @@ class FlowNode:
703
738
  flow_logger = logger if node_logger is None else node_logger
704
739
  cache_result_exists = results_exists(self.hash)
705
740
  if not self.node_stats.has_run_with_current_setup:
706
- flow_logger.info('Node has not run, needs to run')
741
+ flow_logger.info("Node has not run, needs to run")
707
742
  return True
708
743
  if self.node_settings.cache_results and cache_result_exists:
709
744
  return False
@@ -737,7 +772,9 @@ class FlowNode:
737
772
  if example_data is None:
738
773
  example_data = resulting_data.get_sample(100).to_arrow()
739
774
  return example_data
775
+
740
776
  return get_example_data
777
+
741
778
  resulting_data = self.get_resulting_data()
742
779
 
743
780
  if not performance_mode:
@@ -759,8 +796,13 @@ class FlowNode:
759
796
  try:
760
797
  resulting_data = self.get_resulting_data()
761
798
  if not performance_mode:
762
- external_sampler = ExternalSampler(lf=resulting_data.data_frame, file_ref=self.hash,
763
- 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
+ )
764
806
  self.store_example_data_generator(external_sampler)
765
807
  if self.results.errors is None and not self.node_stats.is_canceled:
766
808
  self.node_stats.has_run_with_current_setup = True
@@ -790,48 +832,58 @@ class FlowNode:
790
832
  Exception: If the node_logger is not provided or if execution fails.
791
833
  """
792
834
  if node_logger is None:
793
- raise Exception('Node logger is not defined')
835
+ raise Exception("Node logger is not defined")
794
836
  if self.node_settings.cache_results and results_exists(self.hash):
795
837
  try:
796
838
  self.results.resulting_data = get_external_df_result(self.hash)
797
839
  self._cache_progress = None
798
840
  return
799
- except Exception as e:
800
- node_logger.warning('Failed to read the cache, rerunning the code')
801
- 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":
802
844
  self.results.resulting_data = self.get_resulting_data()
803
845
  self.node_stats.has_run_with_current_setup = True
804
846
  return
805
847
  try:
806
848
  self.get_resulting_data()
807
849
  except Exception as e:
808
- 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"
809
851
  raise e
810
852
  if not performance_mode:
811
- external_df_fetcher = ExternalDfFetcher(lf=self.get_resulting_data().data_frame,
812
- file_ref=self.hash, wait_on_completion=False,
813
- flow_id=node_logger.flow_id,
814
- 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
+ )
815
860
  self._fetch_cached_df = external_df_fetcher
816
861
  try:
817
862
  lf = external_df_fetcher.get_result()
818
863
  self.results.resulting_data = FlowDataEngine(
819
- lf, number_of_records=ExternalDfFetcher(lf=lf, operation_type='calculate_number_of_records',
820
- 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,
821
871
  )
822
872
  if not performance_mode:
823
873
  self.store_example_data_generator(external_df_fetcher)
824
874
  self.node_stats.has_run_with_current_setup = True
825
875
 
826
876
  except Exception as e:
827
- node_logger.error('Error with external process')
877
+ node_logger.error("Error with external process")
828
878
  if external_df_fetcher.error_code == -1:
829
879
  try:
830
880
  self.results.resulting_data = self.get_resulting_data()
831
- self.results.warnings = ('Error with external process (unknown error), '
832
- 'likely the process was killed by the server because of memory constraints, '
833
- 'continue with the process. '
834
- '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
+ )
835
887
  except Exception as e:
836
888
  self.results.errors = str(e)
837
889
  raise e
@@ -858,15 +910,18 @@ class FlowNode:
858
910
  self._fetch_cached_df.cancel()
859
911
  self.node_stats.is_canceled = True
860
912
  else:
861
- logger.warning('No external process to cancel')
913
+ logger.warning("No external process to cancel")
862
914
  self.node_stats.is_canceled = True
863
915
 
864
- def execute_node(self, run_location: schemas.ExecutionLocationsLiteral,
865
- reset_cache: bool = False,
866
- performance_mode: bool = False,
867
- retry: bool = True,
868
- node_logger: NodeLogger = None,
869
- 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
+ ):
870
925
  """Orchestrates the execution, handling location, caching, and retries.
871
926
 
872
927
  Args:
@@ -882,7 +937,7 @@ class FlowNode:
882
937
  Exception: If the node_logger is not defined.
883
938
  """
884
939
  if node_logger is None:
885
- raise Exception('Flow logger is not defined')
940
+ raise Exception("Flow logger is not defined")
886
941
  # TODO: Simplify which route is being picked there are many duplicate checks
887
942
 
888
943
  if reset_cache:
@@ -891,63 +946,78 @@ class FlowNode:
891
946
  self.node_stats.has_completed_last_run = False
892
947
 
893
948
  if self.is_setup:
894
- node_logger.info(f'Starting to run {self.__name__}')
895
- if (self.needs_run(performance_mode, node_logger, run_location) or self.node_template.node_group == "output"
896
- 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
+ ):
897
955
  self.clear_table_example()
898
956
  self.prepare_before_run()
899
957
  self.reset()
900
958
  try:
901
- if (((run_location == 'remote' or
902
- (self.node_default.transform_type == 'wide' and optimize_for_downstream) and
903
- not run_location == 'local'))
904
- or self.node_settings.cache_results):
905
- 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")
906
965
  if self.node_settings.cache_results:
907
966
  performance_mode = False
908
- self.execute_remote(performance_mode=(performance_mode if not self.node_settings.cache_results
909
- else False),
910
- node_logger=node_logger
911
- )
967
+ self.execute_remote(
968
+ performance_mode=(performance_mode if not self.node_settings.cache_results else False),
969
+ node_logger=node_logger,
970
+ )
912
971
  else:
913
- node_logger.info('Running the node locally')
972
+ node_logger.info("Running the node locally")
914
973
  self.execute_local(performance_mode=performance_mode, flow_id=node_logger.flow_id)
915
974
  except Exception as e:
916
- if 'No such file or directory (os error' in str(e) and retry:
917
- logger.warning('Error with the input node, starting to rerun the input node...')
918
- 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()
919
978
  for node_input in all_inputs:
920
- node_input.execute_node(run_location=run_location,
921
- performance_mode=performance_mode, retry=True,
922
- reset_cache=True,
923
- node_logger=node_logger)
924
- self.execute_node(run_location=run_location,
925
- performance_mode=performance_mode, retry=False,
926
- 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
+ )
927
992
  else:
928
993
  self.results.errors = str(e)
929
994
  if "Connection refused" in str(e) and "/submit_query/" in str(e):
930
- node_logger.warning("There was an issue connecting to the remote worker, "
931
- "ensure the worker process is running, "
932
- "or change the settings to, so it executes locally")
933
- 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
+ )
934
1003
  else:
935
- node_logger.error(f'Error with running the node: {e}')
936
- elif ((run_location == 'local') and
937
- (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
+ ):
938
1008
  try:
939
- node_logger.info('Executing fully locally')
1009
+ node_logger.info("Executing fully locally")
940
1010
  self.execute_full_local(performance_mode)
941
1011
  except Exception as e:
942
1012
  self.results.errors = str(e)
943
- node_logger.error(f'Error with running the node: {e}')
1013
+ node_logger.error(f"Error with running the node: {e}")
944
1014
  self.node_stats.error = str(e)
945
1015
  self.node_stats.has_completed_last_run = False
946
1016
 
947
1017
  else:
948
- node_logger.info('Node has already run, not running the node')
1018
+ node_logger.info("Node has already run, not running the node")
949
1019
  else:
950
- 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")
951
1021
 
952
1022
  def store_example_data_generator(self, external_df_fetcher: ExternalDfFetcher | ExternalSampler):
953
1023
  """Stores a generator function for fetching a sample of the result data.
@@ -960,7 +1030,7 @@ class FlowNode:
960
1030
  self.results.example_data_path = file_ref
961
1031
  self.results.example_data_generator = get_read_top_n(file_path=file_ref, n=100)
962
1032
  else:
963
- 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")
964
1034
 
965
1035
  def needs_reset(self) -> bool:
966
1036
  """Checks if the node's hash has changed, indicating an outdated state.
@@ -980,7 +1050,7 @@ class FlowNode:
980
1050
  """
981
1051
  needs_reset = self.needs_reset() or deep
982
1052
  if needs_reset:
983
- logger.info(f'{self.node_id}: Node needs reset')
1053
+ logger.info(f"{self.node_id}: Node needs reset")
984
1054
  self.node_stats.has_run_with_current_setup = False
985
1055
  self.results.reset()
986
1056
  self.node_schema.result_schema = None
@@ -991,7 +1061,7 @@ class FlowNode:
991
1061
  if self.is_correct:
992
1062
  self._schema_callback = None # Ensure the schema callback is reset
993
1063
  if self.schema_callback:
994
- logger.info(f'{self.node_id}: Resetting the schema callback')
1064
+ logger.info(f"{self.node_id}: Resetting the schema callback")
995
1065
  self.schema_callback.start()
996
1066
  self.evaluate_nodes()
997
1067
  _ = self.hash # Recalculate the hash after reset
@@ -1005,17 +1075,18 @@ class FlowNode:
1005
1075
  Returns:
1006
1076
  True if the connection was found and removed, False otherwise.
1007
1077
  """
1008
- logger.info(f'Deleting lead to node: {node_id}')
1078
+ logger.info(f"Deleting lead to node: {node_id}")
1009
1079
  for i, lead_to_node in enumerate(self.leads_to_nodes):
1010
- 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}")
1011
1081
  if lead_to_node.node_id == node_id:
1012
- logger.info(f'Found the node to delete: {node_id}')
1082
+ logger.info(f"Found the node to delete: {node_id}")
1013
1083
  self.leads_to_nodes.pop(i)
1014
1084
  return True
1015
1085
  return False
1016
1086
 
1017
- def delete_input_node(self, node_id: int, connection_type: input_schema.InputConnectionClass = 'input-0',
1018
- 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:
1019
1090
  """Removes a connection from a specific input node.
1020
1091
 
1021
1092
  Args:
@@ -1027,23 +1098,23 @@ class FlowNode:
1027
1098
  True if a connection was found and removed, False otherwise.
1028
1099
  """
1029
1100
  deleted: bool = False
1030
- if connection_type == 'input-0':
1101
+ if connection_type == "input-0":
1031
1102
  for i, node in enumerate(self.node_inputs.main_inputs):
1032
1103
  if node.node_id == node_id:
1033
1104
  self.node_inputs.main_inputs.pop(i)
1034
1105
  deleted = True
1035
1106
  if not complete:
1036
1107
  continue
1037
- elif connection_type == 'input-1' or complete:
1108
+ elif connection_type == "input-1" or complete:
1038
1109
  if self.node_inputs.right_input is not None and self.node_inputs.right_input.node_id == node_id:
1039
1110
  self.node_inputs.right_input = None
1040
1111
  deleted = True
1041
- elif connection_type == 'input-2' or complete:
1112
+ elif connection_type == "input-2" or complete:
1042
1113
  if self.node_inputs.left_input is not None and self.node_inputs.right_input.node_id == node_id:
1043
1114
  self.node_inputs.left_input = None
1044
1115
  deleted = True
1045
1116
  else:
1046
- logger.warning('Could not find the connection to delete...')
1117
+ logger.warning("Could not find the connection to delete...")
1047
1118
  if deleted:
1048
1119
  self.reset()
1049
1120
  return deleted
@@ -1056,7 +1127,7 @@ class FlowNode:
1056
1127
  """
1057
1128
  return f"Node id: {self.node_id} ({self.node_type})"
1058
1129
 
1059
- def _get_readable_schema(self) -> List[dict] | None:
1130
+ def _get_readable_schema(self) -> list[dict] | None:
1060
1131
  """Helper to get a simplified, dictionary representation of the output schema.
1061
1132
 
1062
1133
  Returns:
@@ -1074,11 +1145,14 @@ class FlowNode:
1074
1145
  Returns:
1075
1146
  A dictionary containing key information about the node.
1076
1147
  """
1077
- return dict(FlowNode=
1078
- dict(node_id=self.node_id,
1079
- step_name=self.__name__,
1080
- output_columns=self.node_schema.output_columns,
1081
- 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
+ )
1082
1156
 
1083
1157
  @property
1084
1158
  def number_of_leads_to_nodes(self) -> int | None:
@@ -1149,14 +1223,13 @@ class FlowNode:
1149
1223
  Returns:
1150
1224
  A `TableExample` object, or None if the node is not set up.
1151
1225
  """
1152
- self.print('Getting a table example')
1226
+ self.print("Getting a table example")
1153
1227
  if self.is_setup and include_data and self.node_stats.has_completed_last_run:
1154
-
1155
- if self.node_template.node_group == 'output':
1156
- self.print('getting the table example')
1228
+ if self.node_template.node_group == "output":
1229
+ self.print("getting the table example")
1157
1230
  return self.main_input[0].get_table_example(include_data)
1158
1231
 
1159
- logger.info('getting the table example since the node has run')
1232
+ logger.info("getting the table example since the node has run")
1160
1233
  example_data_getter = self.results.example_data_generator
1161
1234
  if example_data_getter is not None:
1162
1235
  data = example_data_getter().to_pylist()
@@ -1168,26 +1241,34 @@ class FlowNode:
1168
1241
  fl = self.get_resulting_data()
1169
1242
  has_example_data = self.results.example_data_generator is not None
1170
1243
 
1171
- return TableExample(node_id=self.node_id,
1172
- name=str(self.node_id), number_of_records=999,
1173
- number_of_columns=fl.number_of_fields,
1174
- table_schema=schema, columns=fl.columns, data=data,
1175
- has_example_data=has_example_data,
1176
- has_run_with_current_setup=self.node_stats.has_run_with_current_setup
1177
- )
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
+ )
1178
1255
  else:
1179
- 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")
1180
1257
  try:
1181
1258
  schema = [FileColumn.model_validate(c.get_column_repr()) for c in self.schema]
1182
1259
  except Exception as e:
1183
1260
  logger.warning(e)
1184
1261
  schema = []
1185
1262
  columns = [s.name for s in schema]
1186
- return TableExample(node_id=self.node_id,
1187
- name=str(self.node_id), number_of_records=0,
1188
- number_of_columns=len(columns),
1189
- table_schema=schema, columns=columns,
1190
- 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
+ )
1191
1272
 
1192
1273
  def get_node_data(self, flow_id: int, include_example: bool = False) -> NodeData:
1193
1274
  """Gathers all necessary data for representing the node in the UI.
@@ -1199,11 +1280,13 @@ class FlowNode:
1199
1280
  Returns:
1200
1281
  A `NodeData` object.
1201
1282
  """
1202
- node = NodeData(flow_id=flow_id,
1203
- node_id=self.node_id,
1204
- has_run=self.node_stats.has_run_with_current_setup,
1205
- setting_input=self.setting_input,
1206
- 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
+ )
1207
1290
  if self.main_input:
1208
1291
  node.main_input = self.main_input[0].get_table_example()
1209
1292
  if self.left_input:
@@ -1215,6 +1298,9 @@ class FlowNode:
1215
1298
  node = setting_generator.get_setting_generator(self.node_type)(node)
1216
1299
 
1217
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
1218
1304
  return node
1219
1305
 
1220
1306
  def get_output_data(self) -> TableExample:
@@ -1231,12 +1317,14 @@ class FlowNode:
1231
1317
  Returns:
1232
1318
  A `NodeInput` object.
1233
1319
  """
1234
- return schemas.NodeInput(pos_y=self.setting_input.pos_y,
1235
- pos_x=self.setting_input.pos_x,
1236
- id=self.node_id,
1237
- **self.node_template.__dict__)
1238
-
1239
- 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]:
1240
1328
  """Generates `NodeEdge` objects for all input connections to this node.
1241
1329
 
1242
1330
  Returns:
@@ -1245,24 +1333,33 @@ class FlowNode:
1245
1333
  edges = []
1246
1334
  if self.node_inputs.main_inputs is not None:
1247
1335
  for i, main_input in enumerate(self.node_inputs.main_inputs):
1248
- edges.append(schemas.NodeEdge(id=f'{main_input.node_id}-{self.node_id}-{i}',
1249
- source=main_input.node_id,
1250
- target=self.node_id,
1251
- sourceHandle='output-0',
1252
- targetHandle='input-0',
1253
- ))
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
+ )
1254
1345
  if self.node_inputs.left_input is not None:
1255
- edges.append(schemas.NodeEdge(id=f'{self.node_inputs.left_input.node_id}-{self.node_id}-right',
1256
- source=self.node_inputs.left_input.node_id,
1257
- target=self.node_id,
1258
- sourceHandle='output-0',
1259
- targetHandle='input-2',
1260
- ))
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
+ )
1261
1355
  if self.node_inputs.right_input is not None:
1262
- edges.append(schemas.NodeEdge(id=f'{self.node_inputs.right_input.node_id}-{self.node_id}-left',
1263
- source=self.node_inputs.right_input.node_id,
1264
- target=self.node_id,
1265
- sourceHandle='output-0',
1266
- targetHandle='input-1',
1267
- ))
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
+ )
1268
1365
  return edges