Flowfile 0.5.6__py3-none-any.whl → 0.6.1__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 (256) hide show
  1. flowfile/api.py +8 -6
  2. flowfile/web/static/assets/{AdminView-c2c7942b.js → AdminView-C4K1DdHI.js} +28 -33
  3. flowfile/web/static/assets/{CloudConnectionView-7a3042c6.js → CloudConnectionView-BZbPvPUL.js} +39 -50
  4. flowfile/web/static/assets/{CloudStorageReader-24c54524.css → CloudStorageReader-BDByiqPI.css} +25 -25
  5. flowfile/web/static/assets/{CloudStorageReader-709c4037.js → CloudStorageReader-DLVukNJ7.js} +30 -35
  6. flowfile/web/static/assets/{CloudStorageWriter-604c51a8.js → CloudStorageWriter-Bfi-C1QW.js} +32 -37
  7. flowfile/web/static/assets/{CloudStorageWriter-60547855.css → CloudStorageWriter-y8jL8yjG.css} +24 -24
  8. flowfile/web/static/assets/{ColumnActionInput-d63d6746.js → ColumnActionInput-BpiCApw9.js} +7 -12
  9. flowfile/web/static/assets/{ColumnSelector-0c8cd1cd.js → ColumnSelector-CEAwedI7.js} +1 -2
  10. flowfile/web/static/assets/ContextMenu-CdojQu0w.js +9 -0
  11. flowfile/web/static/assets/ContextMenu-D12mhsy1.js +9 -0
  12. flowfile/web/static/assets/ContextMenu-EWUR98va.js +9 -0
  13. flowfile/web/static/assets/{ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js → ContextMenu.vue_vue_type_script_setup_true_lang-I4rXXd6G.js} +4 -5
  14. flowfile/web/static/assets/{CrossJoin-38e5b99a.js → CrossJoin-BOFfxkJO.js} +19 -18
  15. flowfile/web/static/assets/{CrossJoin-71b4cc10.css → CrossJoin-Cmbyt9im.css} +18 -18
  16. flowfile/web/static/assets/{CustomNode-76e8f3f5.js → CustomNode-Bhpezobq.js} +12 -17
  17. flowfile/web/static/assets/{DatabaseConnectionSettings-38155669.js → DatabaseConnectionSettings-Dw3bSJKB.js} +10 -11
  18. flowfile/web/static/assets/{DatabaseReader-5bf8c75b.css → DatabaseReader-D6pUNUCs.css} +21 -21
  19. flowfile/web/static/assets/{DatabaseReader-2e549c8f.js → DatabaseReader-m87ghlw0.js} +36 -34
  20. flowfile/web/static/assets/{DatabaseView-dc877c29.js → DatabaseView-CisSAtpe.js} +30 -38
  21. flowfile/web/static/assets/{DatabaseWriter-ffb91864.js → DatabaseWriter-Bbj9JLdL.js} +33 -35
  22. flowfile/web/static/assets/{DatabaseWriter-bdcf2c8b.css → DatabaseWriter-RBqdFLj8.css} +17 -17
  23. flowfile/web/static/assets/{DesignerView-a4466dab.js → DesignerView-DemDevTQ.js} +1752 -2054
  24. flowfile/web/static/assets/{DesignerView-71d4e9a1.css → DesignerView-Dm6OzlIc.css} +209 -168
  25. flowfile/web/static/assets/{DocumentationView-979afc84.js → DocumentationView-BrC1ZR3H.js} +3 -4
  26. flowfile/web/static/assets/{ExploreData-e4b92aaf.js → ExploreData-BMKcDuRb.js} +8 -10
  27. flowfile/web/static/assets/{ExternalSource-d08e7227.js → ExternalSource-BXrNNS-f.js} +40 -42
  28. flowfile/web/static/assets/{ExternalSource-7ac7373f.css → ExternalSource-NB6WVl5R.css} +14 -14
  29. flowfile/web/static/assets/{Filter-7add806d.js → Filter-C2MjsN6P.js} +36 -33
  30. flowfile/web/static/assets/{Filter-7494ea97.css → Filter-DCMGGuGC.css} +9 -9
  31. flowfile/web/static/assets/{Formula-53d58c43.css → Formula-BYafbDj8.css} +4 -4
  32. flowfile/web/static/assets/{Formula-36ab24d2.js → Formula-ufuy4mVD.js} +27 -26
  33. flowfile/web/static/assets/{FuzzyMatch-ad6361d6.css → FuzzyMatch-BGJAwgd0.css} +42 -42
  34. flowfile/web/static/assets/{FuzzyMatch-cc01bb04.js → FuzzyMatch-BOHODq3h.js} +36 -38
  35. flowfile/web/static/assets/{GraphSolver-4fb98f3b.js → GraphSolver-B6ZzpNGO.js} +23 -21
  36. flowfile/web/static/assets/{GraphSolver-4b4d7db9.css → GraphSolver-DFN83sj3.css} +4 -4
  37. flowfile/web/static/assets/{GroupBy-b3c8f429.js → GroupBy-B9BRNcfe.js} +30 -29
  38. flowfile/web/static/assets/{Sort-4abb7fae.css → GroupBy-x4ooP5np.css} +1 -1
  39. flowfile/web/static/assets/Join-Bx_g5bZz.css +118 -0
  40. flowfile/web/static/assets/{Join-096b7b26.js → Join-DsBEy1IH.js} +48 -43
  41. flowfile/web/static/assets/{LoginView-c33a246a.js → LoginView-Ct0rhdcO.js} +1 -2
  42. flowfile/web/static/assets/{ManualInput-39111f19.css → ManualInput-DlZmtMdt.css} +48 -48
  43. flowfile/web/static/assets/{ManualInput-7307e9b1.js → ManualInput-bC4BUgnG.js} +40 -41
  44. flowfile/web/static/assets/{MultiSelect-14822c48.js → MultiSelect-DIQ8PuTC.js} +2 -2
  45. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-90c4d340.js → MultiSelect.vue_vue_type_script_setup_true_lang-BefHfqTI.js} +1 -1
  46. flowfile/web/static/assets/{NodeDesigner-5036c392.js → NodeDesigner-D39yzr2k.js} +178 -208
  47. flowfile/web/static/assets/{NodeDesigner-94cd4dd3.css → NodeDesigner-R0l6sYyY.css} +76 -76
  48. flowfile/web/static/assets/{NumericInput-15cf3b72.js → NumericInput-DMSX3oOr.js} +2 -2
  49. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-91e679d7.js → NumericInput.vue_vue_type_script_setup_true_lang-d0YlVHAl.js} +1 -1
  50. flowfile/web/static/assets/{Output-1f8ed42c.js → Output-D0VoXGcW.js} +26 -34
  51. flowfile/web/static/assets/{Output-692dd25d.css → Output-DsmglIDy.css} +5 -5
  52. flowfile/web/static/assets/{Pivot-0e153f4e.js → Pivot-BnMB4sEe.js} +26 -26
  53. flowfile/web/static/assets/{Pivot-0eda81b4.css → Pivot-qKTyWxop.css} +4 -4
  54. flowfile/web/static/assets/{PivotValidation-81ec2a33.js → PivotValidation-B2lWvugt.js} +7 -9
  55. flowfile/web/static/assets/{PivotValidation-5a4f7c79.js → PivotValidation-BPlhRjpL.js} +7 -9
  56. flowfile/web/static/assets/{PolarsCode-a39f15ac.js → PolarsCode-5h0tHnWR.js} +22 -20
  57. flowfile/web/static/assets/{PopOver-ddcfe4f6.js → PopOver-BHpt5rsj.js} +5 -9
  58. flowfile/web/static/assets/{PopOver-d96599db.css → PopOver-CyYM4-rV.css} +1 -1
  59. flowfile/web/static/assets/{Read-90f366bc.css → Read-DJxkrTb_.css} +10 -10
  60. flowfile/web/static/assets/Read-TsLEFh3B.js +227 -0
  61. flowfile/web/static/assets/{RecordCount-e9048ccd.js → RecordCount-DkVixq9v.js} +18 -17
  62. flowfile/web/static/assets/{RecordId-ad02521d.js → RecordId-C2UEGlCf.js} +42 -39
  63. flowfile/web/static/assets/{SQLQueryComponent-2eeecf0b.js → SQLQueryComponent-Dr5KMoD3.js} +2 -3
  64. flowfile/web/static/assets/{Sample-9a68c23d.js → Sample-Cb3eQNmd.js} +30 -30
  65. flowfile/web/static/assets/{SecretSelector-2429f35a.js → SecretSelector-De2L2bSx.js} +3 -4
  66. flowfile/web/static/assets/{SecretsView-c6afc915.js → SecretsView-CheC9BPV.js} +13 -16
  67. flowfile/web/static/assets/{Select-fcd002b6.js → Select-CI8TloRs.js} +41 -36
  68. flowfile/web/static/assets/{SettingsSection-5ce15962.js → SettingsSection-B39ulIiI.js} +1 -2
  69. flowfile/web/static/assets/{SettingsSection-c6b1362c.js → SettingsSection-BiCc7S9h.js} +1 -2
  70. flowfile/web/static/assets/{SettingsSection-cebb91d5.js → SettingsSection-CITK_R7o.js} +2 -3
  71. flowfile/web/static/assets/{SettingsSection-26fe48d4.css → SettingsSection-D2GgY-Aq.css} +4 -4
  72. flowfile/web/static/assets/{SetupView-2d12e01f.js → SetupView-C1aXRDvp.js} +1 -2
  73. flowfile/web/static/assets/{SingleSelect-b67de4eb.js → SingleSelect-Kr_hz90m.js} +2 -2
  74. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-eedb70eb.js → SingleSelect.vue_vue_type_script_setup_true_lang-Rxht5Z5N.js} +1 -1
  75. flowfile/web/static/assets/{SliderInput-fd8134ac.js → SliderInput-CLqpCxCb.js} +1 -2
  76. flowfile/web/static/assets/{GroupBy-5792782d.css → Sort-BIt2kc_p.css} +1 -1
  77. flowfile/web/static/assets/{Sort-c005a573.js → Sort-Dnw_J6Qi.js} +25 -25
  78. flowfile/web/static/assets/{TextInput-1bb31dab.js → TextInput-wdlunIZC.js} +2 -2
  79. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-a51fe730.js → TextInput.vue_vue_type_script_setup_true_lang-Bcj3ywzv.js} +1 -1
  80. flowfile/web/static/assets/{TextToRows-4f363753.js → TextToRows-BhtyGWPq.js} +42 -49
  81. flowfile/web/static/assets/{TextToRows-12afb4f4.css → TextToRows-DivDOLDx.css} +9 -9
  82. flowfile/web/static/assets/{ToggleSwitch-ca0f2e5e.js → ToggleSwitch-B-6WzfFf.js} +2 -2
  83. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-49aa41d8.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-Cj8LqT-b.js} +1 -1
  84. flowfile/web/static/assets/{UnavailableFields-f6147968.js → UnavailableFields-Yf6XSqFB.js} +2 -3
  85. flowfile/web/static/assets/{Union-c65f17b7.js → Union-CwpjeKYC.js} +20 -23
  86. flowfile/web/static/assets/{Unpivot-b6ad6427.css → Union-DQJcpp3-.css} +6 -6
  87. flowfile/web/static/assets/{Unique-a1d96fb2.js → Unique-25v3urqH.js} +75 -74
  88. flowfile/web/static/assets/{Union-d6a8d7d5.css → Unpivot-Deqh1gtI.css} +6 -6
  89. flowfile/web/static/assets/{Unpivot-c2657ff3.js → Unpivot-sYcTTXrq.js} +34 -27
  90. flowfile/web/static/assets/{UnpivotValidation-28e29a3b.js → UnpivotValidation-C5DDEKY2.js} +5 -7
  91. flowfile/web/static/assets/VueGraphicWalker-B8l1_Z92.js +131 -0
  92. flowfile/web/static/assets/VueGraphicWalker-Da_1-3me.css +21 -0
  93. flowfile/web/static/assets/{api-df48ec50.js → api-C0LvF-0C.js} +1 -1
  94. flowfile/web/static/assets/{api-ee542cf7.js → api-DaC83EO_.js} +1 -1
  95. flowfile/web/static/assets/client-C8Ygr6Gb.js +42 -0
  96. flowfile/web/static/assets/{dropDown-7576a76a.js → dropDown-D5YXaPRR.js} +7 -12
  97. flowfile/web/static/assets/{fullEditor-7583bef5.js → fullEditor-BVYnWm05.js} +300 -18
  98. flowfile/web/static/assets/genericNodeSettings-2wAu-QKn.css +75 -0
  99. flowfile/web/static/assets/genericNodeSettings-BBtW_Cpz.js +590 -0
  100. flowfile/web/static/assets/{VueGraphicWalker-2fc3ddd4.js → graphic-walker.es-VrK6vdGE.js} +92305 -89751
  101. flowfile/web/static/assets/index-BCJxPfM5.js +6693 -0
  102. flowfile/web/static/assets/{index-057d770d.js → index-CHPMUR0d.js} +150 -170
  103. flowfile/web/static/assets/index-DPkoZWq8.js +32 -0
  104. flowfile/web/static/assets/index-DnW_KC_I.js +277 -0
  105. flowfile/web/static/assets/index-UFXyfirV.css +10797 -0
  106. flowfile/web/static/assets/index-bcuE0Z0p.js +87456 -0
  107. flowfile/web/static/assets/{node.types-2c15bb7e.js → node.types-Dl4gtSW9.js} +2 -2
  108. flowfile/web/static/assets/{outputCsv-c492b15e.js → outputCsv-BELuBiJZ.js} +1 -2
  109. flowfile/web/static/assets/outputCsv-CdGkv-fN.css +2581 -0
  110. flowfile/web/static/assets/{outputExcel-13bfa10f.js → outputExcel-D0TTNM79.js} +1 -2
  111. flowfile/web/static/assets/{outputParquet-9be1523a.js → outputParquet-Cz9EbRHj.js} +1 -2
  112. flowfile/web/static/assets/{readCsv-5a49a8c9.js → readCsv-7bd3kUMI.js} +1 -2
  113. flowfile/web/static/assets/{readExcel-27c30ad8.js → readExcel-Cq8CCwIv.js} +3 -4
  114. flowfile/web/static/assets/{readParquet-c5244ad5.css → readParquet-CRDmBrsp.css} +4 -4
  115. flowfile/web/static/assets/{readParquet-446bde68.js → readParquet-DjR4mRaj.js} +4 -5
  116. flowfile/web/static/assets/{secrets.api-34431884.js → secrets.api-C9o2KE5V.js} +1 -1
  117. flowfile/web/static/assets/{selectDynamic-5754a2b1.js → selectDynamic-Bl5FVsME.js} +5 -7
  118. flowfile/web/static/assets/useNodeSettings-dMS9zmh_.js +69 -0
  119. flowfile/web/static/assets/{vue-codemirror.esm-8f46fb36.js → vue-codemirror.esm-CwaYwln0.js} +3469 -3064
  120. flowfile/web/static/assets/{vue-content-loader.es-808fe33a.js → vue-content-loader.es-CMoRXo7N.js} +3 -3
  121. flowfile/web/static/index.html +2 -3
  122. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/METADATA +2 -1
  123. flowfile-0.6.1.dist-info/RECORD +417 -0
  124. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/WHEEL +1 -1
  125. flowfile_core/auth/password.py +1 -0
  126. flowfile_core/database/init_db.py +7 -5
  127. flowfile_core/fileExplorer/funcs.py +2 -2
  128. flowfile_core/flowfile/code_generator/code_generator.py +13 -11
  129. flowfile_core/flowfile/filter_expressions.py +327 -0
  130. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +61 -59
  131. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +3 -29
  132. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +45 -14
  133. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +20 -3
  134. flowfile_core/flowfile/flow_data_engine/subprocess_operations/streaming.py +206 -0
  135. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +146 -24
  136. flowfile_core/flowfile/flow_graph.py +504 -190
  137. flowfile_core/flowfile/flow_node/__init__.py +32 -0
  138. flowfile_core/flowfile/flow_node/executor.py +404 -0
  139. flowfile_core/flowfile/flow_node/flow_node.py +207 -106
  140. flowfile_core/flowfile/flow_node/models.py +40 -0
  141. flowfile_core/flowfile/flow_node/output_field_config_applier.py +217 -0
  142. flowfile_core/flowfile/flow_node/schema_utils.py +78 -0
  143. flowfile_core/flowfile/flow_node/state.py +155 -0
  144. flowfile_core/flowfile/history_manager.py +401 -0
  145. flowfile_core/flowfile/manage/compatibility_enhancements.py +9 -0
  146. flowfile_core/flowfile/manage/io_flowfile.py +3 -1
  147. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +20 -4
  148. flowfile_core/flowfile/util/execution_orderer.py +89 -36
  149. flowfile_core/routes/auth.py +8 -9
  150. flowfile_core/routes/routes.py +320 -101
  151. flowfile_core/routes/user_defined_components.py +18 -16
  152. flowfile_core/schemas/history_schema.py +220 -0
  153. flowfile_core/schemas/input_schema.py +130 -6
  154. flowfile_core/schemas/schemas.py +9 -0
  155. flowfile_core/schemas/transform_schema.py +27 -5
  156. flowfile_core/schemas/yaml_types.py +23 -5
  157. flowfile_frame/adding_expr.py +18 -126
  158. flowfile_frame/callable_utils.py +261 -0
  159. flowfile_frame/database/connection_manager.py +0 -1
  160. flowfile_frame/expr.py +8 -4
  161. flowfile_frame/flow_frame.py +41 -41
  162. flowfile_frame/lazy.py +3 -12
  163. flowfile_frame/lazy_methods.py +5 -64
  164. flowfile_frame/utils.py +13 -32
  165. flowfile_worker/funcs.py +6 -4
  166. flowfile_worker/main.py +2 -0
  167. flowfile_worker/models.py +31 -11
  168. flowfile_worker/routes.py +60 -35
  169. flowfile_worker/spawner.py +7 -1
  170. flowfile_worker/streaming.py +335 -0
  171. flowfile/web/static/assets/ContextMenu-366bf1b4.js +0 -9
  172. flowfile/web/static/assets/ContextMenu-85cf5b44.js +0 -9
  173. flowfile/web/static/assets/ContextMenu-9d28ae6d.js +0 -9
  174. flowfile/web/static/assets/Join-28b5e18f.css +0 -109
  175. flowfile/web/static/assets/Read-39b63932.js +0 -222
  176. flowfile/web/static/assets/VueGraphicWalker-430f0b86.css +0 -6
  177. flowfile/web/static/assets/database_reader-ce1e55f3.svg +0 -24
  178. flowfile/web/static/assets/database_writer-b4ad0753.svg +0 -23
  179. flowfile/web/static/assets/element-icons-9c88a535.woff +0 -0
  180. flowfile/web/static/assets/element-icons-de5eb258.ttf +0 -0
  181. flowfile/web/static/assets/genericNodeSettings-0155288b.js +0 -136
  182. flowfile/web/static/assets/genericNodeSettings-3b2507ea.css +0 -46
  183. flowfile/web/static/assets/index-aeec439d.js +0 -38
  184. flowfile/web/static/assets/index-ca6799de.js +0 -62760
  185. flowfile/web/static/assets/index-d60c9dd4.css +0 -10777
  186. flowfile/web/static/assets/nodeInput-d478b9ac.js +0 -2
  187. flowfile/web/static/assets/outputCsv-cc84e09f.css +0 -2499
  188. flowfile-0.5.6.dist-info/RECORD +0 -407
  189. /flowfile/web/static/assets/{AdminView-f53bad23.css → AdminView-B2Dthl3u.css} +0 -0
  190. /flowfile/web/static/assets/{CloudConnectionView-cf85f943.css → CloudConnectionView-BdFYGWV7.css} +0 -0
  191. /flowfile/web/static/assets/{ColumnActionInput-c44b7aee.css → ColumnActionInput-dCasSIC9.css} +0 -0
  192. /flowfile/web/static/assets/{ColumnSelector-371637fb.css → ColumnSelector-j6sEOjo1.css} +0 -0
  193. /flowfile/web/static/assets/{CustomNode-edb9b939.css → CustomNode-VPlajG0j.css} +0 -0
  194. /flowfile/web/static/assets/{DatabaseConnectionSettings-c20a1e16.css → DatabaseConnectionSettings-B78hXYgu.css} +0 -0
  195. /flowfile/web/static/assets/{DatabaseView-6655afd6.css → DatabaseView-B-_adk1s.css} +0 -0
  196. /flowfile/web/static/assets/{DocumentationView-9ea6e871.css → DocumentationView-CL7iipFL.css} +0 -0
  197. /flowfile/web/static/assets/{ExploreData-10c5acc8.css → ExploreData-DHjv0Plr.css} +0 -0
  198. /flowfile/web/static/assets/{LoginView-d325d632.css → LoginView-DN1BXY3e.css} +0 -0
  199. /flowfile/web/static/assets/{PivotValidation-0e905b1a.css → PivotValidation-DK-FARWe.css} +0 -0
  200. /flowfile/web/static/assets/{PivotValidation-41b57ad6.css → PivotValidation-FUa9F47u.css} +0 -0
  201. /flowfile/web/static/assets/{PolarsCode-2b1f1f23.css → PolarsCode-G-gRSrSc.css} +0 -0
  202. /flowfile/web/static/assets/{SQLQueryComponent-edb90b98.css → SQLQueryComponent-oAbWw0r-.css} +0 -0
  203. /flowfile/web/static/assets/{SecretSelector-6329f743.css → SecretSelector-CJSadIZx.css} +0 -0
  204. /flowfile/web/static/assets/{SecretsView-aa291340.css → SecretsView-DbzIRAba.css} +0 -0
  205. /flowfile/web/static/assets/{SettingsSection-8f980839.css → SettingsSection-BGcJnH6q.css} +0 -0
  206. /flowfile/web/static/assets/{SettingsSection-07fbbc39.css → SettingsSection-DDWn_EGW.css} +0 -0
  207. /flowfile/web/static/assets/{SetupView-ec26f76a.css → SetupView-CI1nd-5Z.css} +0 -0
  208. /flowfile/web/static/assets/{SliderInput-f2e4f23c.css → SliderInput-BRk-q_Dk.css} +0 -0
  209. /flowfile/web/static/assets/{UnavailableFields-394a1f78.css → UnavailableFields-DRKDImKe.css} +0 -0
  210. /flowfile/web/static/assets/{Unique-2b705521.css → Unique-Absb0aON.css} +0 -0
  211. /flowfile/web/static/assets/{UnpivotValidation-d5ca3b7b.css → UnpivotValidation-DSBkFgS-.css} +0 -0
  212. /flowfile/web/static/assets/{airbyte-292aa232.png → airbyte-W0xvIXwZ.png} +0 -0
  213. /flowfile/web/static/assets/{cloud_storage_reader-aa1415d6.png → cloud_storage_reader-3GpSCk90.png} +0 -0
  214. /flowfile/web/static/assets/{cross_join-d30c0290.png → cross_join-B0qpgYoV.png} +0 -0
  215. /flowfile/web/static/assets/{dropDown-1d6acbd9.css → dropDown-CE0VF5_P.css} +0 -0
  216. /flowfile/web/static/assets/{explore_data-8a0a2861.png → explore_data-tX6olPPL.png} +0 -0
  217. /flowfile/web/static/assets/{fa-brands-400-808443ae.ttf → fa-brands-400-D1LuMI3I.ttf} +0 -0
  218. /flowfile/web/static/assets/{fa-brands-400-d7236a19.woff2 → fa-brands-400-D_cYUPeE.woff2} +0 -0
  219. /flowfile/web/static/assets/{fa-regular-400-e3456d12.woff2 → fa-regular-400-BjRzuEpd.woff2} +0 -0
  220. /flowfile/web/static/assets/{fa-regular-400-54cf6086.ttf → fa-regular-400-DZaxPHgR.ttf} +0 -0
  221. /flowfile/web/static/assets/{fa-solid-900-aa759986.woff2 → fa-solid-900-CTAAxXor.woff2} +0 -0
  222. /flowfile/web/static/assets/{fa-solid-900-d2f05935.ttf → fa-solid-900-D0aA9rwL.ttf} +0 -0
  223. /flowfile/web/static/assets/{fa-v4compatibility-0ce9033c.woff2 → fa-v4compatibility-C9RhG_FT.woff2} +0 -0
  224. /flowfile/web/static/assets/{fa-v4compatibility-30f6abf6.ttf → fa-v4compatibility-CCth-dXg.ttf} +0 -0
  225. /flowfile/web/static/assets/{filter-d7708bda.png → filter-WRdZyUOw.png} +0 -0
  226. /flowfile/web/static/assets/{formula-eeeb1611.png → formula-CgM7uHVI.png} +0 -0
  227. /flowfile/web/static/assets/{fullEditor-fe9f7e18.css → fullEditor-CmDI7T9F.css} +0 -0
  228. /flowfile/web/static/assets/{fuzzy_match-40c161b2.png → fuzzy_match-Yon3k5Tc.png} +0 -0
  229. /flowfile/web/static/assets/{graph_solver-8b7888b8.png → graph_solver-BlMrBttD.png} +0 -0
  230. /flowfile/web/static/assets/{group_by-80561fc3.png → group_by-Gici0CSS.png} +0 -0
  231. /flowfile/web/static/assets/{input_data-ab2eb678.png → input_data-BRdGecLc.png} +0 -0
  232. /flowfile/web/static/assets/{join-349043ae.png → join-BITWRu73.png} +0 -0
  233. /flowfile/web/static/assets/{manual_input-ae98f31d.png → manual_input-CFvo_EUS.png} +0 -0
  234. /flowfile/web/static/assets/{old_join-5d0eb604.png → old_join-B9bkpPqv.png} +0 -0
  235. /flowfile/web/static/assets/{output-06ec0371.png → output-Dp7-ZpC4.png} +0 -0
  236. /flowfile/web/static/assets/{outputExcel-f5d272b2.css → outputExcel-CKgRe2iT.css} +0 -0
  237. /flowfile/web/static/assets/{outputParquet-54597c3c.css → outputParquet-d7j407cK.css} +0 -0
  238. /flowfile/web/static/assets/{pivot-9660df51.png → pivot-DSxKhNlD.png} +0 -0
  239. /flowfile/web/static/assets/{polars_code-05ce5dc6.png → polars_code-DxiztZ1c.png} +0 -0
  240. /flowfile/web/static/assets/{readCsv-3bfac4c3.css → readCsv-BG-1Jilp.css} +0 -0
  241. /flowfile/web/static/assets/{readExcel-3db6b763.css → readExcel-DBQXKPtC.css} +0 -0
  242. /flowfile/web/static/assets/{record_count-dab44eb5.png → record_count-DCeaLtpS.png} +0 -0
  243. /flowfile/web/static/assets/{record_id-0b15856b.png → record_id-FeUjyIFh.png} +0 -0
  244. /flowfile/web/static/assets/{sample-693a88b5.png → sample-DeqfRiB-.png} +0 -0
  245. /flowfile/web/static/assets/{select-b0d0437a.png → select-D4JjbdjS.png} +0 -0
  246. /flowfile/web/static/assets/{selectDynamic-f2fb394f.css → selectDynamic-CjeTPUUo.css} +0 -0
  247. /flowfile/web/static/assets/{sort-2aa579f0.png → sort-DGwUG9WS.png} +0 -0
  248. /flowfile/web/static/assets/{summarize-2a099231.png → summarize-DFaNHpfp.png} +0 -0
  249. /flowfile/web/static/assets/{text_to_rows-859b29ea.png → text_to_rows-BdiAewrN.png} +0 -0
  250. /flowfile/web/static/assets/{union-2d8609f4.png → union-DCK-LSMq.png} +0 -0
  251. /flowfile/web/static/assets/{unique-1958b98a.png → unique-CdP3zZIq.png} +0 -0
  252. /flowfile/web/static/assets/{unpivot-d3cb4b5b.png → unpivot-CHttrEt8.png} +0 -0
  253. /flowfile/web/static/assets/{user-defined-icon-0ae16c90.png → user-defined-icon-BcIp2Vzo.png} +0 -0
  254. /flowfile/web/static/assets/{view-7a0f0be1.png → view-DUSRwjvq.png} +0 -0
  255. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/entry_points.txt +0 -0
  256. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -184,7 +184,9 @@ class SelectInput(BaseModel):
184
184
  result["new_name"] = self.new_name
185
185
  if not self.keep:
186
186
  result["keep"] = self.keep
187
- if self.data_type_change and self.data_type:
187
+ # Always include data_type if it's set, not just when data_type_change is True
188
+ # This ensures undo/redo snapshots preserve the data_type field
189
+ if self.data_type:
188
190
  result["data_type"] = self.data_type
189
191
  return result
190
192
 
@@ -193,22 +195,42 @@ class SelectInput(BaseModel):
193
195
  """Load from slim YAML format."""
194
196
  old_name = data["old_name"]
195
197
  new_name = data.get("new_name", old_name)
198
+ data_type = data.get("data_type")
199
+ # is_altered should be True if either name was changed OR data_type was explicitly set
200
+ # This ensures updateNodeSelect in the frontend won't overwrite user-specified data_type
201
+ is_altered = (old_name != new_name) or (data_type is not None)
196
202
  return cls(
197
203
  old_name=old_name,
198
204
  new_name=new_name,
199
205
  keep=data.get("keep", True),
200
- data_type=data.get("data_type"),
201
- data_type_change=data.get("data_type") is not None,
202
- is_altered=old_name != new_name,
206
+ data_type=data_type,
207
+ data_type_change=data_type is not None,
208
+ is_altered=is_altered,
203
209
  )
204
210
 
211
+ @model_validator(mode="before")
212
+ @classmethod
213
+ def infer_data_type_change(cls, data):
214
+ """Infer data_type_change when loading from YAML.
215
+
216
+ When data_type is present but data_type_change is not explicitly set,
217
+ infer that the user explicitly set the data_type (e.g., when loading from YAML).
218
+ This ensures is_altered will be set correctly in the after validator.
219
+ """
220
+ if isinstance(data, dict):
221
+ if data.get("data_type") is not None and "data_type_change" not in data:
222
+ data["data_type_change"] = True
223
+ return data
224
+
205
225
  @model_validator(mode="after")
206
226
  def set_default_new_name(self):
207
- """If new_name is None, default it to old_name."""
227
+ """If new_name is None, default it to old_name. Also set is_altered if needed."""
208
228
  if self.new_name is None:
209
229
  self.new_name = self.old_name
210
230
  if self.old_name != self.new_name:
211
231
  self.is_altered = True
232
+ if self.data_type_change:
233
+ self.is_altered = True
212
234
  return self
213
235
 
214
236
  def __hash__(self):
@@ -23,6 +23,19 @@ class SelectInputYaml(TypedDict, total=False):
23
23
  data_type: str
24
24
 
25
25
 
26
+ class OutputFieldInfoYaml(TypedDict, total=False):
27
+ name: str
28
+ data_type: str
29
+ default_value: str
30
+
31
+
32
+ class OutputFieldConfigYaml(TypedDict, total=False):
33
+ enabled: bool
34
+ validation_mode_behavior: str # "add_missing", "raise_on_missing", or "select_only"
35
+ fields: list[OutputFieldInfoYaml]
36
+ validate_data_types: bool # Enable data type validation
37
+
38
+
26
39
  class JoinInputsYaml(TypedDict):
27
40
  select: list[SelectInputYaml]
28
41
 
@@ -75,14 +88,15 @@ class OutputSettingsYaml(TypedDict, total=False):
75
88
  table_settings: dict
76
89
 
77
90
 
78
- class NodeSelectYaml(TypedDict):
91
+ class NodeSelectYaml(TypedDict, total=False):
79
92
  cache_results: bool
80
93
  keep_missing: bool
81
94
  select_input: list[SelectInputYaml]
82
95
  sorted_by: str
96
+ output_field_config: OutputFieldConfigYaml
83
97
 
84
98
 
85
- class NodeJoinYaml(TypedDict):
99
+ class NodeJoinYaml(TypedDict, total=False):
86
100
  cache_results: bool
87
101
  auto_generate_selection: bool
88
102
  verify_integrity: bool
@@ -90,9 +104,10 @@ class NodeJoinYaml(TypedDict):
90
104
  auto_keep_all: bool
91
105
  auto_keep_right: bool
92
106
  auto_keep_left: bool
107
+ output_field_config: OutputFieldConfigYaml
93
108
 
94
109
 
95
- class NodeCrossJoinYaml(TypedDict):
110
+ class NodeCrossJoinYaml(TypedDict, total=False):
96
111
  cache_results: bool
97
112
  auto_generate_selection: bool
98
113
  verify_integrity: bool
@@ -100,9 +115,10 @@ class NodeCrossJoinYaml(TypedDict):
100
115
  auto_keep_all: bool
101
116
  auto_keep_right: bool
102
117
  auto_keep_left: bool
118
+ output_field_config: OutputFieldConfigYaml
103
119
 
104
120
 
105
- class NodeFuzzyMatchYaml(TypedDict):
121
+ class NodeFuzzyMatchYaml(TypedDict, total=False):
106
122
  cache_results: bool
107
123
  auto_generate_selection: bool
108
124
  verify_integrity: bool
@@ -110,8 +126,10 @@ class NodeFuzzyMatchYaml(TypedDict):
110
126
  auto_keep_all: bool
111
127
  auto_keep_right: bool
112
128
  auto_keep_left: bool
129
+ output_field_config: OutputFieldConfigYaml
113
130
 
114
131
 
115
- class NodeOutputYaml(TypedDict):
132
+ class NodeOutputYaml(TypedDict, total=False):
116
133
  cache_results: bool
117
134
  output_settings: OutputSettingsYaml
135
+ output_field_config: OutputFieldConfigYaml
@@ -4,10 +4,9 @@ from typing import TypeVar
4
4
 
5
5
  import polars as pl
6
6
 
7
+ from flowfile_frame.callable_utils import process_callable_args
7
8
  from flowfile_frame.config import logger
8
- from flowfile_frame.utils import _get_function_source
9
9
 
10
- T = TypeVar("T")
11
10
  ExprT = TypeVar("ExprT", bound="Expr")
12
11
  PASSTHROUGH_METHODS = {"map_elements", "map_batches"}
13
12
 
@@ -36,46 +35,7 @@ def create_expr_method_wrapper(method_name: str, original_method: Callable) -> C
36
35
  if self.expr is None:
37
36
  raise ValueError(f"Cannot call '{method_name}' on Expr with no underlying polars expression.")
38
37
 
39
- # Collect function sources and build representations
40
- function_sources = []
41
- args_representations = []
42
- kwargs_representations = []
43
-
44
- # Process positional arguments
45
- for arg in args:
46
- if callable(arg) and not isinstance(arg, type):
47
- # Try to get function source
48
- try:
49
- source, is_module_level = _get_function_source(arg)
50
- if source and hasattr(arg, "__name__") and arg.__name__ != "<lambda>":
51
- function_sources.append(source)
52
- # Use the function name in the representation
53
- args_representations.append(arg.__name__)
54
- else:
55
- # Fallback to repr if we can't get the source
56
- args_representations.append(repr(arg))
57
- except:
58
- args_representations.append(repr(arg))
59
- else:
60
- args_representations.append(repr(arg))
61
-
62
- # Process keyword arguments
63
- for key, value in kwargs.items():
64
- if callable(value) and not isinstance(value, type):
65
- # Try to get function source
66
- try:
67
- source, is_module_level = _get_function_source(value)
68
- if source and hasattr(value, "__name__") and value.__name__ != "<lambda>":
69
- function_sources.append(source)
70
- # Use the function name in the representation
71
- kwargs_representations.append(f"{key}={value.__name__}")
72
- else:
73
- # Fallback to repr if we can't get the source
74
- kwargs_representations.append(f"{key}={repr(value)}")
75
- except:
76
- kwargs_representations.append(f"{key}={repr(value)}")
77
- else:
78
- kwargs_representations.append(f"{key}={repr(value)}")
38
+ processed = process_callable_args(args, kwargs)
79
39
 
80
40
  # Call the method on the underlying polars expression
81
41
  try:
@@ -84,22 +44,6 @@ def create_expr_method_wrapper(method_name: str, original_method: Callable) -> C
84
44
  logger.debug(f"Warning: Error in {method_name}() call: {e}")
85
45
  result_expr = None
86
46
 
87
- # Format arguments for repr string
88
- args_repr = ", ".join(args_representations)
89
- kwargs_repr = ", ".join(kwargs_representations)
90
-
91
- if args_repr and kwargs_repr:
92
- params_repr = f"{args_repr}, {kwargs_repr}"
93
- elif args_repr:
94
- params_repr = args_repr
95
- elif kwargs_repr:
96
- params_repr = kwargs_repr
97
- else:
98
- params_repr = ""
99
-
100
- # Create the repr string for this method call
101
- new_repr = f"{self._repr_str}.{method_name}({params_repr})"
102
-
103
47
  # Methods that typically change the aggregation status or complexity
104
48
  agg_methods = {
105
49
  "sum",
@@ -167,7 +111,7 @@ def create_expr_method_wrapper(method_name: str, original_method: Callable) -> C
167
111
  result_expr=result_expr,
168
112
  is_complex=is_complex,
169
113
  method_name=method_name,
170
- _function_sources=function_sources, # Pass function sources
114
+ _function_sources=processed.function_sources,
171
115
  )
172
116
 
173
117
  # Set the agg_func if needed
@@ -220,76 +164,23 @@ def add_expr_methods(cls: type[ExprT]) -> type[ExprT]:
220
164
  f"Cannot call '{method_name}' on Expr with no underlying polars expression."
221
165
  )
222
166
 
223
- # Collect function sources and build representations
224
- function_sources = []
225
- args_representations = []
226
- kwargs_representations = []
227
- convertable_to_code = True
228
-
229
- # Process positional arguments
230
- for i, arg in enumerate(args):
231
- if callable(arg) and not isinstance(arg, type):
232
- # Try to get function source
233
- try:
234
- source, is_module_level = _get_function_source(arg)
235
- if source and hasattr(arg, "__name__") and arg.__name__ != "<lambda>":
236
- function_sources.append(source)
237
- # Use the function name in the representation
238
- args_representations.append(arg.__name__)
239
- arg.__repr__ = lambda: arg.__name__
240
-
241
- else:
242
- # Lambda or unnamed function - not convertible
243
- logger.warning(
244
- f"Warning: Using anonymous functions in {method_name} is not convertable to UI code"
245
- )
246
- logger.warning(
247
- "Consider using defined functions (def abc(a, b, c): return ...), "
248
- "In a separate script"
249
- )
250
- convertable_to_code = False
251
- args_representations.append(repr(arg))
252
- except:
253
- args_representations.append(repr(arg))
254
- else:
255
- args_representations.append(repr(arg))
256
-
257
- # Process keyword arguments
258
- for key, value in kwargs.items():
259
- if callable(value) and not isinstance(value, type):
260
- # Try to get function source
261
- try:
262
- source, is_module_level = _get_function_source(value)
263
- if source and hasattr(value, "__name__") and value.__name__ != "<lambda>":
264
- function_sources.append(source)
265
- # Use the function name in the representation
266
- kwargs_representations.append(f"{key}={value.__name__}")
267
- else:
268
- # Lambda or unnamed function - not convertible
269
- convertable_to_code = False
270
- kwargs_representations.append(f"{key}={repr(value)}")
271
- except:
272
- kwargs_representations.append(f"{key}={repr(value)}")
273
- else:
274
- kwargs_representations.append(f"{key}={repr(value)}")
167
+ processed = process_callable_args(args, kwargs)
168
+ convertable_to_code = processed.all_resolved
169
+
170
+ if not convertable_to_code:
171
+ logger.warning(
172
+ f"Warning: Using anonymous functions in {method_name} is not convertable to UI code"
173
+ )
174
+ logger.warning(
175
+ "Consider using defined functions (def abc(a, b, c): return ...), "
176
+ "In a separate script"
177
+ )
275
178
 
276
179
  # Call the underlying polars method
277
180
  result_expr = getattr(self.expr, method_name)(*args, **kwargs)
278
- # Build parameter string
279
- args_repr = ", ".join(args_representations)
280
- kwargs_repr = ", ".join(kwargs_representations)
281
-
282
- if args_repr and kwargs_repr:
283
- params_repr = f"{args_repr}, {kwargs_repr}"
284
- elif args_repr:
285
- params_repr = args_repr
286
- elif kwargs_repr:
287
- params_repr = kwargs_repr
288
- else:
289
- params_repr = ""
181
+
290
182
  # Create a representation string
291
- new_repr = f"{self._repr_str}.{method_name}({params_repr})"
292
- # self._repr_str = new_repr
183
+ new_repr = f"{self._repr_str}.{method_name}({processed.params_repr})"
293
184
  # Return a new expression with the convertable_to_code flag set appropriately
294
185
  result = self._create_next_expr(
295
186
  *args,
@@ -297,7 +188,8 @@ def add_expr_methods(cls: type[ExprT]) -> type[ExprT]:
297
188
  result_expr=result_expr,
298
189
  is_complex=True,
299
190
  convertable_to_code=convertable_to_code,
300
- _function_sources=function_sources, # Pass function sources
191
+ _function_sources=processed.function_sources,
192
+ _repr_override=new_repr,
301
193
  **kwargs,
302
194
  )
303
195
  return result
@@ -0,0 +1,261 @@
1
+ """
2
+ Utilities for resolving callable source code (lambdas and named functions)
3
+ and processing callable arguments for code generation.
4
+
5
+ This module centralizes the logic for extracting function definitions so
6
+ that FlowFrame graph nodes store human-readable code instead of serialized
7
+ LazyFrame blobs.
8
+ """
9
+
10
+ import ast
11
+ import inspect
12
+ import textwrap
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Low-level extraction helpers
19
+ # ---------------------------------------------------------------------------
20
+
21
+
22
+ def _get_function_source(func) -> tuple[str | None, bool]:
23
+ """
24
+ Get the source code of a named function if possible.
25
+
26
+ Returns:
27
+ tuple: (source_code, is_module_level)
28
+ """
29
+ try:
30
+ source = inspect.getsource(func)
31
+
32
+ if func.__name__ == "<lambda>":
33
+ return None, False
34
+
35
+ is_module_level = func.__code__.co_flags & 0x10 == 0
36
+ source = textwrap.dedent(source)
37
+ return source, is_module_level
38
+ except (OSError, TypeError):
39
+ return None, False
40
+
41
+
42
+ def _is_safely_representable(value: Any) -> bool:
43
+ """Check if a value can be safely round-tripped through repr()."""
44
+ if isinstance(value, (int, float, bool, str, bytes, type(None))):
45
+ return True
46
+ if isinstance(value, (list, tuple)):
47
+ return all(_is_safely_representable(item) for item in value)
48
+ if isinstance(value, dict):
49
+ return all(
50
+ _is_safely_representable(k) and _is_safely_representable(v)
51
+ for k, v in value.items()
52
+ )
53
+ if isinstance(value, set):
54
+ return all(_is_safely_representable(item) for item in value)
55
+ return False
56
+
57
+
58
+ def _extract_lambda_source(func) -> tuple[str | None, str | None]:
59
+ """
60
+ Extract a lambda's source code and convert it to a named function definition.
61
+
62
+ Uses inspect.getsource() + AST parsing to find the lambda's argument list
63
+ and body, then generates a named function definition. Closure variables
64
+ are captured as constant assignments so the generated code is self-contained.
65
+
66
+ Returns:
67
+ (function_definition_source, function_name) or (None, None) on failure.
68
+ """
69
+ try:
70
+ source = inspect.getsource(func)
71
+ except (OSError, TypeError):
72
+ return None, None
73
+
74
+ source = textwrap.dedent(source).strip()
75
+
76
+ try:
77
+ tree = ast.parse(source)
78
+ except SyntaxError:
79
+ return None, None
80
+
81
+ lambdas = [node for node in ast.walk(tree) if isinstance(node, ast.Lambda)]
82
+ if not lambdas:
83
+ return None, None
84
+
85
+ # Match the lambda to our function based on argument names
86
+ expected_args = list(func.__code__.co_varnames[: func.__code__.co_argcount])
87
+ matched_lambda = None
88
+ for lambda_node in lambdas:
89
+ node_args = [arg.arg for arg in lambda_node.args.args]
90
+ if node_args == expected_args:
91
+ matched_lambda = lambda_node
92
+ break
93
+
94
+ if matched_lambda is None:
95
+ matched_lambda = lambdas[0]
96
+
97
+ func_name = f"_lambda_fn_{abs(hash(func.__code__)) % 100000}"
98
+
99
+ args_str = ast.unparse(matched_lambda.args)
100
+ body_str = ast.unparse(matched_lambda.body)
101
+
102
+ # Capture closure variables
103
+ closure_defs: list[str] = []
104
+ if func.__code__.co_freevars and func.__closure__:
105
+ for var_name, cell in zip(func.__code__.co_freevars, func.__closure__):
106
+ try:
107
+ value = cell.cell_contents
108
+ except ValueError:
109
+ return None, None
110
+
111
+ if _is_safely_representable(value):
112
+ closure_defs.append(f"{var_name} = {repr(value)}")
113
+ elif callable(value) and hasattr(value, "__name__") and value.__name__ != "<lambda>":
114
+ # Closure variable is a named function — extract its source
115
+ source, _ = _get_function_source(value)
116
+ if source:
117
+ closure_defs.append(source)
118
+ else:
119
+ return None, None
120
+ elif callable(value) and hasattr(value, "__name__") and value.__name__ == "<lambda>":
121
+ # Closure variable is itself a lambda — recurse
122
+ inner_def, inner_name = _extract_lambda_source(value)
123
+ if inner_def and inner_name:
124
+ # Assign the generated function to the variable name used in the outer lambda
125
+ closure_defs.append(inner_def)
126
+ closure_defs.append(f"{var_name} = {inner_name}")
127
+ else:
128
+ return None, None
129
+ else:
130
+ # Cannot safely serialize this closure variable
131
+ return None, None
132
+
133
+ lines: list[str] = []
134
+ if closure_defs:
135
+ lines.extend(closure_defs)
136
+ lines.append("")
137
+ lines.append(f"def {func_name}({args_str}):")
138
+ lines.append(f" return {body_str}")
139
+
140
+ return "\n".join(lines), func_name
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # High-level resolution
145
+ # ---------------------------------------------------------------------------
146
+
147
+
148
+ @dataclass
149
+ class ResolvedCallable:
150
+ """Result of resolving a single callable for code generation."""
151
+
152
+ source: str | None
153
+ """Function definition source code, or ``None`` if extraction failed."""
154
+
155
+ name: str
156
+ """Name to use in the generated code (function name or ``repr(func)``)."""
157
+
158
+ resolved: bool
159
+ """Whether source code was successfully extracted."""
160
+
161
+
162
+ def resolve_callable(func: Any) -> ResolvedCallable:
163
+ """
164
+ Resolve a callable (lambda or named function) to its source code.
165
+
166
+ * For lambdas: attempts AST extraction via ``_extract_lambda_source``.
167
+ * For named functions: uses ``_get_function_source``.
168
+ * Falls back to ``repr(func)`` if extraction fails.
169
+ """
170
+ if hasattr(func, "__name__") and func.__name__ == "<lambda>":
171
+ func_def, func_name = _extract_lambda_source(func)
172
+ if func_def and func_name:
173
+ return ResolvedCallable(source=func_def, name=func_name, resolved=True)
174
+ return ResolvedCallable(source=None, name=repr(func), resolved=False)
175
+
176
+ if hasattr(func, "__name__"):
177
+ try:
178
+ source, _ = _get_function_source(func)
179
+ except Exception:
180
+ source = None
181
+ if source:
182
+ return ResolvedCallable(source=source, name=func.__name__, resolved=True)
183
+ # Named function but source unavailable (e.g. built-in) — still use its name
184
+ return ResolvedCallable(source=None, name=func.__name__, resolved=True)
185
+
186
+ return ResolvedCallable(source=None, name=repr(func), resolved=False)
187
+
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Batch argument processing
191
+ # ---------------------------------------------------------------------------
192
+
193
+
194
+ @dataclass
195
+ class ProcessedArgs:
196
+ """Result of processing a function's args and kwargs for code generation."""
197
+
198
+ args_reprs: list[str] = field(default_factory=list)
199
+ """String representations for each positional argument."""
200
+
201
+ kwargs_reprs: list[str] = field(default_factory=list)
202
+ """String representations for each keyword argument (``key=value``)."""
203
+
204
+ function_sources: list[str] = field(default_factory=list)
205
+ """Collected function definition source strings."""
206
+
207
+ all_resolved: bool = True
208
+ """False if any callable could not be resolved to source code."""
209
+
210
+ @property
211
+ def params_repr(self) -> str:
212
+ """Join args and kwargs into a single comma-separated parameter string."""
213
+ args_str = ", ".join(self.args_reprs)
214
+ kwargs_str = ", ".join(self.kwargs_reprs)
215
+ if args_str and kwargs_str:
216
+ return f"{args_str}, {kwargs_str}"
217
+ return args_str or kwargs_str
218
+
219
+
220
+ def process_callable_args(args: tuple, kwargs: dict) -> ProcessedArgs:
221
+ """
222
+ Process positional and keyword arguments, resolving any callables to source code.
223
+
224
+ Non-callable arguments are converted via ``repr()``.
225
+
226
+ Returns a :class:`ProcessedArgs` collecting the string representations,
227
+ extracted function sources, and an ``all_resolved`` flag.
228
+ """
229
+ result = ProcessedArgs()
230
+
231
+ for arg in args:
232
+ if callable(arg) and not isinstance(arg, type):
233
+ try:
234
+ resolved = resolve_callable(arg)
235
+ result.args_reprs.append(resolved.name)
236
+ if resolved.source:
237
+ result.function_sources.append(resolved.source)
238
+ if not resolved.resolved:
239
+ result.all_resolved = False
240
+ except Exception:
241
+ result.args_reprs.append(repr(arg))
242
+ result.all_resolved = False
243
+ else:
244
+ result.args_reprs.append(repr(arg))
245
+
246
+ for key, value in kwargs.items():
247
+ if callable(value) and not isinstance(value, type):
248
+ try:
249
+ resolved = resolve_callable(value)
250
+ result.kwargs_reprs.append(f"{key}={resolved.name}")
251
+ if resolved.source:
252
+ result.function_sources.append(resolved.source)
253
+ if not resolved.resolved:
254
+ result.all_resolved = False
255
+ except Exception:
256
+ result.kwargs_reprs.append(f"{key}={repr(value)}")
257
+ result.all_resolved = False
258
+ else:
259
+ result.kwargs_reprs.append(f"{key}={repr(value)}")
260
+
261
+ return result
@@ -186,7 +186,6 @@ def del_database_connection(connection_name: str) -> bool:
186
186
  Returns:
187
187
  True if the connection was deleted, False if it didn't exist.
188
188
  """
189
- from flowfile_core.database.models import DatabaseConnection as DBConnectionModel
190
189
  from flowfile_core.database.models import Secret
191
190
 
192
191
  user_id = get_current_user_id()
flowfile_frame/expr.py CHANGED
@@ -485,13 +485,17 @@ class Expr:
485
485
  convertable_to_code: bool = None,
486
486
  is_complex: bool,
487
487
  _function_sources: list[str] | None = None,
488
+ _repr_override: str | None = None,
488
489
  **kwargs,
489
490
  ) -> Expr:
490
491
  """Creates a new Expr instance, appending method call to repr string."""
491
- # Filter out _function_sources from kwargs to avoid passing it to _repr_args
492
- filtered_kwargs = {k: v for k, v in kwargs.items() if k != "_function_sources"}
493
- args_repr = _repr_args(*args, **filtered_kwargs)
494
- new_repr = f"{self._repr_str}.{method_name}({args_repr})"
492
+ if _repr_override is not None:
493
+ new_repr = _repr_override
494
+ else:
495
+ # Filter out _function_sources from kwargs to avoid passing it to _repr_args
496
+ filtered_kwargs = {k: v for k, v in kwargs.items() if k != "_function_sources"}
497
+ args_repr = _repr_args(*args, **filtered_kwargs)
498
+ new_repr = f"{self._repr_str}.{method_name}({args_repr})"
495
499
 
496
500
  if convertable_to_code is None:
497
501
  convertable_to_code = self.convertable_to_code