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,120 +1,125 @@
1
- from typing import List, Optional, Literal, Iterator, Any
2
- from flowfile_core.schemas import transform_schema
3
- from pathlib import Path
4
1
  import os
2
+ from pathlib import Path
3
+ from typing import Annotated, Any, Literal
4
+
5
+ import polars as pl
6
+ from pydantic import (
7
+ BaseModel,
8
+ ConfigDict,
9
+ Field,
10
+ SecretStr,
11
+ StringConstraints,
12
+ ValidationInfo,
13
+ field_validator,
14
+ model_validator,
15
+ )
16
+
17
+ from flowfile_core.schemas import transform_schema
5
18
  from flowfile_core.schemas.analysis_schemas import graphic_walker_schemas as gs_schemas
6
19
  from flowfile_core.schemas.cloud_storage_schemas import CloudStorageReadSettings, CloudStorageWriteSettings
7
- from flowfile_core.schemas.schemas import SecretRef
20
+ from flowfile_core.schemas.yaml_types import (
21
+ NodeCrossJoinYaml,
22
+ NodeFuzzyMatchYaml,
23
+ NodeJoinYaml,
24
+ NodeOutputYaml,
25
+ NodeSelectYaml,
26
+ OutputSettingsYaml,
27
+ )
8
28
  from flowfile_core.utils.utils import ensure_similarity_dicts, standardize_col_dtype
9
- from pydantic import BaseModel, Field, model_validator, SecretStr, ConfigDict
10
- import polars as pl
11
29
 
30
+ SecretRef = Annotated[
31
+ str, StringConstraints(min_length=1, max_length=100), Field(description="An ID referencing an encrypted secret.")
32
+ ]
12
33
 
13
- OutputConnectionClass = Literal['output-0', 'output-1', 'output-2', 'output-3', 'output-4',
14
- 'output-5', 'output-6', 'output-7', 'output-8', 'output-9']
15
34
 
16
- InputConnectionClass = Literal['input-0', 'input-1', 'input-2', 'input-3', 'input-4',
17
- 'input-5', 'input-6', 'input-7', 'input-8', 'input-9']
35
+ OutputConnectionClass = Literal[
36
+ "output-0",
37
+ "output-1",
38
+ "output-2",
39
+ "output-3",
40
+ "output-4",
41
+ "output-5",
42
+ "output-6",
43
+ "output-7",
44
+ "output-8",
45
+ "output-9",
46
+ ]
47
+
48
+ InputConnectionClass = Literal[
49
+ "input-0", "input-1", "input-2", "input-3", "input-4", "input-5", "input-6", "input-7", "input-8", "input-9"
50
+ ]
18
51
 
19
52
  InputType = Literal["main", "left", "right"]
20
53
 
21
54
 
22
55
  class NewDirectory(BaseModel):
23
56
  """Defines the information required to create a new directory."""
57
+
24
58
  source_path: str
25
59
  dir_name: str
26
60
 
27
61
 
28
62
  class RemoveItem(BaseModel):
29
63
  """Represents a single item to be removed from a directory or list."""
64
+
30
65
  path: str
31
66
  id: int = -1
32
67
 
33
68
 
34
69
  class RemoveItemsInput(BaseModel):
35
70
  """Defines a list of items to be removed."""
36
- paths: List[RemoveItem]
71
+
72
+ paths: list[RemoveItem]
37
73
  source_path: str
38
74
 
39
75
 
40
76
  class MinimalFieldInfo(BaseModel):
41
77
  """Represents the most basic information about a data field (column)."""
78
+
42
79
  name: str
43
80
  data_type: str = "String"
44
81
 
45
82
 
46
- class ReceivedTableBase(BaseModel):
47
- """Base model for defining a table received from an external source."""
48
- id: Optional[int] = None
49
- name: Optional[str]
50
- path: str # This can be an absolute or relative path
51
- directory: Optional[str] = None
52
- analysis_file_available: bool = False
53
- status: Optional[str] = None
54
- file_type: Optional[str] = None
55
- fields: List[MinimalFieldInfo] = Field(default_factory=list)
56
- abs_file_path: Optional[str] = None
57
-
58
- @classmethod
59
- def create_from_path(cls, path: str):
60
- """Creates an instance from a file path string."""
61
- filename = Path(path).name
62
- return cls(name=filename, path=path)
83
+ class InputTableBase(BaseModel):
84
+ """Base settings for input file operations."""
63
85
 
64
- @property
65
- def file_path(self) -> str:
66
- """Constructs the full file path from the directory and name."""
67
- if not self.name in self.path:
68
- return os.path.join(self.path, self.name)
69
- else:
70
- return self.path
71
-
72
- def set_absolute_filepath(self):
73
- """Resolves the path to an absolute file path."""
74
- base_path = Path(self.path).expanduser()
75
- if not base_path.is_absolute():
76
- base_path = Path.cwd() / base_path
77
- if self.name and self.name not in base_path.name:
78
- base_path = base_path / self.name
79
- self.abs_file_path = str(base_path.resolve())
80
-
81
- @model_validator(mode='after')
82
- def populate_abs_file_path(self):
83
- """Ensures the absolute file path is populated after validation."""
84
- if not self.abs_file_path:
85
- self.set_absolute_filepath()
86
- return self
86
+ file_type: str # Will be overridden with Literal in subclasses
87
87
 
88
88
 
89
- class ReceivedCsvTable(ReceivedTableBase):
89
+ class InputCsvTable(InputTableBase):
90
90
  """Defines settings for reading a CSV file."""
91
- file_type: str = 'csv'
92
- reference: str = ''
91
+
92
+ file_type: Literal["csv"] = "csv"
93
+ reference: str = ""
93
94
  starting_from_line: int = 0
94
- delimiter: str = ','
95
+ delimiter: str = ","
95
96
  has_headers: bool = True
96
- encoding: Optional[str] = 'utf-8'
97
- parquet_ref: Optional[str] = None
98
- row_delimiter: str = '\n'
97
+ encoding: str = "utf-8"
98
+ parquet_ref: str | None = None
99
+ row_delimiter: str = "\n"
99
100
  quote_char: str = '"'
100
101
  infer_schema_length: int = 10_000
101
102
  truncate_ragged_lines: bool = False
102
103
  ignore_errors: bool = False
103
104
 
104
105
 
105
- class ReceivedJsonTable(ReceivedCsvTable):
106
- """Defines settings for reading a JSON file (inherits from CSV settings)."""
107
- pass
106
+ class InputJsonTable(InputCsvTable):
107
+ """Defines settings for reading a JSON file."""
108
108
 
109
+ file_type: Literal["json"] = "json"
109
110
 
110
- class ReceivedParquetTable(ReceivedTableBase):
111
+
112
+ class InputParquetTable(InputTableBase):
111
113
  """Defines settings for reading a Parquet file."""
112
- file_type: str = 'parquet'
114
+
115
+ file_type: Literal["parquet"] = "parquet"
113
116
 
114
117
 
115
- class ReceivedExcelTable(ReceivedTableBase):
118
+ class InputExcelTable(InputTableBase):
116
119
  """Defines settings for reading an Excel file."""
117
- sheet_name: Optional[str] = None
120
+
121
+ file_type: Literal["excel"] = "excel"
122
+ sheet_name: str | None = None
118
123
  start_row: int = 0
119
124
  start_column: int = 0
120
125
  end_row: int = 0
@@ -122,50 +127,185 @@ class ReceivedExcelTable(ReceivedTableBase):
122
127
  has_headers: bool = True
123
128
  type_inference: bool = False
124
129
 
130
+ @model_validator(mode="after")
125
131
  def validate_range_values(self):
126
132
  """Validates that the Excel cell range is logical."""
127
133
  for attribute in [self.start_row, self.start_column, self.end_row, self.end_column]:
128
134
  if not isinstance(attribute, int) or attribute < 0:
129
135
  raise ValueError("Row and column indices must be non-negative integers")
130
- if (self.end_row > 0 and self.start_row > self.end_row) or \
131
- (self.end_column > 0 and self.start_column > self.end_column):
136
+ if (self.end_row > 0 and self.start_row > self.end_row) or (
137
+ self.end_column > 0 and self.start_column > self.end_column
138
+ ):
132
139
  raise ValueError("Start row/column must not be greater than end row/column")
140
+ return self
141
+
133
142
 
143
+ # Create the discriminated union (similar to OutputTableSettings)
144
+ InputTableSettings = Annotated[
145
+ InputCsvTable | InputJsonTable | InputParquetTable | InputExcelTable, Field(discriminator="file_type")
146
+ ]
134
147
 
135
- class ReceivedTable(ReceivedExcelTable, ReceivedCsvTable, ReceivedParquetTable):
136
- """A comprehensive model that can represent any type of received table."""
137
- ...
148
+
149
+ # Now create the main ReceivedTable model
150
+ class ReceivedTable(BaseModel):
151
+ """Model for defining a table received from an external source."""
152
+
153
+ # Metadata fields
154
+ id: int | None = None
155
+ name: str | None = None
156
+ path: str # This can be an absolute or relative path
157
+ directory: str | None = None
158
+ analysis_file_available: bool = False
159
+ status: str | None = None
160
+ fields: list[MinimalFieldInfo] = Field(default_factory=list)
161
+ abs_file_path: str | None = None
162
+
163
+ file_type: Literal["csv", "json", "parquet", "excel"]
164
+
165
+ table_settings: InputTableSettings
166
+
167
+ @classmethod
168
+ def create_from_path(cls, path: str, file_type: Literal["csv", "json", "parquet", "excel"] = "csv"):
169
+ """Creates an instance from a file path string."""
170
+ filename = Path(path).name
171
+
172
+ # Create appropriate table_settings based on file_type
173
+ settings_map = {
174
+ "csv": InputCsvTable(),
175
+ "json": InputJsonTable(),
176
+ "parquet": InputParquetTable(),
177
+ "excel": InputExcelTable(),
178
+ }
179
+
180
+ return cls(
181
+ name=filename, path=path, file_type=file_type, table_settings=settings_map.get(file_type, InputCsvTable())
182
+ )
183
+
184
+ @property
185
+ def file_path(self) -> str:
186
+ """Constructs the full file path from the directory and name."""
187
+ if self.name and self.name not in self.path:
188
+ return os.path.join(self.path, self.name)
189
+ else:
190
+ return self.path
191
+
192
+ def set_absolute_filepath(self):
193
+ """Resolves the path to an absolute file path."""
194
+ base_path = Path(self.path).expanduser()
195
+ if not base_path.is_absolute():
196
+ base_path = Path.cwd() / base_path
197
+ if self.name and self.name not in base_path.name:
198
+ base_path = base_path / self.name
199
+ self.abs_file_path = str(base_path.resolve())
200
+
201
+ @model_validator(mode="before")
202
+ @classmethod
203
+ def set_default_table_settings(cls, data):
204
+ """Create default table_settings based on file_type if not provided."""
205
+ if isinstance(data, dict):
206
+ if "table_settings" not in data or data["table_settings"] is None:
207
+ data["table_settings"] = {}
208
+
209
+ if isinstance(data["table_settings"], dict) and "file_type" not in data["table_settings"]:
210
+ data["table_settings"]["file_type"] = data.get("file_type", "csv")
211
+ return data
212
+
213
+ @model_validator(mode="after")
214
+ def populate_abs_file_path(self):
215
+ """Ensures the absolute file path is populated after validation."""
216
+ if not self.abs_file_path:
217
+ self.set_absolute_filepath()
218
+ return self
138
219
 
139
220
 
140
221
  class OutputCsvTable(BaseModel):
141
222
  """Defines settings for writing a CSV file."""
142
- file_type: str = 'csv'
143
- delimiter: str = ','
144
- encoding: str = 'utf-8'
223
+
224
+ file_type: Literal["csv"] = "csv"
225
+ delimiter: str = ","
226
+ encoding: str = "utf-8"
145
227
 
146
228
 
147
229
  class OutputParquetTable(BaseModel):
148
230
  """Defines settings for writing a Parquet file."""
149
- file_type: str = 'parquet'
231
+
232
+ file_type: Literal["parquet"] = "parquet"
150
233
 
151
234
 
152
235
  class OutputExcelTable(BaseModel):
153
236
  """Defines settings for writing an Excel file."""
154
- file_type: str = 'excel'
155
- sheet_name: str = 'Sheet1'
237
+
238
+ file_type: Literal["excel"] = "excel"
239
+ sheet_name: str = "Sheet1"
240
+
241
+
242
+ # Create a discriminated union
243
+ OutputTableSettings = Annotated[
244
+ OutputCsvTable | OutputParquetTable | OutputExcelTable, Field(discriminator="file_type")
245
+ ]
156
246
 
157
247
 
158
248
  class OutputSettings(BaseModel):
159
249
  """Defines the complete settings for an output node."""
250
+
160
251
  name: str
161
252
  directory: str
162
- file_type: str
163
- fields: Optional[List[str]] = Field(default_factory=list)
164
- write_mode: str = 'overwrite'
165
- output_csv_table: Optional[OutputCsvTable] = Field(default_factory=OutputCsvTable)
166
- output_parquet_table: OutputParquetTable = Field(default_factory=OutputParquetTable)
167
- output_excel_table: OutputExcelTable = Field(default_factory=OutputExcelTable)
168
- abs_file_path: Optional[str] = None
253
+ file_type: str # This drives which table_settings to use
254
+ fields: list[str] | None = Field(default_factory=list)
255
+ write_mode: str = "overwrite"
256
+ table_settings: OutputTableSettings
257
+ abs_file_path: str | None = None
258
+
259
+ def to_yaml_dict(self) -> OutputSettingsYaml:
260
+ """Converts the output settings to a dictionary suitable for YAML serialization."""
261
+ result: OutputSettingsYaml = {
262
+ "name": self.name,
263
+ "directory": self.directory,
264
+ "file_type": self.file_type,
265
+ "write_mode": self.write_mode,
266
+ }
267
+ if self.abs_file_path:
268
+ result["abs_file_path"] = self.abs_file_path
269
+ if self.fields:
270
+ result["fields"] = self.fields
271
+ # Only include table_settings if it has non-default values beyond file_type
272
+ ts_dict = self.table_settings.model_dump(exclude={"file_type"})
273
+ if any(v for v in ts_dict.values()): # Has meaningful settings
274
+ result["table_settings"] = ts_dict
275
+ return result
276
+
277
+ @property
278
+ def sheet_name(self) -> str | None:
279
+ if self.file_type == "excel":
280
+ return self.table_settings.sheet_name
281
+
282
+ @property
283
+ def delimiter(self) -> str | None:
284
+ if self.file_type == "csv":
285
+ return self.table_settings.delimiter
286
+
287
+ @field_validator("table_settings", mode="before")
288
+ @classmethod
289
+ def validate_table_settings(cls, v, info: ValidationInfo):
290
+ """Ensures table_settings matches the file_type."""
291
+ if v is None:
292
+ file_type = info.data.get("file_type", "csv")
293
+ # Create default based on file_type
294
+ match file_type:
295
+ case "csv":
296
+ return OutputCsvTable()
297
+ case "parquet":
298
+ return OutputParquetTable()
299
+ case "excel":
300
+ return OutputExcelTable()
301
+ case _:
302
+ return OutputCsvTable()
303
+
304
+ # If it's a dict, add file_type if missing
305
+ if isinstance(v, dict) and "file_type" not in v:
306
+ v["file_type"] = info.data.get("file_type", "csv")
307
+
308
+ return v
169
309
 
170
310
  def set_absolute_filepath(self):
171
311
  """Resolves the output directory and name into an absolute path."""
@@ -176,7 +316,7 @@ class OutputSettings(BaseModel):
176
316
  base_path = base_path / self.name
177
317
  self.abs_file_path = str(base_path.resolve())
178
318
 
179
- @model_validator(mode='after')
319
+ @model_validator(mode="after")
180
320
  def populate_abs_file_path(self):
181
321
  """Ensures the absolute file path is populated after validation."""
182
322
  self.set_absolute_filepath()
@@ -185,63 +325,82 @@ class OutputSettings(BaseModel):
185
325
 
186
326
  class NodeBase(BaseModel):
187
327
  """Base model for all nodes in a FlowGraph. Contains common metadata."""
328
+
188
329
  model_config = ConfigDict(arbitrary_types_allowed=True)
189
330
  flow_id: int
190
331
  node_id: int
191
- cache_results: Optional[bool] = False
192
- pos_x: Optional[float] = 0
193
- pos_y: Optional[float] = 0
194
- is_setup: Optional[bool] = True
195
- description: Optional[str] = ''
196
- user_id: Optional[int] = None
197
- is_flow_output: Optional[bool] = False
198
- is_user_defined: Optional[bool] = False # Indicator if the node is a user defined node
332
+ cache_results: bool | None = False
333
+ pos_x: float | None = 0
334
+ pos_y: float | None = 0
335
+ is_setup: bool | None = True
336
+ description: str | None = ""
337
+ user_id: int | None = None
338
+ is_flow_output: bool | None = False
339
+ is_user_defined: bool | None = False # Indicator if the node is a user defined node
199
340
 
200
341
 
201
342
  class NodeSingleInput(NodeBase):
202
343
  """A base model for any node that takes a single data input."""
203
- depending_on_id: Optional[int] = -1
344
+
345
+ depending_on_id: int | None = -1
204
346
 
205
347
 
206
348
  class NodeMultiInput(NodeBase):
207
349
  """A base model for any node that takes multiple data inputs."""
208
- depending_on_ids: Optional[List[int]] = [-1]
350
+
351
+ depending_on_ids: list[int] | None = Field(default_factory=list)
209
352
 
210
353
 
211
354
  class NodeSelect(NodeSingleInput):
212
355
  """Settings for a node that selects, renames, and reorders columns."""
356
+
213
357
  keep_missing: bool = True
214
- select_input: List[transform_schema.SelectInput] = Field(default_factory=list)
215
- sorted_by: Optional[Literal['none', 'asc', 'desc']] = 'none'
358
+ select_input: list[transform_schema.SelectInput] = Field(default_factory=list)
359
+ sorted_by: Literal["none", "asc", "desc"] | None = "none"
360
+
361
+ def to_yaml_dict(self) -> NodeSelectYaml:
362
+ """Converts the select node settings to a dictionary for YAML serialization."""
363
+ return {
364
+ "cache_results": self.cache_results,
365
+ "keep_missing": self.keep_missing,
366
+ "select_input": [s.to_yaml_dict() for s in self.select_input],
367
+ "sorted_by": self.sorted_by,
368
+ }
216
369
 
217
370
 
218
371
  class NodeFilter(NodeSingleInput):
219
372
  """Settings for a node that filters rows based on a condition."""
373
+
220
374
  filter_input: transform_schema.FilterInput
221
375
 
222
376
 
223
377
  class NodeSort(NodeSingleInput):
224
378
  """Settings for a node that sorts the data by one or more columns."""
225
- sort_input: List[transform_schema.SortByInput] = Field(default_factory=list)
379
+
380
+ sort_input: list[transform_schema.SortByInput] = Field(default_factory=list)
226
381
 
227
382
 
228
383
  class NodeTextToRows(NodeSingleInput):
229
384
  """Settings for a node that splits a text column into multiple rows."""
385
+
230
386
  text_to_rows_input: transform_schema.TextToRowsInput
231
387
 
232
388
 
233
389
  class NodeSample(NodeSingleInput):
234
390
  """Settings for a node that samples a subset of the data."""
391
+
235
392
  sample_size: int = 1000
236
393
 
237
394
 
238
395
  class NodeRecordId(NodeSingleInput):
239
396
  """Settings for a node that adds a unique record ID column."""
397
+
240
398
  record_id_input: transform_schema.RecordIdInput
241
399
 
242
400
 
243
401
  class NodeJoin(NodeMultiInput):
244
402
  """Settings for a node that performs a standard SQL-style join."""
403
+
245
404
  auto_generate_selection: bool = True
246
405
  verify_integrity: bool = True
247
406
  join_input: transform_schema.JoinInput
@@ -249,9 +408,22 @@ class NodeJoin(NodeMultiInput):
249
408
  auto_keep_right: bool = True
250
409
  auto_keep_left: bool = True
251
410
 
411
+ def to_yaml_dict(self) -> NodeJoinYaml:
412
+ """Converts the join node settings to a dictionary for YAML serialization."""
413
+ return {
414
+ "cache_results": self.cache_results,
415
+ "auto_generate_selection": self.auto_generate_selection,
416
+ "verify_integrity": self.verify_integrity,
417
+ "join_input": self.join_input.to_yaml_dict(),
418
+ "auto_keep_all": self.auto_keep_all,
419
+ "auto_keep_right": self.auto_keep_right,
420
+ "auto_keep_left": self.auto_keep_left,
421
+ }
422
+
252
423
 
253
424
  class NodeCrossJoin(NodeMultiInput):
254
425
  """Settings for a node that performs a cross join."""
426
+
255
427
  auto_generate_selection: bool = True
256
428
  verify_integrity: bool = True
257
429
  cross_join_input: transform_schema.CrossJoinInput
@@ -259,106 +431,138 @@ class NodeCrossJoin(NodeMultiInput):
259
431
  auto_keep_right: bool = True
260
432
  auto_keep_left: bool = True
261
433
 
434
+ def to_yaml_dict(self) -> NodeCrossJoinYaml:
435
+ """Converts the cross join node settings to a dictionary for YAML serialization."""
436
+ return {
437
+ "cache_results": self.cache_results,
438
+ "auto_generate_selection": self.auto_generate_selection,
439
+ "verify_integrity": self.verify_integrity,
440
+ "cross_join_input": self.cross_join_input.to_yaml_dict(),
441
+ "auto_keep_all": self.auto_keep_all,
442
+ "auto_keep_right": self.auto_keep_right,
443
+ "auto_keep_left": self.auto_keep_left,
444
+ }
445
+
262
446
 
263
447
  class NodeFuzzyMatch(NodeJoin):
264
448
  """Settings for a node that performs a fuzzy join based on string similarity."""
449
+
265
450
  join_input: transform_schema.FuzzyMatchInput
266
451
 
452
+ def to_yaml_dict(self) -> NodeFuzzyMatchYaml:
453
+ """Converts the fuzzy match node settings to a dictionary for YAML serialization."""
454
+ return {
455
+ "cache_results": self.cache_results,
456
+ "auto_generate_selection": self.auto_generate_selection,
457
+ "verify_integrity": self.verify_integrity,
458
+ "join_input": self.join_input.to_yaml_dict(),
459
+ "auto_keep_all": self.auto_keep_all,
460
+ "auto_keep_right": self.auto_keep_right,
461
+ "auto_keep_left": self.auto_keep_left,
462
+ }
463
+
267
464
 
268
465
  class NodeDatasource(NodeBase):
269
466
  """Base settings for a node that acts as a data source."""
467
+
270
468
  file_ref: str = None
271
469
 
272
470
 
273
471
  class RawData(BaseModel):
274
472
  """Represents data in a raw, columnar format for manual input."""
275
- columns: List[MinimalFieldInfo] = None
276
- data: List[List]
473
+
474
+ columns: list[MinimalFieldInfo] = None
475
+ data: list[list]
277
476
 
278
477
  @classmethod
279
- def from_pylist(cls, pylist: List[dict]):
478
+ def from_pylist(cls, pylist: list[dict]):
280
479
  """Creates a RawData object from a list of Python dictionaries."""
281
480
  if len(pylist) == 0:
282
481
  return cls(columns=[], data=[])
283
482
  pylist = ensure_similarity_dicts(pylist)
284
- values = [standardize_col_dtype([vv for vv in c]) for c in
285
- zip(*(r.values() for r in pylist))]
483
+ values = [standardize_col_dtype([vv for vv in c]) for c in zip(*(r.values() for r in pylist), strict=False)]
286
484
  data_types = (pl.DataType.from_python(type(next((v for v in column_values), None))) for column_values in values)
287
485
  columns = [MinimalFieldInfo(name=c, data_type=str(next(data_types))) for c in pylist[0].keys()]
288
486
  return cls(columns=columns, data=values)
289
487
 
290
- def to_pylist(self) -> List[dict]:
488
+ def to_pylist(self) -> list[dict]:
291
489
  """Converts the RawData object back into a list of Python dictionaries."""
292
490
  return [{c.name: self.data[ci][ri] for ci, c in enumerate(self.columns)} for ri in range(len(self.data[0]))]
293
491
 
294
492
 
295
493
  class NodeManualInput(NodeBase):
296
494
  """Settings for a node that allows direct data entry in the UI."""
297
- raw_data_format: Optional[RawData] = None
495
+
496
+ raw_data_format: RawData | None = None
298
497
 
299
498
 
300
499
  class NodeRead(NodeBase):
301
500
  """Settings for a node that reads data from a file."""
501
+
302
502
  received_file: ReceivedTable
303
503
 
304
504
 
305
505
  class DatabaseConnection(BaseModel):
306
506
  """Defines the connection parameters for a database."""
507
+
307
508
  database_type: str = "postgresql"
308
- username: Optional[str] = None
309
- password_ref: Optional[SecretRef] = None
310
- host: Optional[str] = None
311
- port: Optional[int] = None
312
- database: Optional[str] = None
313
- url: Optional[str] = None
509
+ username: str | None = None
510
+ password_ref: SecretRef | None = None
511
+ host: str | None = None
512
+ port: int | None = None
513
+ database: str | None = None
514
+ url: str | None = None
314
515
 
315
516
 
316
517
  class FullDatabaseConnection(BaseModel):
317
518
  """A complete database connection model including the secret password."""
519
+
318
520
  connection_name: str
319
521
  database_type: str = "postgresql"
320
522
  username: str
321
523
  password: SecretStr
322
- host: Optional[str] = None
323
- port: Optional[int] = None
324
- database: Optional[str] = None
325
- ssl_enabled: Optional[bool] = False
326
- url: Optional[str] = None
524
+ host: str | None = None
525
+ port: int | None = None
526
+ database: str | None = None
527
+ ssl_enabled: bool | None = False
528
+ url: str | None = None
327
529
 
328
530
 
329
531
  class FullDatabaseConnectionInterface(BaseModel):
330
532
  """A database connection model intended for UI display, omitting the password."""
533
+
331
534
  connection_name: str
332
535
  database_type: str = "postgresql"
333
536
  username: str
334
- host: Optional[str] = None
335
- port: Optional[int] = None
336
- database: Optional[str] = None
337
- ssl_enabled: Optional[bool] = False
338
- url: Optional[str] = None
537
+ host: str | None = None
538
+ port: int | None = None
539
+ database: str | None = None
540
+ ssl_enabled: bool | None = False
541
+ url: str | None = None
339
542
 
340
543
 
341
544
  class DatabaseSettings(BaseModel):
342
545
  """Defines settings for reading from a database, either via table or query."""
343
- connection_mode: Optional[Literal['inline', 'reference']] = 'inline'
344
- database_connection: Optional[DatabaseConnection] = None
345
- database_connection_name: Optional[str] = None
346
- schema_name: Optional[str] = None
347
- table_name: Optional[str] = None
348
- query: Optional[str] = None
349
- query_mode: Literal['query', 'table', 'reference'] = 'table'
350
-
351
- @model_validator(mode='after')
546
+
547
+ connection_mode: Literal["inline", "reference"] | None = "inline"
548
+ database_connection: DatabaseConnection | None = None
549
+ database_connection_name: str | None = None
550
+ schema_name: str | None = None
551
+ table_name: str | None = None
552
+ query: str | None = None
553
+ query_mode: Literal["query", "table", "reference"] = "table"
554
+
555
+ @model_validator(mode="after")
352
556
  def validate_table_or_query(self):
353
557
  # Validate that either table_name or query is provided
354
- if (not self.table_name and not self.query) and self.query_mode == 'inline':
558
+ if (not self.table_name and not self.query) and self.query_mode == "inline":
355
559
  raise ValueError("Either 'table_name' or 'query' must be provided")
356
560
 
357
561
  # Validate correct connection information based on connection_mode
358
- if self.connection_mode == 'inline' and self.database_connection is None:
562
+ if self.connection_mode == "inline" and self.database_connection is None:
359
563
  raise ValueError("When 'connection_mode' is 'inline', 'database_connection' must be provided")
360
564
 
361
- if self.connection_mode == 'reference' and not self.database_connection_name:
565
+ if self.connection_mode == "reference" and not self.database_connection_name:
362
566
  raise ValueError("When 'connection_mode' is 'reference', 'database_connection_name' must be provided")
363
567
 
364
568
  return self
@@ -366,44 +570,51 @@ class DatabaseSettings(BaseModel):
366
570
 
367
571
  class DatabaseWriteSettings(BaseModel):
368
572
  """Defines settings for writing data to a database table."""
369
- connection_mode: Optional[Literal['inline', 'reference']] = 'inline'
370
- database_connection: Optional[DatabaseConnection] = None
371
- database_connection_name: Optional[str] = None
573
+
574
+ connection_mode: Literal["inline", "reference"] | None = "inline"
575
+ database_connection: DatabaseConnection | None = None
576
+ database_connection_name: str | None = None
372
577
  table_name: str
373
- schema_name: Optional[str] = None
374
- if_exists: Optional[Literal['append', 'replace', 'fail']] = 'append'
578
+ schema_name: str | None = None
579
+ if_exists: Literal["append", "replace", "fail"] | None = "append"
375
580
 
376
581
 
377
582
  class NodeDatabaseReader(NodeBase):
378
583
  """Settings for a node that reads from a database."""
584
+
379
585
  database_settings: DatabaseSettings
380
- fields: Optional[List[MinimalFieldInfo]] = None
586
+ fields: list[MinimalFieldInfo] | None = None
381
587
 
382
588
 
383
589
  class NodeDatabaseWriter(NodeSingleInput):
384
590
  """Settings for a node that writes data to a database."""
591
+
385
592
  database_write_settings: DatabaseWriteSettings
386
593
 
387
594
 
388
595
  class NodeCloudStorageReader(NodeBase):
389
596
  """Settings for a node that reads from a cloud storage service (S3, GCS, etc.)."""
597
+
390
598
  cloud_storage_settings: CloudStorageReadSettings
391
- fields: Optional[List[MinimalFieldInfo]] = None
599
+ fields: list[MinimalFieldInfo] | None = None
392
600
 
393
601
 
394
602
  class NodeCloudStorageWriter(NodeSingleInput):
395
603
  """Settings for a node that writes to a cloud storage service."""
604
+
396
605
  cloud_storage_settings: CloudStorageWriteSettings
397
606
 
398
607
 
399
608
  class ExternalSource(BaseModel):
400
609
  """Base model for data coming from a predefined external source."""
401
- orientation: str = 'row'
402
- fields: Optional[List[MinimalFieldInfo]] = None
610
+
611
+ orientation: str = "row"
612
+ fields: list[MinimalFieldInfo] | None = None
403
613
 
404
614
 
405
615
  class SampleUsers(ExternalSource):
406
616
  """Settings for generating a sample dataset of users."""
617
+
407
618
  SAMPLE_USERS: bool
408
619
  class_name: str = "sample_users"
409
620
  size: int = 100
@@ -411,69 +622,91 @@ class SampleUsers(ExternalSource):
411
622
 
412
623
  class NodeExternalSource(NodeBase):
413
624
  """Settings for a node that connects to a registered external data source."""
625
+
414
626
  identifier: str
415
627
  source_settings: SampleUsers
416
628
 
417
629
 
418
630
  class NodeFormula(NodeSingleInput):
419
631
  """Settings for a node that applies a formula to create/modify a column."""
632
+
420
633
  function: transform_schema.FunctionInput = None
421
634
 
422
635
 
423
636
  class NodeGroupBy(NodeSingleInput):
424
637
  """Settings for a node that performs a group-by and aggregation operation."""
638
+
425
639
  groupby_input: transform_schema.GroupByInput = None
426
640
 
427
641
 
428
642
  class NodePromise(NodeBase):
429
643
  """A placeholder node for an operation that has not yet been configured."""
644
+
430
645
  is_setup: bool = False
431
646
  node_type: str
432
647
 
433
648
 
434
649
  class NodeInputConnection(BaseModel):
435
650
  """Represents the input side of a connection between two nodes."""
651
+
436
652
  node_id: int
437
653
  connection_class: InputConnectionClass
438
654
 
439
- def get_node_input_connection_type(self) -> Literal['main', 'right', 'left']:
655
+ def get_node_input_connection_type(self) -> Literal["main", "right", "left"]:
440
656
  """Determines the semantic type of the input (e.g., for a join)."""
441
657
  match self.connection_class:
442
- case 'input-0': return 'main'
443
- case 'input-1': return 'right'
444
- case 'input-2': return 'left'
445
- case _: raise ValueError(f"Unexpected connection_class: {self.connection_class}")
658
+ case "input-0":
659
+ return "main"
660
+ case "input-1":
661
+ return "right"
662
+ case "input-2":
663
+ return "left"
664
+ case _:
665
+ raise ValueError(f"Unexpected connection_class: {self.connection_class}")
446
666
 
447
667
 
448
668
  class NodePivot(NodeSingleInput):
449
669
  """Settings for a node that pivots data from a long to a wide format."""
670
+
450
671
  pivot_input: transform_schema.PivotInput = None
451
- output_fields: Optional[List[MinimalFieldInfo]] = None
672
+ output_fields: list[MinimalFieldInfo] | None = None
452
673
 
453
674
 
454
675
  class NodeUnpivot(NodeSingleInput):
455
676
  """Settings for a node that unpivots data from a wide to a long format."""
677
+
456
678
  unpivot_input: transform_schema.UnpivotInput = None
457
679
 
458
680
 
459
681
  class NodeUnion(NodeMultiInput):
460
682
  """Settings for a node that concatenates multiple data inputs."""
683
+
461
684
  union_input: transform_schema.UnionInput = Field(default_factory=transform_schema.UnionInput)
462
685
 
463
686
 
464
687
  class NodeOutput(NodeSingleInput):
465
688
  """Settings for a node that writes its input to a file."""
689
+
466
690
  output_settings: OutputSettings
467
691
 
692
+ def to_yaml_dict(self) -> NodeOutputYaml:
693
+ """Converts the output node settings to a dictionary for YAML serialization."""
694
+ return {
695
+ "cache_results": self.cache_results,
696
+ "output_settings": self.output_settings.to_yaml_dict(),
697
+ }
698
+
468
699
 
469
700
  class NodeOutputConnection(BaseModel):
470
701
  """Represents the output side of a connection between two nodes."""
702
+
471
703
  node_id: int
472
704
  connection_class: OutputConnectionClass
473
705
 
474
706
 
475
707
  class NodeConnection(BaseModel):
476
708
  """Represents a connection (edge) between two nodes in the graph."""
709
+
477
710
  input_connection: NodeInputConnection
478
711
  output_connection: NodeOutputConnection
479
712
 
@@ -481,45 +714,56 @@ class NodeConnection(BaseModel):
481
714
  def create_from_simple_input(cls, from_id: int, to_id: int, input_type: InputType = "input-0"):
482
715
  """Creates a standard connection between two nodes."""
483
716
  match input_type:
484
- case "main": connection_class: InputConnectionClass = "input-0"
485
- case "right": connection_class: InputConnectionClass = "input-1"
486
- case "left": connection_class: InputConnectionClass = "input-2"
487
- case _: connection_class: InputConnectionClass = "input-0"
717
+ case "main":
718
+ connection_class: InputConnectionClass = "input-0"
719
+ case "right":
720
+ connection_class: InputConnectionClass = "input-1"
721
+ case "left":
722
+ connection_class: InputConnectionClass = "input-2"
723
+ case _:
724
+ connection_class: InputConnectionClass = "input-0"
488
725
  node_input = NodeInputConnection(node_id=to_id, connection_class=connection_class)
489
- node_output = NodeOutputConnection(node_id=from_id, connection_class='output-0')
726
+ node_output = NodeOutputConnection(node_id=from_id, connection_class="output-0")
490
727
  return cls(input_connection=node_input, output_connection=node_output)
491
728
 
492
729
 
493
730
  class NodeDescription(BaseModel):
494
731
  """A simple model for updating a node's description text."""
495
- description: str = ''
732
+
733
+ description: str = ""
496
734
 
497
735
 
498
736
  class NodeExploreData(NodeBase):
499
737
  """Settings for a node that provides an interactive data exploration interface."""
500
- graphic_walker_input: Optional[gs_schemas.GraphicWalkerInput] = None
738
+
739
+ graphic_walker_input: gs_schemas.GraphicWalkerInput | None = None
501
740
 
502
741
 
503
742
  class NodeGraphSolver(NodeSingleInput):
504
743
  """Settings for a node that solves graph-based problems (e.g., connected components)."""
744
+
505
745
  graph_solver_input: transform_schema.GraphSolverInput
506
746
 
507
747
 
508
748
  class NodeUnique(NodeSingleInput):
509
749
  """Settings for a node that returns the unique rows from the data."""
750
+
510
751
  unique_input: transform_schema.UniqueInput
511
752
 
512
753
 
513
754
  class NodeRecordCount(NodeSingleInput):
514
755
  """Settings for a node that counts the number of records."""
756
+
515
757
  pass
516
758
 
517
759
 
518
760
  class NodePolarsCode(NodeMultiInput):
519
761
  """Settings for a node that executes arbitrary user-provided Polars code."""
762
+
520
763
  polars_code_input: transform_schema.PolarsCodeInput
521
764
 
522
765
 
523
766
  class UserDefinedNode(NodeMultiInput):
524
767
  """Settings for a node that contains the user defined node information"""
768
+
525
769
  settings: Any