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,16 +1,27 @@
1
1
 
2
- from typing import Dict, Any
2
+ import ast
3
+ import re
4
+ from typing import Dict, Any, List, Optional
5
+ from pathlib import Path
3
6
 
4
- from fastapi import APIRouter, HTTPException, Depends
7
+ from fastapi import APIRouter, HTTPException, Depends, UploadFile, File
8
+ from fastapi.responses import FileResponse
9
+ from pydantic import BaseModel
5
10
 
6
11
  from flowfile_core import flow_file_handler
7
12
  # Core modules
8
13
  from flowfile_core.auth.jwt import get_current_active_user
9
14
  from flowfile_core.configs import logger
10
- from flowfile_core.configs.node_store import CUSTOM_NODE_STORE
15
+ from flowfile_core.configs.node_store import (
16
+ CUSTOM_NODE_STORE,
17
+ add_to_custom_node_store,
18
+ remove_from_custom_node_store,
19
+ load_single_node_from_file,
20
+ )
11
21
  # File handling
12
22
  from flowfile_core.schemas import input_schema
13
23
  from flowfile_core.utils.utils import camel_case_to_snake_case
24
+ from shared import storage
14
25
 
15
26
  # External dependencies
16
27
 
@@ -18,6 +29,22 @@ from flowfile_core.utils.utils import camel_case_to_snake_case
18
29
  router = APIRouter()
19
30
 
20
31
 
32
+ class CustomNodeInfo(BaseModel):
33
+ """Info about a custom node file."""
34
+ file_name: str
35
+ node_name: str = ""
36
+ node_category: str = ""
37
+ title: str = ""
38
+ intro: str = ""
39
+ node_icon: str = "user-defined-icon.png"
40
+
41
+
42
+ class SaveCustomNodeRequest(BaseModel):
43
+ """Request model for saving a custom node."""
44
+ file_name: str
45
+ code: str
46
+
47
+
21
48
  @router.get("/custom-node-schema", summary="Get a simple UI schema")
22
49
  def get_simple_custom_object(flow_id: int, node_id: int):
23
50
  """
@@ -48,8 +75,460 @@ def update_user_defined_node(input_data: Dict[str, Any], node_type: str, current
48
75
  user_defined_model = CUSTOM_NODE_STORE.get(node_type)
49
76
  if not user_defined_model:
50
77
  raise HTTPException(status_code=404, detail=f"Node type '{node_type}' not found")
51
-
78
+ print('adding user defined node')
79
+ print(input_data)
80
+ print('-----')
52
81
  user_defined_node_settings = input_schema.UserDefinedNode.model_validate(input_data)
53
82
  initialized_model = user_defined_model.from_settings(user_defined_node_settings.settings)
54
83
 
55
84
  flow.add_user_defined_node(custom_node=initialized_model, user_defined_node_settings=user_defined_node_settings)
85
+
86
+
87
+ @router.post("/save-custom-node", summary="Save a custom node definition")
88
+ def save_custom_node(request: SaveCustomNodeRequest):
89
+ """
90
+ Save a custom node Python file to the user-defined nodes directory.
91
+
92
+ This endpoint:
93
+ 1. Validates the Python syntax
94
+ 2. Ensures the file name is safe
95
+ 3. Writes the file to the user_defined_nodes directory
96
+ 4. Attempts to load and register the new node
97
+ """
98
+ # Validate file name
99
+ file_name = request.file_name
100
+ if not file_name.endswith('.py'):
101
+ file_name += '.py'
102
+
103
+ # Sanitize file name - only allow alphanumeric, underscore, and .py extension
104
+ safe_name = re.sub(r'[^a-zA-Z0-9_]', '_', file_name[:-3]) + '.py'
105
+ if not safe_name or safe_name == '.py':
106
+ raise HTTPException(status_code=400, detail="Invalid file name")
107
+
108
+ # Validate Python syntax
109
+ try:
110
+ ast.parse(request.code)
111
+ except SyntaxError as e:
112
+ raise HTTPException(
113
+ status_code=400,
114
+ detail=f"Python syntax error at line {e.lineno}: {e.msg}"
115
+ )
116
+
117
+ # Get the directory path
118
+ nodes_dir = storage.user_defined_nodes_directory
119
+ file_path = nodes_dir / safe_name
120
+
121
+ # Write the file
122
+ try:
123
+ with open(file_path, 'w', encoding='utf-8') as f:
124
+ f.write(request.code)
125
+ logger.info(f"Saved custom node to {file_path}")
126
+ except Exception as e:
127
+ logger.error(f"Failed to save custom node: {e}")
128
+ raise HTTPException(status_code=500, detail=f"Failed to save file: {str(e)}")
129
+
130
+ # Try to load and register the node using the centralized loader
131
+ try:
132
+ node_class = load_single_node_from_file(file_path)
133
+ if node_class:
134
+ add_to_custom_node_store(node_class)
135
+ logger.info(f"Registered custom node: {node_class().node_name}")
136
+ except Exception as e:
137
+ logger.warning(f"Node saved but failed to load: {e}")
138
+ # Don't fail the request - the file is saved, it just couldn't be loaded yet
139
+
140
+ return {
141
+ "success": True,
142
+ "file_name": safe_name,
143
+ "message": f"Node saved successfully to {safe_name}"
144
+ }
145
+
146
+
147
+ def _extract_node_info_from_file(file_path: Path) -> CustomNodeInfo:
148
+ """Extract node metadata from a Python file by parsing its AST."""
149
+ info = CustomNodeInfo(file_name=file_path.name)
150
+
151
+ try:
152
+ with open(file_path, 'r', encoding='utf-8') as f:
153
+ content = f.read()
154
+
155
+ tree = ast.parse(content)
156
+
157
+ # Find class definitions that might be custom nodes
158
+ for node in ast.walk(tree):
159
+ if isinstance(node, ast.ClassDef):
160
+ # Look for class attributes (both annotated and simple assignments)
161
+ for item in node.body:
162
+ attr_name = None
163
+ value = None
164
+
165
+ # Handle annotated assignments: node_name: str = "value"
166
+ if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
167
+ attr_name = item.target.id
168
+ if item.value and isinstance(item.value, ast.Constant) and isinstance(item.value.value, str):
169
+ value = item.value.value
170
+ # Handle simple assignments: node_name = "value"
171
+ elif isinstance(item, ast.Assign):
172
+ for target in item.targets:
173
+ if isinstance(target, ast.Name):
174
+ attr_name = target.id
175
+ if isinstance(item.value, ast.Constant) and isinstance(item.value.value, str):
176
+ value = item.value.value
177
+ break
178
+
179
+ # Map attribute names to info fields
180
+ if attr_name and value:
181
+ if attr_name == "node_name":
182
+ info.node_name = value
183
+ elif attr_name == "node_category":
184
+ info.node_category = value
185
+ elif attr_name == "title":
186
+ info.title = value
187
+ elif attr_name == "intro":
188
+ info.intro = value
189
+ elif attr_name == "node_icon":
190
+ info.node_icon = value
191
+
192
+ # If we found a node_name, this is likely a custom node class
193
+ if info.node_name:
194
+ break
195
+
196
+ except Exception as e:
197
+ logger.warning(f"Failed to parse node info from {file_path}: {e}")
198
+
199
+ return info
200
+
201
+
202
+ @router.get("/list-custom-nodes", summary="List all custom nodes", response_model=List[CustomNodeInfo])
203
+ def list_custom_nodes() -> List[CustomNodeInfo]:
204
+ """
205
+ List all custom node Python files in the user-defined nodes directory.
206
+ Returns basic metadata extracted from each file.
207
+ """
208
+ nodes_dir = storage.user_defined_nodes_directory
209
+ nodes: List[CustomNodeInfo] = []
210
+
211
+ if not nodes_dir.exists():
212
+ return nodes
213
+
214
+ for file_path in nodes_dir.glob("*.py"):
215
+ if file_path.name.startswith("_"):
216
+ continue # Skip private files
217
+ info = _extract_node_info_from_file(file_path)
218
+ nodes.append(info)
219
+
220
+ # Sort by node name
221
+ nodes.sort(key=lambda x: x.node_name or x.file_name)
222
+ return nodes
223
+
224
+
225
+ @router.get("/get-custom-node/{file_name}", summary="Get custom node details")
226
+ def get_custom_node(file_name: str) -> Dict[str, Any]:
227
+ """
228
+ Get the full content and parsed metadata of a custom node file.
229
+ This endpoint is used by the Node Designer to load an existing node for editing.
230
+ """
231
+ # Sanitize file name
232
+ if not file_name.endswith('.py'):
233
+ file_name += '.py'
234
+
235
+ safe_name = re.sub(r'[^a-zA-Z0-9_.]', '_', file_name)
236
+ file_path = storage.user_defined_nodes_directory / safe_name
237
+
238
+ if not file_path.exists():
239
+ raise HTTPException(status_code=404, detail=f"Node file '{safe_name}' not found")
240
+
241
+ try:
242
+ with open(file_path, 'r', encoding='utf-8') as f:
243
+ content = f.read()
244
+ except Exception as e:
245
+ raise HTTPException(status_code=500, detail=f"Failed to read file: {str(e)}")
246
+
247
+ # Parse the file to extract metadata and sections
248
+ result = {
249
+ "file_name": safe_name,
250
+ "content": content,
251
+ "metadata": {},
252
+ "sections": [],
253
+ "processCode": ""
254
+ }
255
+
256
+ try:
257
+ tree = ast.parse(content)
258
+
259
+ for node in ast.walk(tree):
260
+ if isinstance(node, ast.ClassDef):
261
+ # Check if this looks like a custom node class (has node_name attribute)
262
+ is_custom_node = False
263
+ for item in node.body:
264
+ # Check annotated assignments: node_name: str = "value"
265
+ if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
266
+ if item.target.id == "node_name":
267
+ is_custom_node = True
268
+ break
269
+ # Check simple assignments: node_name = "value"
270
+ elif isinstance(item, ast.Assign):
271
+ for target in item.targets:
272
+ if isinstance(target, ast.Name) and target.id == "node_name":
273
+ is_custom_node = True
274
+ break
275
+
276
+ if is_custom_node:
277
+ # Extract metadata from both annotated and simple assignments
278
+ for item in node.body:
279
+ attr_name = None
280
+ value = None
281
+
282
+ if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
283
+ attr_name = item.target.id
284
+ if item.value and isinstance(item.value, ast.Constant):
285
+ value = item.value.value
286
+ elif isinstance(item, ast.Assign):
287
+ for target in item.targets:
288
+ if isinstance(target, ast.Name):
289
+ attr_name = target.id
290
+ if isinstance(item.value, ast.Constant):
291
+ value = item.value.value
292
+ break
293
+
294
+ if attr_name and value is not None:
295
+ if attr_name in ["node_name", "node_category", "title", "intro", "node_icon"]:
296
+ result["metadata"][attr_name] = value
297
+ elif attr_name == "number_of_inputs":
298
+ result["metadata"]["number_of_inputs"] = value
299
+ elif attr_name == "number_of_outputs":
300
+ result["metadata"]["number_of_outputs"] = value
301
+
302
+ # Extract process method
303
+ for item in node.body:
304
+ if isinstance(item, ast.FunctionDef) and item.name == "process":
305
+ # Get the source code of the process method
306
+ start_line = item.lineno - 1
307
+ end_line = item.end_lineno if hasattr(item, 'end_lineno') else start_line + 20
308
+ lines = content.split('\n')
309
+ process_lines = lines[start_line:end_line]
310
+ result["processCode"] = '\n'.join(process_lines)
311
+ break
312
+
313
+ break
314
+
315
+ except Exception as e:
316
+ logger.warning(f"Failed to parse custom node file: {e}")
317
+ # Return the raw content even if parsing fails
318
+
319
+ return result
320
+
321
+
322
+ @router.delete("/delete-custom-node/{file_name}", summary="Delete a custom node")
323
+ def delete_custom_node(file_name: str) -> Dict[str, Any]:
324
+ """
325
+ Delete a custom node Python file from the user-defined nodes directory.
326
+ This also attempts to unregister the node from the node store.
327
+ """
328
+ # Sanitize file name
329
+ if not file_name.endswith('.py'):
330
+ file_name += '.py'
331
+
332
+ safe_name = re.sub(r'[^a-zA-Z0-9_.]', '_', file_name)
333
+ file_path = storage.user_defined_nodes_directory / safe_name
334
+
335
+ if not file_path.exists():
336
+ raise HTTPException(status_code=404, detail=f"Node file '{safe_name}' not found")
337
+
338
+ # Try to find and unregister the node from all stores
339
+ try:
340
+ info = _extract_node_info_from_file(file_path)
341
+ file_stem = file_path.stem # filename without .py extension
342
+ logger.info(f"Extracted node info: node_name='{info.node_name}', file_name='{info.file_name}', file_stem='{file_stem}'")
343
+
344
+ # Use the centralized remove function which cleans up all stores
345
+ # Pass both the computed key from node_name and the file_stem as fallback
346
+ if info.node_name:
347
+ node_type_key = info.node_name.lower().replace(' ', '_')
348
+ logger.info(f"Computed node_type_key: '{node_type_key}'")
349
+ else:
350
+ node_type_key = file_stem
351
+ logger.info(f"Using file_stem as node_type_key: '{node_type_key}'")
352
+
353
+ if remove_from_custom_node_store(node_type_key, file_stem=file_stem):
354
+ logger.info(f"Unregistered custom node: {info.node_name or file_stem}")
355
+ else:
356
+ logger.warning(f"Node '{node_type_key}' was not found in stores during unregister")
357
+ except Exception as e:
358
+ logger.warning(f"Could not unregister node: {e}")
359
+
360
+ # Delete the file
361
+ try:
362
+ file_path.unlink()
363
+ logger.info(f"Deleted custom node file: {file_path}")
364
+ except Exception as e:
365
+ logger.error(f"Failed to delete custom node file: {e}")
366
+ raise HTTPException(status_code=500, detail=f"Failed to delete file: {str(e)}")
367
+
368
+ return {
369
+ "success": True,
370
+ "file_name": safe_name,
371
+ "message": f"Node '{safe_name}' deleted successfully"
372
+ }
373
+
374
+
375
+ # ==================== Custom Icon Endpoints ====================
376
+
377
+ class IconInfo(BaseModel):
378
+ """Info about a custom icon file."""
379
+ file_name: str
380
+ is_custom: bool = True
381
+
382
+
383
+ ALLOWED_ICON_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.svg', '.gif', '.webp'}
384
+ MAX_ICON_SIZE = 5 * 1024 * 1024 # 5MB
385
+
386
+
387
+ @router.get("/list-icons", summary="List all available icons", response_model=List[IconInfo])
388
+ def list_icons() -> List[IconInfo]:
389
+ """
390
+ List all icon files available for custom nodes.
391
+ Returns icons from the user_defined_nodes/icons directory.
392
+ """
393
+ icons_dir = storage.user_defined_nodes_icons
394
+ icons: List[IconInfo] = []
395
+
396
+ if not icons_dir.exists():
397
+ return icons
398
+
399
+ for file_path in icons_dir.iterdir():
400
+ if file_path.is_file() and file_path.suffix.lower() in ALLOWED_ICON_EXTENSIONS:
401
+ icons.append(IconInfo(file_name=file_path.name, is_custom=True))
402
+
403
+ # Sort by file name
404
+ icons.sort(key=lambda x: x.file_name.lower())
405
+ return icons
406
+
407
+
408
+ @router.post("/upload-icon", summary="Upload a custom icon")
409
+ async def upload_icon(file: UploadFile = File(...)) -> Dict[str, Any]:
410
+ """
411
+ Upload a new icon file to the user_defined_nodes/icons directory.
412
+
413
+ Accepts PNG, JPG, JPEG, SVG, GIF, and WebP files up to 5MB.
414
+ """
415
+ if not file.filename:
416
+ raise HTTPException(status_code=400, detail="No file provided")
417
+
418
+ # Validate file extension
419
+ file_ext = Path(file.filename).suffix.lower()
420
+ if file_ext not in ALLOWED_ICON_EXTENSIONS:
421
+ raise HTTPException(
422
+ status_code=400,
423
+ detail=f"Invalid file type. Allowed types: {', '.join(ALLOWED_ICON_EXTENSIONS)}"
424
+ )
425
+
426
+ # Read file content
427
+ content = await file.read()
428
+
429
+ # Validate file size
430
+ if len(content) > MAX_ICON_SIZE:
431
+ raise HTTPException(
432
+ status_code=400,
433
+ detail=f"File too large. Maximum size is {MAX_ICON_SIZE // (1024 * 1024)}MB"
434
+ )
435
+
436
+ # Sanitize filename - preserve hyphens, dots, and underscores
437
+ safe_name = re.sub(r'[^a-zA-Z0-9_.\-]', '_', file.filename)
438
+ if not safe_name:
439
+ raise HTTPException(status_code=400, detail="Invalid file name")
440
+
441
+ icons_dir = storage.user_defined_nodes_icons
442
+
443
+ # Ensure the icons directory exists
444
+ icons_dir.mkdir(parents=True, exist_ok=True)
445
+
446
+ file_path = icons_dir / safe_name
447
+
448
+ # Write the file
449
+ try:
450
+ with open(file_path, 'wb') as f:
451
+ f.write(content)
452
+ logger.info(f"Uploaded icon: {file_path} (size: {len(content)} bytes)")
453
+ except Exception as e:
454
+ logger.error(f"Failed to save icon: {e}")
455
+ raise HTTPException(status_code=500, detail=f"Failed to save icon: {str(e)}")
456
+
457
+ return {
458
+ "success": True,
459
+ "file_name": safe_name,
460
+ "path": str(file_path),
461
+ "message": f"Icon '{safe_name}' uploaded successfully"
462
+ }
463
+
464
+
465
+ @router.get("/icon/{file_name}", summary="Get a custom icon file")
466
+ def get_icon(file_name: str) -> FileResponse:
467
+ """
468
+ Retrieve a custom icon file by name.
469
+ Returns the icon file for display in the UI.
470
+ """
471
+ # Sanitize file name - preserve hyphens, dots, and underscores
472
+ safe_name = re.sub(r'[^a-zA-Z0-9_.\-]', '_', file_name)
473
+
474
+ icons_dir = storage.user_defined_nodes_icons
475
+ file_path = icons_dir / safe_name
476
+
477
+ logger.debug(f"Attempting to serve icon: {file_path}")
478
+
479
+ if not file_path.exists():
480
+ logger.warning(f"Icon not found: {file_path} (icons_dir exists: {icons_dir.exists()})")
481
+ # List available icons for debugging
482
+ if icons_dir.exists():
483
+ available = list(icons_dir.iterdir())
484
+ logger.debug(f"Available icons: {[f.name for f in available]}")
485
+ raise HTTPException(status_code=404, detail=f"Icon '{safe_name}' not found at {file_path}")
486
+
487
+ # Validate it's actually an icon file
488
+ if file_path.suffix.lower() not in ALLOWED_ICON_EXTENSIONS:
489
+ raise HTTPException(status_code=400, detail="Invalid file type")
490
+
491
+ # Determine content type
492
+ content_type_map = {
493
+ '.png': 'image/png',
494
+ '.jpg': 'image/jpeg',
495
+ '.jpeg': 'image/jpeg',
496
+ '.svg': 'image/svg+xml',
497
+ '.gif': 'image/gif',
498
+ '.webp': 'image/webp',
499
+ }
500
+ content_type = content_type_map.get(file_path.suffix.lower(), 'application/octet-stream')
501
+
502
+ return FileResponse(
503
+ path=file_path,
504
+ media_type=content_type,
505
+ filename=safe_name
506
+ )
507
+
508
+
509
+ @router.delete("/delete-icon/{file_name}", summary="Delete a custom icon")
510
+ def delete_icon(file_name: str) -> Dict[str, Any]:
511
+ """
512
+ Delete a custom icon file from the icons directory.
513
+ """
514
+ # Sanitize file name - preserve hyphens, dots, and underscores
515
+ safe_name = re.sub(r'[^a-zA-Z0-9_.\-]', '_', file_name)
516
+
517
+ icons_dir = storage.user_defined_nodes_icons
518
+ file_path = icons_dir / safe_name
519
+
520
+ if not file_path.exists():
521
+ raise HTTPException(status_code=404, detail=f"Icon '{safe_name}' not found")
522
+
523
+ try:
524
+ file_path.unlink()
525
+ logger.info(f"Deleted icon: {file_path}")
526
+ except Exception as e:
527
+ logger.error(f"Failed to delete icon: {e}")
528
+ raise HTTPException(status_code=500, detail=f"Failed to delete icon: {str(e)}")
529
+
530
+ return {
531
+ "success": True,
532
+ "file_name": safe_name,
533
+ "message": f"Icon '{safe_name}' deleted successfully"
534
+ }
flowfile_core/run_lock.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
 
3
-
4
3
  flow_run_locks = {}
5
4
 
6
5
 
@@ -1,8 +1,6 @@
1
- from flowfile_core.schemas import input_schema as node_interface, transform_schema as transformation_settings
2
- from flowfile_core.schemas.schemas import FlowSettings, FlowInformation
1
+ from flowfile_core.schemas import input_schema as node_interface
2
+ from flowfile_core.schemas import transform_schema as transformation_settings
3
3
  from flowfile_core.schemas.input_schema import RawData
4
+ from flowfile_core.schemas.schemas import FlowInformation, FlowSettings
4
5
 
5
-
6
- __all__ = [
7
- "transformation_settings", "node_interface", "FlowSettings", "FlowInformation", "RawData"
8
- ]
6
+ __all__ = ["transformation_settings", "node_interface", "FlowSettings", "FlowInformation", "RawData"]