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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. build_backends/main.py +25 -22
  2. build_backends/main_prd.py +10 -19
  3. flowfile/__init__.py +179 -73
  4. flowfile/__main__.py +10 -7
  5. flowfile/api.py +52 -59
  6. flowfile/web/__init__.py +14 -9
  7. flowfile/web/static/assets/AdminView-49392a9a.js +713 -0
  8. flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
  9. flowfile/web/static/assets/CloudConnectionView-36bcd6df.css +72 -0
  10. flowfile/web/static/assets/{CloudConnectionManager-d3248f8d.js → CloudConnectionView-f13f202b.js} +11 -11
  11. flowfile/web/static/assets/{CloudStorageReader-d65bf041.js → CloudStorageReader-0023d4a5.js} +10 -8
  12. flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
  13. flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
  14. flowfile/web/static/assets/{CloudStorageWriter-e83be3ed.js → CloudStorageWriter-8e781e11.js} +10 -8
  15. flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
  16. flowfile/web/static/assets/{ColumnSelector-cce661cf.js → ColumnSelector-8ad68ea9.js} +3 -5
  17. flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
  18. flowfile/web/static/assets/{ContextMenu-11a4652a.js → ContextMenu-31ee57f0.js} +3 -3
  19. flowfile/web/static/assets/{ContextMenu-160afb08.js → ContextMenu-69a74055.js} +3 -3
  20. flowfile/web/static/assets/{ContextMenu-cf18d2cc.js → ContextMenu-8e2051c6.js} +3 -3
  21. flowfile/web/static/assets/{ContextMenu-4c74eef1.css → ContextMenu-8ec1729e.css} +6 -6
  22. flowfile/web/static/assets/{ContextMenu-63cfa99b.css → ContextMenu-9b310c60.css} +6 -6
  23. flowfile/web/static/assets/{CrossJoin-d395d38c.js → CrossJoin-03df6938.js} +12 -10
  24. flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
  25. flowfile/web/static/assets/CustomNode-59e99a86.css +32 -0
  26. flowfile/web/static/assets/{CustomNode-b812dc0b.js → CustomNode-8479239b.js} +36 -24
  27. flowfile/web/static/assets/{DatabaseConnectionSettings-7000bf2c.js → DatabaseConnectionSettings-869e3efd.js} +5 -4
  28. flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-e91df89a.css} +13 -13
  29. flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-36898a00.css} +24 -24
  30. flowfile/web/static/assets/{DatabaseReader-4f035d0c.js → DatabaseReader-c58b9552.js} +25 -15
  31. flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
  32. flowfile/web/static/assets/{DatabaseManager-9662ec5b.js → DatabaseView-d26a9140.js} +11 -11
  33. flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
  34. flowfile/web/static/assets/{DatabaseWriter-f65dcd54.js → DatabaseWriter-4d05ddc7.js} +17 -10
  35. flowfile/web/static/assets/{designer-e3c150ec.css → DesignerView-a6d0ee84.css} +629 -538
  36. flowfile/web/static/assets/{designer-f3656d8c.js → DesignerView-e6f5c0e8.js} +1214 -3209
  37. flowfile/web/static/assets/{documentation-52b241e7.js → DocumentationView-2e78ef1b.js} +5 -5
  38. flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-fd46c656.css} +7 -7
  39. flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
  40. flowfile/web/static/assets/{ExploreData-94c43dfc.js → ExploreData-7b54caca.js} +18 -9
  41. flowfile/web/static/assets/{ExternalSource-ac04b3cc.js → ExternalSource-3fa399b2.js} +9 -7
  42. flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-47ab05a3.css} +17 -17
  43. flowfile/web/static/assets/Filter-7494ea97.css +48 -0
  44. flowfile/web/static/assets/Filter-8cbbdbf3.js +287 -0
  45. flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
  46. flowfile/web/static/assets/{Formula-71472193.js → Formula-aac42b1e.js} +13 -11
  47. flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
  48. flowfile/web/static/assets/{FuzzyMatch-b317f631.js → FuzzyMatch-cd9bbfca.js} +12 -10
  49. flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
  50. flowfile/web/static/assets/{GraphSolver-754a234f.js → GraphSolver-c7e6780e.js} +13 -11
  51. flowfile/web/static/assets/{GroupBy-6c6f9802.js → GroupBy-93c5d22b.js} +9 -7
  52. flowfile/web/static/assets/{GroupBy-b9505323.css → GroupBy-be7ac0bf.css} +10 -10
  53. flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
  54. flowfile/web/static/assets/{Join-a1b800be.js → Join-a19b2de2.js} +13 -11
  55. flowfile/web/static/assets/LoginView-0df4ed0a.js +134 -0
  56. flowfile/web/static/assets/LoginView-d325d632.css +172 -0
  57. flowfile/web/static/assets/ManualInput-3702e677.css +293 -0
  58. flowfile/web/static/assets/{ManualInput-a9640276.js → ManualInput-8d3374b2.js} +170 -116
  59. flowfile/web/static/assets/{MultiSelect-97213888.js → MultiSelect-ad1b6243.js} +2 -2
  60. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-6ffe088a.js → MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js} +1 -1
  61. flowfile/web/static/assets/NodeDesigner-40b647c9.js +2610 -0
  62. flowfile/web/static/assets/NodeDesigner-5f53be3f.css +1429 -0
  63. flowfile/web/static/assets/{NumericInput-e638088a.js → NumericInput-7100234c.js} +2 -2
  64. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-90eb2cba.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
  65. flowfile/web/static/assets/{Output-ddc9079f.css → Output-35e97000.css} +6 -6
  66. flowfile/web/static/assets/{Output-76750610.js → Output-f5efd2aa.js} +60 -38
  67. flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
  68. flowfile/web/static/assets/{Pivot-7814803f.js → Pivot-d981d23c.js} +11 -9
  69. flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
  70. flowfile/web/static/assets/{PivotValidation-f92137d2.js → PivotValidation-39386e95.js} +3 -3
  71. flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
  72. flowfile/web/static/assets/{PivotValidation-76dd431a.js → PivotValidation-63de1f73.js} +3 -3
  73. flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
  74. flowfile/web/static/assets/{PolarsCode-889c3008.js → PolarsCode-f9d69217.js} +18 -9
  75. flowfile/web/static/assets/PopOver-b22f049e.js +939 -0
  76. flowfile/web/static/assets/PopOver-d96599db.css +33 -0
  77. flowfile/web/static/assets/{Read-6b17491f.css → Read-36e7bd51.css} +12 -12
  78. flowfile/web/static/assets/{Read-637b72a7.js → Read-aec2e377.js} +83 -105
  79. flowfile/web/static/assets/{RecordCount-2b050c41.js → RecordCount-78ed6845.js} +6 -4
  80. flowfile/web/static/assets/{RecordId-81df7784.js → RecordId-2156e890.js} +8 -6
  81. flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
  82. flowfile/web/static/assets/{SQLQueryComponent-88dcfe53.js → SQLQueryComponent-48c72f5b.js} +3 -3
  83. flowfile/web/static/assets/{Sample-258ad2a9.js → Sample-1352ca74.js} +6 -4
  84. flowfile/web/static/assets/SecretSelector-22b5ff89.js +113 -0
  85. flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
  86. flowfile/web/static/assets/{SecretManager-2a2cb7e2.js → SecretsView-17df66ee.js} +35 -36
  87. flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
  88. flowfile/web/static/assets/{Select-850215fd.js → Select-0aee4c54.js} +9 -7
  89. flowfile/web/static/assets/{SettingsSection-55bae608.js → SettingsSection-0784e157.js} +3 -3
  90. flowfile/web/static/assets/{SettingsSection-71e6b7e3.css → SettingsSection-07fbbc39.css} +4 -4
  91. flowfile/web/static/assets/{SettingsSection-5c696bee.css → SettingsSection-26fe48d4.css} +4 -4
  92. flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
  93. flowfile/web/static/assets/{SettingsSection-0e8d9123.js → SettingsSection-cd341bb6.js} +3 -3
  94. flowfile/web/static/assets/{SettingsSection-29b4fa6b.js → SettingsSection-f2002a6d.js} +3 -3
  95. flowfile/web/static/assets/{SingleSelect-bebd408b.js → SingleSelect-460cc0ea.js} +2 -2
  96. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-6093741c.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
  97. flowfile/web/static/assets/{SliderInput-6a05ab61.js → SliderInput-5d926864.js} +7 -4
  98. flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
  99. flowfile/web/static/assets/{Sort-10ab48ed.js → Sort-3cdc971b.js} +9 -7
  100. flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
  101. flowfile/web/static/assets/{TextInput-df9d6259.js → TextInput-a2d0bfbd.js} +2 -2
  102. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-000e1178.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
  103. flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
  104. flowfile/web/static/assets/{TextToRows-6c2d93d8.js → TextToRows-918945f7.js} +11 -10
  105. flowfile/web/static/assets/{ToggleSwitch-0ff7ac52.js → ToggleSwitch-f0ef5196.js} +2 -2
  106. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-c6dc3029.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
  107. flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
  108. flowfile/web/static/assets/{UnavailableFields-1bab97cb.js → UnavailableFields-bdad6144.js} +4 -4
  109. flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
  110. flowfile/web/static/assets/{Union-b563478a.js → Union-e8ab8c86.js} +8 -6
  111. flowfile/web/static/assets/{Unique-f90db5db.js → Unique-8cd4f976.js} +13 -22
  112. flowfile/web/static/assets/{Sort-3643d625.css → Unique-9fb2f567.css} +10 -10
  113. flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-710a2948.css} +7 -7
  114. flowfile/web/static/assets/{Unpivot-bcb0025f.js → Unpivot-8da14095.js} +10 -8
  115. flowfile/web/static/assets/{UnpivotValidation-c4e73b04.js → UnpivotValidation-6f7d89ff.js} +3 -3
  116. flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
  117. flowfile/web/static/assets/{VueGraphicWalker-bb8535e2.js → VueGraphicWalker-3fb312e1.js} +4 -4
  118. flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
  119. flowfile/web/static/assets/{api-4c8e3822.js → api-24483f0d.js} +1 -1
  120. flowfile/web/static/assets/{api-2d6adc4f.js → api-8b81fa73.js} +1 -1
  121. flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
  122. flowfile/web/static/assets/{dropDown-1bca8a74.js → dropDown-ac0fda9d.js} +3 -3
  123. flowfile/web/static/assets/{fullEditor-2985687e.js → fullEditor-5497a84a.js} +11 -10
  124. flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-a0be62b3.css} +74 -62
  125. flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
  126. flowfile/web/static/assets/{genericNodeSettings-0476ba4e.js → genericNodeSettings-99014e1d.js} +5 -5
  127. flowfile/web/static/assets/index-07dda503.js +38 -0
  128. flowfile/web/static/assets/index-3ba44389.js +2696 -0
  129. flowfile/web/static/assets/{index-50508d4d.css → index-e6289dd0.css} +1945 -569
  130. flowfile/web/static/assets/{index-246f201c.js → index-fb6493ae.js} +41626 -40869
  131. flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
  132. flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
  133. flowfile/web/static/assets/{outputCsv-d686eeaf.js → outputCsv-8f8ba42d.js} +3 -3
  134. flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
  135. flowfile/web/static/assets/{outputExcel-8809ea2f.js → outputExcel-393f4fef.js} +3 -3
  136. flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
  137. flowfile/web/static/assets/{outputParquet-53ba645a.js → outputParquet-07c81f65.js} +4 -4
  138. flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
  139. flowfile/web/static/assets/{readCsv-053bf97b.js → readCsv-07f6d9ad.js} +21 -20
  140. flowfile/web/static/assets/{readCsv-bca3ed53.css → readCsv-3bfac4c3.css} +15 -15
  141. flowfile/web/static/assets/{readExcel-e1b381ea.css → readExcel-3db6b763.css} +13 -13
  142. flowfile/web/static/assets/{readExcel-ad531eab.js → readExcel-ed69bc8f.js} +10 -12
  143. flowfile/web/static/assets/{readParquet-cee068e2.css → readParquet-c5244ad5.css} +4 -4
  144. flowfile/web/static/assets/{readParquet-58e899a1.js → readParquet-e3ed4528.js} +4 -7
  145. flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
  146. flowfile/web/static/assets/{selectDynamic-b38de2ba.js → selectDynamic-80b92899.js} +5 -5
  147. flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
  148. flowfile/web/static/assets/{vue-codemirror.esm-db9b8936.js → vue-codemirror.esm-0965f39f.js} +31 -637
  149. flowfile/web/static/assets/{vue-content-loader.es-b5f3ac30.js → vue-content-loader.es-c506ad97.js} +1 -1
  150. flowfile/web/static/index.html +2 -2
  151. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +4 -4
  152. flowfile-0.5.3.dist-info/RECORD +402 -0
  153. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +1 -1
  154. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +1 -0
  155. flowfile_core/__init__.py +13 -3
  156. flowfile_core/auth/jwt.py +51 -16
  157. flowfile_core/auth/models.py +32 -7
  158. flowfile_core/auth/password.py +89 -0
  159. flowfile_core/auth/secrets.py +8 -6
  160. flowfile_core/configs/__init__.py +9 -7
  161. flowfile_core/configs/flow_logger.py +15 -14
  162. flowfile_core/configs/node_store/__init__.py +72 -4
  163. flowfile_core/configs/node_store/nodes.py +155 -172
  164. flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
  165. flowfile_core/configs/settings.py +28 -15
  166. flowfile_core/database/connection.py +7 -6
  167. flowfile_core/database/init_db.py +96 -2
  168. flowfile_core/database/models.py +3 -1
  169. flowfile_core/fileExplorer/__init__.py +17 -0
  170. flowfile_core/fileExplorer/funcs.py +123 -57
  171. flowfile_core/fileExplorer/utils.py +10 -11
  172. flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
  173. flowfile_core/flowfile/analytics/analytics_processor.py +27 -24
  174. flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
  175. flowfile_core/flowfile/analytics/utils.py +1 -1
  176. flowfile_core/flowfile/code_generator/code_generator.py +391 -279
  177. flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
  178. flowfile_core/flowfile/connection_manager/models.py +1 -1
  179. flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
  180. flowfile_core/flowfile/database_connection_manager/models.py +1 -1
  181. flowfile_core/flowfile/extensions.py +17 -12
  182. flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
  183. flowfile_core/flowfile/flow_data_engine/create/funcs.py +152 -103
  184. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +526 -477
  185. flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
  186. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
  187. flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
  188. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
  189. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
  190. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +43 -32
  191. flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
  192. flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
  193. flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +15 -11
  194. flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
  195. flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
  196. flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
  197. flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
  198. flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
  199. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
  200. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +360 -191
  201. flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
  202. flowfile_core/flowfile/flow_data_engine/utils.py +101 -67
  203. flowfile_core/flowfile/flow_graph.py +1011 -561
  204. flowfile_core/flowfile/flow_graph_utils.py +31 -49
  205. flowfile_core/flowfile/flow_node/flow_node.py +332 -232
  206. flowfile_core/flowfile/flow_node/models.py +54 -41
  207. flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
  208. flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
  209. flowfile_core/flowfile/handler.py +82 -32
  210. flowfile_core/flowfile/manage/compatibility_enhancements.py +493 -47
  211. flowfile_core/flowfile/manage/io_flowfile.py +391 -0
  212. flowfile_core/flowfile/node_designer/__init__.py +15 -13
  213. flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
  214. flowfile_core/flowfile/node_designer/custom_node.py +162 -36
  215. flowfile_core/flowfile/node_designer/ui_components.py +136 -35
  216. flowfile_core/flowfile/schema_callbacks.py +77 -54
  217. flowfile_core/flowfile/setting_generator/__init__.py +0 -1
  218. flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
  219. flowfile_core/flowfile/setting_generator/settings.py +72 -55
  220. flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
  221. flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
  222. flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
  223. flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
  224. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
  225. flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
  226. flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
  227. flowfile_core/flowfile/util/calculate_layout.py +9 -13
  228. flowfile_core/flowfile/util/execution_orderer.py +25 -17
  229. flowfile_core/flowfile/util/node_skipper.py +4 -4
  230. flowfile_core/flowfile/utils.py +19 -21
  231. flowfile_core/main.py +26 -19
  232. flowfile_core/routes/auth.py +284 -11
  233. flowfile_core/routes/cloud_connections.py +25 -25
  234. flowfile_core/routes/logs.py +21 -29
  235. flowfile_core/routes/public.py +3 -3
  236. flowfile_core/routes/routes.py +77 -43
  237. flowfile_core/routes/secrets.py +25 -27
  238. flowfile_core/routes/user_defined_components.py +483 -4
  239. flowfile_core/run_lock.py +0 -1
  240. flowfile_core/schemas/__init__.py +4 -6
  241. flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
  242. flowfile_core/schemas/cloud_storage_schemas.py +59 -55
  243. flowfile_core/schemas/input_schema.py +398 -154
  244. flowfile_core/schemas/output_model.py +50 -35
  245. flowfile_core/schemas/schemas.py +207 -67
  246. flowfile_core/schemas/transform_schema.py +1360 -435
  247. flowfile_core/schemas/yaml_types.py +117 -0
  248. flowfile_core/secret_manager/secret_manager.py +17 -13
  249. flowfile_core/{flowfile/node_designer/data_types.py → types.py} +33 -3
  250. flowfile_core/utils/arrow_reader.py +7 -6
  251. flowfile_core/utils/excel_file_manager.py +3 -3
  252. flowfile_core/utils/fileManager.py +7 -7
  253. flowfile_core/utils/fl_executor.py +8 -10
  254. flowfile_core/utils/utils.py +4 -4
  255. flowfile_core/utils/validate_setup.py +5 -4
  256. flowfile_frame/__init__.py +107 -50
  257. flowfile_frame/adapters.py +2 -9
  258. flowfile_frame/adding_expr.py +73 -32
  259. flowfile_frame/cloud_storage/frame_helpers.py +27 -23
  260. flowfile_frame/cloud_storage/secret_manager.py +12 -26
  261. flowfile_frame/config.py +2 -5
  262. flowfile_frame/expr.py +311 -218
  263. flowfile_frame/expr.pyi +160 -159
  264. flowfile_frame/expr_name.py +23 -23
  265. flowfile_frame/flow_frame.py +581 -489
  266. flowfile_frame/flow_frame.pyi +123 -104
  267. flowfile_frame/flow_frame_methods.py +236 -252
  268. flowfile_frame/group_frame.py +50 -20
  269. flowfile_frame/join.py +2 -2
  270. flowfile_frame/lazy.py +129 -87
  271. flowfile_frame/lazy_methods.py +83 -30
  272. flowfile_frame/list_name_space.py +55 -50
  273. flowfile_frame/selectors.py +148 -68
  274. flowfile_frame/series.py +9 -7
  275. flowfile_frame/utils.py +19 -21
  276. flowfile_worker/__init__.py +12 -4
  277. flowfile_worker/configs.py +11 -19
  278. flowfile_worker/create/__init__.py +14 -27
  279. flowfile_worker/create/funcs.py +143 -94
  280. flowfile_worker/create/models.py +139 -68
  281. flowfile_worker/create/pl_types.py +14 -15
  282. flowfile_worker/create/read_excel_tables.py +34 -41
  283. flowfile_worker/create/utils.py +22 -19
  284. flowfile_worker/external_sources/s3_source/main.py +18 -51
  285. flowfile_worker/external_sources/s3_source/models.py +34 -27
  286. flowfile_worker/external_sources/sql_source/main.py +8 -5
  287. flowfile_worker/external_sources/sql_source/models.py +13 -9
  288. flowfile_worker/flow_logger.py +10 -8
  289. flowfile_worker/funcs.py +214 -155
  290. flowfile_worker/main.py +11 -17
  291. flowfile_worker/models.py +35 -28
  292. flowfile_worker/process_manager.py +2 -3
  293. flowfile_worker/routes.py +121 -93
  294. flowfile_worker/secrets.py +9 -6
  295. flowfile_worker/spawner.py +80 -49
  296. flowfile_worker/utils.py +3 -2
  297. shared/__init__.py +2 -7
  298. shared/storage_config.py +25 -13
  299. test_utils/postgres/commands.py +3 -2
  300. test_utils/postgres/fixtures.py +9 -9
  301. test_utils/s3/commands.py +1 -1
  302. test_utils/s3/data_generator.py +3 -4
  303. test_utils/s3/demo_data_generator.py +4 -7
  304. test_utils/s3/fixtures.py +7 -5
  305. tools/migrate/README.md +56 -0
  306. tools/migrate/__init__.py +12 -0
  307. tools/migrate/__main__.py +118 -0
  308. tools/migrate/legacy_schemas.py +682 -0
  309. tools/migrate/migrate.py +610 -0
  310. tools/migrate/tests/__init__.py +0 -0
  311. tools/migrate/tests/conftest.py +21 -0
  312. tools/migrate/tests/test_migrate.py +622 -0
  313. tools/migrate/tests/test_migration_e2e.py +1009 -0
  314. tools/migrate/tests/test_node_migrations.py +843 -0
  315. flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
  316. flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
  317. flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
  318. flowfile/web/static/assets/Filter-812dcbca.js +0 -164
  319. flowfile/web/static/assets/Filter-f62091b3.css +0 -20
  320. flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
  321. flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
  322. flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
  323. flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
  324. flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
  325. flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
  326. flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
  327. flowfile/web/static/assets/secretApi-538058f3.js +0 -46
  328. flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
  329. flowfile-0.4.1.dist-info/RECORD +0 -376
  330. flowfile_core/flowfile/manage/open_flowfile.py +0 -143
  331. {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
  332. /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
@@ -1,8 +1,8 @@
1
- from typing import List
2
1
  from flowfile_core.flowfile.flow_node.flow_node import FlowNode
3
2
 
4
- def determine_nodes_to_skip(nodes : List[FlowNode]) -> List[FlowNode]:
5
- """ Finds nodes to skip on the execution step. """
3
+
4
+ def determine_nodes_to_skip(nodes: list[FlowNode]) -> list[FlowNode]:
5
+ """Finds nodes to skip on the execution step."""
6
6
  skip_nodes = [node for node in nodes if not node.is_correct]
7
7
  skip_nodes.extend([lead_to_node for node in skip_nodes for lead_to_node in node.leads_to_nodes])
8
- return skip_nodes
8
+ return skip_nodes
@@ -1,15 +1,13 @@
1
- import os
1
+ import datetime
2
+ import hashlib
2
3
  import json
4
+ import os
5
+ import random
3
6
  import shutil
4
-
5
- import datetime
6
- from typing import List
7
- from decimal import Decimal
7
+ import socket
8
8
  import time
9
- import random
10
9
  import uuid
11
- import socket
12
- import hashlib
10
+ from decimal import Decimal
13
11
 
14
12
 
15
13
  def generate_sha256_hash(data: bytes):
@@ -25,25 +23,25 @@ def create_directory_if_not_exists(directory: str):
25
23
 
26
24
  def snake_case_to_camel_case(text: str) -> str:
27
25
  # Split the text by underscores, capitalize each piece, and join them together
28
- transformed_text = ''.join(word.capitalize() for word in text.split('_'))
26
+ transformed_text = "".join(word.capitalize() for word in text.split("_"))
29
27
  return transformed_text
30
28
 
31
29
 
32
30
  def json_default(val):
33
31
  if isinstance(val, datetime.datetime):
34
- return val.isoformat(timespec='microseconds')
32
+ return val.isoformat(timespec="microseconds")
35
33
  elif isinstance(val, datetime.date):
36
34
  return val.isoformat()
37
35
  elif isinstance(val, datetime.time):
38
36
  return val.isoformat()
39
- elif hasattr(val, '__dict__'):
37
+ elif hasattr(val, "__dict__"):
40
38
  return val.__dict__
41
39
  elif isinstance(val, Decimal):
42
40
  if val.as_integer_ratio()[1] == 1:
43
41
  return int(val)
44
42
  return float(val)
45
43
  else:
46
- raise Exception('Value is not serializable')
44
+ raise Exception("Value is not serializable")
47
45
 
48
46
 
49
47
  def json_dumps(thing) -> str:
@@ -53,22 +51,22 @@ def json_dumps(thing) -> str:
53
51
  ensure_ascii=False,
54
52
  sort_keys=True,
55
53
  indent=None,
56
- separators=(',', ':'),
54
+ separators=(",", ":"),
57
55
  )
58
56
 
59
57
 
60
58
  def get_hash(val):
61
- if hasattr(val, 'overridden_hash') and val.overridden_hash():
59
+ if hasattr(val, "overridden_hash") and val.overridden_hash():
62
60
  val = hash(val)
63
- elif hasattr(val, '__dict__'):
64
- val = {k: v for k, v in val.__dict__.items() if k not in {'pos_x', 'pos_y', 'description'}}
65
- elif hasattr(val, 'json'):
61
+ elif hasattr(val, "__dict__"):
62
+ val = {k: v for k, v in val.__dict__.items() if k not in {"pos_x", "pos_y", "description"}}
63
+ elif hasattr(val, "json"):
66
64
  pass
67
- return generate_sha256_hash(json_dumps(val).encode('utf-8'))
65
+ return generate_sha256_hash(json_dumps(val).encode("utf-8"))
68
66
 
69
67
 
70
- def cleanup(start_location: str = 'temp_storage'):
71
- def get_all_files_and_folders(_start_location) -> List[str]:
68
+ def cleanup(start_location: str = "temp_storage"):
69
+ def get_all_files_and_folders(_start_location) -> list[str]:
72
70
  inspect_items = [_start_location]
73
71
  output = []
74
72
  while len(inspect_items) > 0:
@@ -107,7 +105,7 @@ def cleanup(start_location: str = 'temp_storage'):
107
105
  shutil.rmtree(_f)
108
106
 
109
107
 
110
- def batch_generator(input_list: List, batch_size: int = 10000):
108
+ def batch_generator(input_list: list, batch_size: int = 10000):
111
109
  run: bool = True
112
110
  while run:
113
111
  if len(input_list) > batch_size:
flowfile_core/main.py CHANGED
@@ -7,27 +7,34 @@ import uvicorn
7
7
  from fastapi import FastAPI
8
8
  from fastapi.middleware.cors import CORSMiddleware
9
9
 
10
- from shared.storage_config import storage
11
-
12
10
  from flowfile_core import ServerRun
13
- from flowfile_core.configs.settings import (SERVER_HOST, SERVER_PORT, WORKER_HOST, WORKER_PORT, WORKER_URL,)
14
-
11
+ from flowfile_core.configs.flow_logger import clear_all_flow_logs
12
+ from flowfile_core.configs.settings import (
13
+ SERVER_HOST,
14
+ SERVER_PORT,
15
+ WORKER_HOST,
16
+ WORKER_PORT,
17
+ WORKER_URL,
18
+ )
15
19
  from flowfile_core.routes.auth import router as auth_router
16
- from flowfile_core.routes.secrets import router as secrets_router
17
- from flowfile_core.routes.routes import router
18
- from flowfile_core.routes.public import router as public_router
19
- from flowfile_core.routes.logs import router as logs_router
20
20
  from flowfile_core.routes.cloud_connections import router as cloud_connections_router
21
+ from flowfile_core.routes.logs import router as logs_router
22
+ from flowfile_core.routes.public import router as public_router
23
+ from flowfile_core.routes.routes import router
24
+ from flowfile_core.routes.secrets import router as secrets_router
21
25
  from flowfile_core.routes.user_defined_components import router as user_defined_components_router
26
+ from shared.storage_config import storage
22
27
 
23
- from flowfile_core.configs.flow_logger import clear_all_flow_logs
24
28
  storage.cleanup_directories()
25
29
 
26
- os.environ["FLOWFILE_MODE"] = "electron"
30
+ # Set default mode to electron if not already set (allows Docker mode override)
31
+ if "FLOWFILE_MODE" not in os.environ:
32
+ os.environ["FLOWFILE_MODE"] = "electron"
27
33
 
28
34
  should_exit = False
29
35
  server_instance = None
30
36
 
37
+
31
38
  @asynccontextmanager
32
39
  async def shutdown_handler(app: FastAPI):
33
40
  """Handles the graceful startup and shutdown of the FastAPI application.
@@ -35,11 +42,11 @@ async def shutdown_handler(app: FastAPI):
35
42
  This context manager ensures that resources, such as log files, are cleaned
36
43
  up properly when the application is terminated.
37
44
  """
38
- print('Starting core application...')
45
+ print("Starting core application...")
39
46
  try:
40
47
  yield
41
48
  finally:
42
- print('Shutting down core application...')
49
+ print("Shutting down core application...")
43
50
  print("Cleaning up core service resources...")
44
51
  clear_all_flow_logs()
45
52
  await asyncio.sleep(0.1) # Give a moment for cleanup
@@ -47,10 +54,10 @@ async def shutdown_handler(app: FastAPI):
47
54
 
48
55
  # Initialize FastAPI with metadata
49
56
  app = FastAPI(
50
- title='Flowfile Backend',
51
- version='0.1',
52
- description='Backend for the Flowfile application',
53
- lifespan=shutdown_handler
57
+ title="Flowfile Backend",
58
+ version="0.1",
59
+ description="Backend for the Flowfile application",
60
+ lifespan=shutdown_handler,
54
61
  )
55
62
 
56
63
  # Configure CORS
@@ -63,7 +70,7 @@ origins = [
63
70
  "http://localhost:4173",
64
71
  "http://localhost:4174",
65
72
  "http://localhost:63578",
66
- "http://127.0.0.1:63578"
73
+ "http://127.0.0.1:63578",
67
74
  ]
68
75
 
69
76
  app.add_middleware(
@@ -148,8 +155,8 @@ def run(host: str = None, port: int = None):
148
155
  server = uvicorn.Server(config)
149
156
  server_instance = server # Store server instance globally
150
157
 
151
- print('Starting core server...')
152
- print('Core server started')
158
+ print("Starting core server...")
159
+ print("Core server started")
153
160
 
154
161
  try:
155
162
  server.run()
@@ -1,34 +1,307 @@
1
1
  # app_routes/auth.py
2
2
 
3
3
  import os
4
+ from typing import Optional, List
4
5
 
5
- from fastapi import APIRouter, Depends, HTTPException, status, Request
6
+ from fastapi import APIRouter, Depends, HTTPException, status, Request, Form
6
7
  from sqlalchemy.orm import Session
7
8
 
8
- from flowfile_core.auth.jwt import get_current_active_user, create_access_token
9
- from flowfile_core.auth.models import Token, User
9
+ from flowfile_core.auth.jwt import get_current_active_user, get_current_admin_user, create_access_token
10
+ from flowfile_core.auth.models import Token, User, UserCreate, UserUpdate, ChangePassword
11
+ from flowfile_core.auth.password import verify_password, get_password_hash, validate_password, PASSWORD_REQUIREMENTS
10
12
  from flowfile_core.database.connection import get_db
13
+ from flowfile_core.database import models as db_models
11
14
 
12
15
  router = APIRouter()
13
16
 
14
17
 
15
18
  @router.post("/token", response_model=Token)
16
- async def login_for_access_token(request: Request, db: Session = Depends(get_db)):
19
+ async def login_for_access_token(
20
+ request: Request,
21
+ db: Session = Depends(get_db),
22
+ username: Optional[str] = Form(None),
23
+ password: Optional[str] = Form(None)
24
+ ):
17
25
  # In Electron mode, auto-authenticate without requiring form data
18
- if os.environ.get("FLOWFILE_MODE") == "electron" or 1 == 1:
26
+ if os.environ.get("FLOWFILE_MODE") == "electron":
19
27
  access_token = create_access_token(data={"sub": "local_user"})
20
28
  return {"access_token": access_token, "token_type": "bearer"}
21
29
  else:
22
30
  # In Docker mode, authenticate against database
23
- # Would typically process form data here
24
- raise HTTPException(
25
- status_code=status.HTTP_401_UNAUTHORIZED,
26
- detail="Docker mode authentication not implemented yet",
27
- headers={"WWW-Authenticate": "Bearer"},
28
- )
31
+ if not username or not password:
32
+ raise HTTPException(
33
+ status_code=status.HTTP_401_UNAUTHORIZED,
34
+ detail="Incorrect username or password",
35
+ headers={"WWW-Authenticate": "Bearer"},
36
+ )
37
+
38
+ user = db.query(db_models.User).filter(
39
+ db_models.User.username == username
40
+ ).first()
41
+
42
+ if not user or not verify_password(password, user.hashed_password):
43
+ raise HTTPException(
44
+ status_code=status.HTTP_401_UNAUTHORIZED,
45
+ detail="Incorrect username or password",
46
+ headers={"WWW-Authenticate": "Bearer"},
47
+ )
48
+
49
+ access_token = create_access_token(data={"sub": user.username})
50
+ return {"access_token": access_token, "token_type": "bearer"}
29
51
 
30
52
 
31
53
  # Get current user endpoint
32
54
  @router.get("/users/me", response_model=User)
33
55
  async def read_users_me(current_user=Depends(get_current_active_user)):
34
56
  return current_user
57
+
58
+
59
+ # ============= Admin User Management Endpoints =============
60
+
61
+ @router.get("/users", response_model=List[User])
62
+ async def list_users(
63
+ current_user: User = Depends(get_current_admin_user),
64
+ db: Session = Depends(get_db)
65
+ ):
66
+ """List all users (admin only)"""
67
+ users = db.query(db_models.User).all()
68
+ return [
69
+ User(
70
+ username=u.username,
71
+ id=u.id,
72
+ email=u.email,
73
+ full_name=u.full_name,
74
+ disabled=u.disabled,
75
+ is_admin=u.is_admin,
76
+ must_change_password=u.must_change_password
77
+ )
78
+ for u in users
79
+ ]
80
+
81
+
82
+ @router.post("/users", response_model=User)
83
+ async def create_user(
84
+ user_data: UserCreate,
85
+ current_user: User = Depends(get_current_admin_user),
86
+ db: Session = Depends(get_db)
87
+ ):
88
+ """Create a new user (admin only)"""
89
+ # Check if username already exists
90
+ existing_user = db.query(db_models.User).filter(
91
+ db_models.User.username == user_data.username
92
+ ).first()
93
+ if existing_user:
94
+ raise HTTPException(
95
+ status_code=status.HTTP_400_BAD_REQUEST,
96
+ detail="Username already exists"
97
+ )
98
+
99
+ # Check if email already exists (if provided)
100
+ if user_data.email:
101
+ existing_email = db.query(db_models.User).filter(
102
+ db_models.User.email == user_data.email
103
+ ).first()
104
+ if existing_email:
105
+ raise HTTPException(
106
+ status_code=status.HTTP_400_BAD_REQUEST,
107
+ detail="Email already exists"
108
+ )
109
+
110
+ # Validate password requirements
111
+ is_valid, error_message = validate_password(user_data.password)
112
+ if not is_valid:
113
+ raise HTTPException(
114
+ status_code=status.HTTP_400_BAD_REQUEST,
115
+ detail=error_message
116
+ )
117
+
118
+ # Create new user with must_change_password=True
119
+ hashed_password = get_password_hash(user_data.password)
120
+ new_user = db_models.User(
121
+ username=user_data.username,
122
+ email=user_data.email or f"{user_data.username}@flowfile.app",
123
+ full_name=user_data.full_name,
124
+ hashed_password=hashed_password,
125
+ is_admin=user_data.is_admin,
126
+ must_change_password=True
127
+ )
128
+ db.add(new_user)
129
+ db.commit()
130
+ db.refresh(new_user)
131
+
132
+ return User(
133
+ username=new_user.username,
134
+ id=new_user.id,
135
+ email=new_user.email,
136
+ full_name=new_user.full_name,
137
+ disabled=new_user.disabled,
138
+ is_admin=new_user.is_admin,
139
+ must_change_password=new_user.must_change_password
140
+ )
141
+
142
+
143
+ @router.put("/users/{user_id}", response_model=User)
144
+ async def update_user(
145
+ user_id: int,
146
+ user_data: UserUpdate,
147
+ current_user: User = Depends(get_current_admin_user),
148
+ db: Session = Depends(get_db)
149
+ ):
150
+ """Update a user (admin only)"""
151
+ user = db.query(db_models.User).filter(db_models.User.id == user_id).first()
152
+ if not user:
153
+ raise HTTPException(
154
+ status_code=status.HTTP_404_NOT_FOUND,
155
+ detail="User not found"
156
+ )
157
+
158
+ # Prevent admin from disabling themselves
159
+ if user.id == current_user.id and user_data.disabled:
160
+ raise HTTPException(
161
+ status_code=status.HTTP_400_BAD_REQUEST,
162
+ detail="Cannot disable your own account"
163
+ )
164
+
165
+ # Prevent admin from removing their own admin status
166
+ if user.id == current_user.id and user_data.is_admin is False:
167
+ raise HTTPException(
168
+ status_code=status.HTTP_400_BAD_REQUEST,
169
+ detail="Cannot remove your own admin privileges"
170
+ )
171
+
172
+ # Update fields
173
+ if user_data.email is not None:
174
+ # Check if email already exists for another user
175
+ existing_email = db.query(db_models.User).filter(
176
+ db_models.User.email == user_data.email,
177
+ db_models.User.id != user_id
178
+ ).first()
179
+ if existing_email:
180
+ raise HTTPException(
181
+ status_code=status.HTTP_400_BAD_REQUEST,
182
+ detail="Email already exists"
183
+ )
184
+ user.email = user_data.email
185
+
186
+ if user_data.full_name is not None:
187
+ user.full_name = user_data.full_name
188
+
189
+ if user_data.disabled is not None:
190
+ user.disabled = user_data.disabled
191
+
192
+ if user_data.is_admin is not None:
193
+ user.is_admin = user_data.is_admin
194
+
195
+ if user_data.password is not None:
196
+ # Validate password requirements
197
+ is_valid, error_message = validate_password(user_data.password)
198
+ if not is_valid:
199
+ raise HTTPException(
200
+ status_code=status.HTTP_400_BAD_REQUEST,
201
+ detail=error_message
202
+ )
203
+ user.hashed_password = get_password_hash(user_data.password)
204
+ # Reset must_change_password when admin sets a new password
205
+ user.must_change_password = True
206
+
207
+ if user_data.must_change_password is not None:
208
+ user.must_change_password = user_data.must_change_password
209
+
210
+ db.commit()
211
+ db.refresh(user)
212
+
213
+ return User(
214
+ username=user.username,
215
+ id=user.id,
216
+ email=user.email,
217
+ full_name=user.full_name,
218
+ disabled=user.disabled,
219
+ is_admin=user.is_admin,
220
+ must_change_password=user.must_change_password
221
+ )
222
+
223
+
224
+ @router.delete("/users/{user_id}")
225
+ async def delete_user(
226
+ user_id: int,
227
+ current_user: User = Depends(get_current_admin_user),
228
+ db: Session = Depends(get_db)
229
+ ):
230
+ """Delete a user (admin only)"""
231
+ user = db.query(db_models.User).filter(db_models.User.id == user_id).first()
232
+ if not user:
233
+ raise HTTPException(
234
+ status_code=status.HTTP_404_NOT_FOUND,
235
+ detail="User not found"
236
+ )
237
+
238
+ # Prevent admin from deleting themselves
239
+ if user.id == current_user.id:
240
+ raise HTTPException(
241
+ status_code=status.HTTP_400_BAD_REQUEST,
242
+ detail="Cannot delete your own account"
243
+ )
244
+
245
+ # Delete user's secrets and connections first (cascade)
246
+ db.query(db_models.Secret).filter(db_models.Secret.user_id == user_id).delete()
247
+ db.query(db_models.DatabaseConnection).filter(db_models.DatabaseConnection.user_id == user_id).delete()
248
+ db.query(db_models.CloudStorageConnection).filter(db_models.CloudStorageConnection.user_id == user_id).delete()
249
+
250
+ db.delete(user)
251
+ db.commit()
252
+
253
+ return {"message": f"User '{user.username}' deleted successfully"}
254
+
255
+
256
+ # ============= User Self-Service Endpoints =============
257
+
258
+ @router.post("/users/me/change-password", response_model=User)
259
+ async def change_own_password(
260
+ password_data: ChangePassword,
261
+ current_user: User = Depends(get_current_active_user),
262
+ db: Session = Depends(get_db)
263
+ ):
264
+ """Change the current user's password"""
265
+ user = db.query(db_models.User).filter(db_models.User.id == current_user.id).first()
266
+ if not user:
267
+ raise HTTPException(
268
+ status_code=status.HTTP_404_NOT_FOUND,
269
+ detail="User not found"
270
+ )
271
+
272
+ # Verify current password
273
+ if not verify_password(password_data.current_password, user.hashed_password):
274
+ raise HTTPException(
275
+ status_code=status.HTTP_400_BAD_REQUEST,
276
+ detail="Current password is incorrect"
277
+ )
278
+
279
+ # Validate new password requirements
280
+ is_valid, error_message = validate_password(password_data.new_password)
281
+ if not is_valid:
282
+ raise HTTPException(
283
+ status_code=status.HTTP_400_BAD_REQUEST,
284
+ detail=error_message
285
+ )
286
+
287
+ # Update password and clear must_change_password flag
288
+ user.hashed_password = get_password_hash(password_data.new_password)
289
+ user.must_change_password = False
290
+ db.commit()
291
+ db.refresh(user)
292
+
293
+ return User(
294
+ username=user.username,
295
+ id=user.id,
296
+ email=user.email,
297
+ full_name=user.full_name,
298
+ disabled=user.disabled,
299
+ is_admin=user.is_admin,
300
+ must_change_password=user.must_change_password
301
+ )
302
+
303
+
304
+ @router.get("/password-requirements")
305
+ async def get_password_requirements():
306
+ """Get password requirements for client-side validation"""
307
+ return PASSWORD_REQUIREMENTS
@@ -1,16 +1,17 @@
1
- from typing import List
2
-
3
- from fastapi import HTTPException, Depends, APIRouter
1
+ from fastapi import APIRouter, Depends, HTTPException
4
2
  from sqlalchemy.orm import Session
5
3
 
6
4
  # Core modules
7
5
  from flowfile_core.auth.jwt import get_current_active_user
8
6
  from flowfile_core.configs import logger
9
7
  from flowfile_core.database.connection import get_db
10
- from flowfile_core.flowfile.database_connection_manager.db_connections import (store_cloud_connection,
11
- get_cloud_connection_schema,
12
- get_all_cloud_connections_interface,
13
- delete_cloud_connection)
8
+ from flowfile_core.flowfile.database_connection_manager.db_connections import (
9
+ delete_cloud_connection,
10
+ get_all_cloud_connections_interface,
11
+ get_cloud_connection_schema,
12
+ store_cloud_connection,
13
+ )
14
+
14
15
  # Schema and models
15
16
  from flowfile_core.schemas.cloud_storage_schemas import FullCloudStorageConnection, FullCloudStorageConnectionInterface
16
17
 
@@ -19,11 +20,12 @@ from flowfile_core.schemas.cloud_storage_schemas import FullCloudStorageConnecti
19
20
  router = APIRouter()
20
21
 
21
22
 
22
- @router.post("/cloud_connection", tags=['cloud_connections'])
23
- def create_cloud_storage_connection(input_connection: FullCloudStorageConnection,
24
- current_user=Depends(get_current_active_user),
25
- db: Session = Depends(get_db)
26
- ):
23
+ @router.post("/cloud_connection", tags=["cloud_connections"])
24
+ def create_cloud_storage_connection(
25
+ input_connection: FullCloudStorageConnection,
26
+ current_user=Depends(get_current_active_user),
27
+ db: Session = Depends(get_db),
28
+ ):
27
29
  """
28
30
  Create a new cloud storage connection.
29
31
  Parameters
@@ -33,38 +35,36 @@ def create_cloud_storage_connection(input_connection: FullCloudStorageConnection
33
35
  Returns
34
36
  Dict with a success message
35
37
  """
36
- logger.info(f'Create cloud connection {input_connection.connection_name}')
38
+ logger.info(f"Create cloud connection {input_connection.connection_name}")
37
39
  try:
38
40
  store_cloud_connection(db, input_connection, current_user.id)
39
41
  except ValueError:
40
- raise HTTPException(422, 'Connection name already exists')
42
+ raise HTTPException(422, "Connection name already exists")
41
43
  except Exception as e:
42
44
  logger.error(e)
43
45
  raise HTTPException(422, str(e))
44
46
  return {"message": "Cloud connection created successfully"}
45
47
 
46
48
 
47
- @router.delete('/cloud_connection', tags=['cloud_connections'])
48
- def delete_cloud_connection_with_connection_name(connection_name: str,
49
- current_user=Depends(get_current_active_user),
50
- db: Session = Depends(get_db)
51
- ):
49
+ @router.delete("/cloud_connection", tags=["cloud_connections"])
50
+ def delete_cloud_connection_with_connection_name(
51
+ connection_name: str, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)
52
+ ):
52
53
  """
53
54
  Delete a cloud connection.
54
55
  """
55
- logger.info(f'Deleting cloud connection {connection_name}')
56
+ logger.info(f"Deleting cloud connection {connection_name}")
56
57
  cloud_storage_connection = get_cloud_connection_schema(db, connection_name, current_user.id)
57
58
  if cloud_storage_connection is None:
58
- raise HTTPException(404, 'Cloud connection connection not found')
59
+ raise HTTPException(404, "Cloud connection connection not found")
59
60
  delete_cloud_connection(db, connection_name, current_user.id)
60
61
  return {"message": "Cloud connection deleted successfully"}
61
62
 
62
63
 
63
- @router.get('/cloud_connections', tags=['cloud_connection'],
64
- response_model=List[FullCloudStorageConnectionInterface])
64
+ @router.get("/cloud_connections", tags=["cloud_connection"], response_model=list[FullCloudStorageConnectionInterface])
65
65
  def get_cloud_connections(
66
- db: Session = Depends(get_db),
67
- current_user=Depends(get_current_active_user)) -> List[FullCloudStorageConnectionInterface]:
66
+ db: Session = Depends(get_db), current_user=Depends(get_current_active_user)
67
+ ) -> list[FullCloudStorageConnectionInterface]:
68
68
  """
69
69
  Get all cloud storage connections for the current user.
70
70
  Parameters