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.
- flowfile/api.py +8 -6
- flowfile/web/static/assets/{AdminView-c2c7942b.js → AdminView-C4K1DdHI.js} +28 -33
- flowfile/web/static/assets/{CloudConnectionView-7a3042c6.js → CloudConnectionView-BZbPvPUL.js} +39 -50
- flowfile/web/static/assets/{CloudStorageReader-24c54524.css → CloudStorageReader-BDByiqPI.css} +25 -25
- flowfile/web/static/assets/{CloudStorageReader-709c4037.js → CloudStorageReader-DLVukNJ7.js} +30 -35
- flowfile/web/static/assets/{CloudStorageWriter-604c51a8.js → CloudStorageWriter-Bfi-C1QW.js} +32 -37
- flowfile/web/static/assets/{CloudStorageWriter-60547855.css → CloudStorageWriter-y8jL8yjG.css} +24 -24
- flowfile/web/static/assets/{ColumnActionInput-d63d6746.js → ColumnActionInput-BpiCApw9.js} +7 -12
- flowfile/web/static/assets/{ColumnSelector-0c8cd1cd.js → ColumnSelector-CEAwedI7.js} +1 -2
- flowfile/web/static/assets/ContextMenu-CdojQu0w.js +9 -0
- flowfile/web/static/assets/ContextMenu-D12mhsy1.js +9 -0
- flowfile/web/static/assets/ContextMenu-EWUR98va.js +9 -0
- 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
- flowfile/web/static/assets/{CrossJoin-38e5b99a.js → CrossJoin-BOFfxkJO.js} +19 -18
- flowfile/web/static/assets/{CrossJoin-71b4cc10.css → CrossJoin-Cmbyt9im.css} +18 -18
- flowfile/web/static/assets/{CustomNode-76e8f3f5.js → CustomNode-Bhpezobq.js} +12 -17
- flowfile/web/static/assets/{DatabaseConnectionSettings-38155669.js → DatabaseConnectionSettings-Dw3bSJKB.js} +10 -11
- flowfile/web/static/assets/{DatabaseReader-5bf8c75b.css → DatabaseReader-D6pUNUCs.css} +21 -21
- flowfile/web/static/assets/{DatabaseReader-2e549c8f.js → DatabaseReader-m87ghlw0.js} +36 -34
- flowfile/web/static/assets/{DatabaseView-dc877c29.js → DatabaseView-CisSAtpe.js} +30 -38
- flowfile/web/static/assets/{DatabaseWriter-ffb91864.js → DatabaseWriter-Bbj9JLdL.js} +33 -35
- flowfile/web/static/assets/{DatabaseWriter-bdcf2c8b.css → DatabaseWriter-RBqdFLj8.css} +17 -17
- flowfile/web/static/assets/{DesignerView-a4466dab.js → DesignerView-DemDevTQ.js} +1752 -2054
- flowfile/web/static/assets/{DesignerView-71d4e9a1.css → DesignerView-Dm6OzlIc.css} +209 -168
- flowfile/web/static/assets/{DocumentationView-979afc84.js → DocumentationView-BrC1ZR3H.js} +3 -4
- flowfile/web/static/assets/{ExploreData-e4b92aaf.js → ExploreData-BMKcDuRb.js} +8 -10
- flowfile/web/static/assets/{ExternalSource-d08e7227.js → ExternalSource-BXrNNS-f.js} +40 -42
- flowfile/web/static/assets/{ExternalSource-7ac7373f.css → ExternalSource-NB6WVl5R.css} +14 -14
- flowfile/web/static/assets/{Filter-7add806d.js → Filter-C2MjsN6P.js} +36 -33
- flowfile/web/static/assets/{Filter-7494ea97.css → Filter-DCMGGuGC.css} +9 -9
- flowfile/web/static/assets/{Formula-53d58c43.css → Formula-BYafbDj8.css} +4 -4
- flowfile/web/static/assets/{Formula-36ab24d2.js → Formula-ufuy4mVD.js} +27 -26
- flowfile/web/static/assets/{FuzzyMatch-ad6361d6.css → FuzzyMatch-BGJAwgd0.css} +42 -42
- flowfile/web/static/assets/{FuzzyMatch-cc01bb04.js → FuzzyMatch-BOHODq3h.js} +36 -38
- flowfile/web/static/assets/{GraphSolver-4fb98f3b.js → GraphSolver-B6ZzpNGO.js} +23 -21
- flowfile/web/static/assets/{GraphSolver-4b4d7db9.css → GraphSolver-DFN83sj3.css} +4 -4
- flowfile/web/static/assets/{GroupBy-b3c8f429.js → GroupBy-B9BRNcfe.js} +30 -29
- flowfile/web/static/assets/{Sort-4abb7fae.css → GroupBy-x4ooP5np.css} +1 -1
- flowfile/web/static/assets/Join-Bx_g5bZz.css +118 -0
- flowfile/web/static/assets/{Join-096b7b26.js → Join-DsBEy1IH.js} +48 -43
- flowfile/web/static/assets/{LoginView-c33a246a.js → LoginView-Ct0rhdcO.js} +1 -2
- flowfile/web/static/assets/{ManualInput-39111f19.css → ManualInput-DlZmtMdt.css} +48 -48
- flowfile/web/static/assets/{ManualInput-7307e9b1.js → ManualInput-bC4BUgnG.js} +40 -41
- flowfile/web/static/assets/{MultiSelect-14822c48.js → MultiSelect-DIQ8PuTC.js} +2 -2
- 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
- flowfile/web/static/assets/{NodeDesigner-5036c392.js → NodeDesigner-D39yzr2k.js} +178 -208
- flowfile/web/static/assets/{NodeDesigner-94cd4dd3.css → NodeDesigner-R0l6sYyY.css} +76 -76
- flowfile/web/static/assets/{NumericInput-15cf3b72.js → NumericInput-DMSX3oOr.js} +2 -2
- 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
- flowfile/web/static/assets/{Output-1f8ed42c.js → Output-D0VoXGcW.js} +26 -34
- flowfile/web/static/assets/{Output-692dd25d.css → Output-DsmglIDy.css} +5 -5
- flowfile/web/static/assets/{Pivot-0e153f4e.js → Pivot-BnMB4sEe.js} +26 -26
- flowfile/web/static/assets/{Pivot-0eda81b4.css → Pivot-qKTyWxop.css} +4 -4
- flowfile/web/static/assets/{PivotValidation-81ec2a33.js → PivotValidation-B2lWvugt.js} +7 -9
- flowfile/web/static/assets/{PivotValidation-5a4f7c79.js → PivotValidation-BPlhRjpL.js} +7 -9
- flowfile/web/static/assets/{PolarsCode-a39f15ac.js → PolarsCode-5h0tHnWR.js} +22 -20
- flowfile/web/static/assets/{PopOver-ddcfe4f6.js → PopOver-BHpt5rsj.js} +5 -9
- flowfile/web/static/assets/{PopOver-d96599db.css → PopOver-CyYM4-rV.css} +1 -1
- flowfile/web/static/assets/{Read-90f366bc.css → Read-DJxkrTb_.css} +10 -10
- flowfile/web/static/assets/Read-TsLEFh3B.js +227 -0
- flowfile/web/static/assets/{RecordCount-e9048ccd.js → RecordCount-DkVixq9v.js} +18 -17
- flowfile/web/static/assets/{RecordId-ad02521d.js → RecordId-C2UEGlCf.js} +42 -39
- flowfile/web/static/assets/{SQLQueryComponent-2eeecf0b.js → SQLQueryComponent-Dr5KMoD3.js} +2 -3
- flowfile/web/static/assets/{Sample-9a68c23d.js → Sample-Cb3eQNmd.js} +30 -30
- flowfile/web/static/assets/{SecretSelector-2429f35a.js → SecretSelector-De2L2bSx.js} +3 -4
- flowfile/web/static/assets/{SecretsView-c6afc915.js → SecretsView-CheC9BPV.js} +13 -16
- flowfile/web/static/assets/{Select-fcd002b6.js → Select-CI8TloRs.js} +41 -36
- flowfile/web/static/assets/{SettingsSection-5ce15962.js → SettingsSection-B39ulIiI.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-c6b1362c.js → SettingsSection-BiCc7S9h.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-cebb91d5.js → SettingsSection-CITK_R7o.js} +2 -3
- flowfile/web/static/assets/{SettingsSection-26fe48d4.css → SettingsSection-D2GgY-Aq.css} +4 -4
- flowfile/web/static/assets/{SetupView-2d12e01f.js → SetupView-C1aXRDvp.js} +1 -2
- flowfile/web/static/assets/{SingleSelect-b67de4eb.js → SingleSelect-Kr_hz90m.js} +2 -2
- 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
- flowfile/web/static/assets/{SliderInput-fd8134ac.js → SliderInput-CLqpCxCb.js} +1 -2
- flowfile/web/static/assets/{GroupBy-5792782d.css → Sort-BIt2kc_p.css} +1 -1
- flowfile/web/static/assets/{Sort-c005a573.js → Sort-Dnw_J6Qi.js} +25 -25
- flowfile/web/static/assets/{TextInput-1bb31dab.js → TextInput-wdlunIZC.js} +2 -2
- 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
- flowfile/web/static/assets/{TextToRows-4f363753.js → TextToRows-BhtyGWPq.js} +42 -49
- flowfile/web/static/assets/{TextToRows-12afb4f4.css → TextToRows-DivDOLDx.css} +9 -9
- flowfile/web/static/assets/{ToggleSwitch-ca0f2e5e.js → ToggleSwitch-B-6WzfFf.js} +2 -2
- 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
- flowfile/web/static/assets/{UnavailableFields-f6147968.js → UnavailableFields-Yf6XSqFB.js} +2 -3
- flowfile/web/static/assets/{Union-c65f17b7.js → Union-CwpjeKYC.js} +20 -23
- flowfile/web/static/assets/{Unpivot-b6ad6427.css → Union-DQJcpp3-.css} +6 -6
- flowfile/web/static/assets/{Unique-a1d96fb2.js → Unique-25v3urqH.js} +75 -74
- flowfile/web/static/assets/{Union-d6a8d7d5.css → Unpivot-Deqh1gtI.css} +6 -6
- flowfile/web/static/assets/{Unpivot-c2657ff3.js → Unpivot-sYcTTXrq.js} +34 -27
- flowfile/web/static/assets/{UnpivotValidation-28e29a3b.js → UnpivotValidation-C5DDEKY2.js} +5 -7
- flowfile/web/static/assets/VueGraphicWalker-B8l1_Z92.js +131 -0
- flowfile/web/static/assets/VueGraphicWalker-Da_1-3me.css +21 -0
- flowfile/web/static/assets/{api-df48ec50.js → api-C0LvF-0C.js} +1 -1
- flowfile/web/static/assets/{api-ee542cf7.js → api-DaC83EO_.js} +1 -1
- flowfile/web/static/assets/client-C8Ygr6Gb.js +42 -0
- flowfile/web/static/assets/{dropDown-7576a76a.js → dropDown-D5YXaPRR.js} +7 -12
- flowfile/web/static/assets/{fullEditor-7583bef5.js → fullEditor-BVYnWm05.js} +300 -18
- flowfile/web/static/assets/genericNodeSettings-2wAu-QKn.css +75 -0
- flowfile/web/static/assets/genericNodeSettings-BBtW_Cpz.js +590 -0
- flowfile/web/static/assets/{VueGraphicWalker-2fc3ddd4.js → graphic-walker.es-VrK6vdGE.js} +92305 -89751
- flowfile/web/static/assets/index-BCJxPfM5.js +6693 -0
- flowfile/web/static/assets/{index-057d770d.js → index-CHPMUR0d.js} +150 -170
- flowfile/web/static/assets/index-DPkoZWq8.js +32 -0
- flowfile/web/static/assets/index-DnW_KC_I.js +277 -0
- flowfile/web/static/assets/index-UFXyfirV.css +10797 -0
- flowfile/web/static/assets/index-bcuE0Z0p.js +87456 -0
- flowfile/web/static/assets/{node.types-2c15bb7e.js → node.types-Dl4gtSW9.js} +2 -2
- flowfile/web/static/assets/{outputCsv-c492b15e.js → outputCsv-BELuBiJZ.js} +1 -2
- flowfile/web/static/assets/outputCsv-CdGkv-fN.css +2581 -0
- flowfile/web/static/assets/{outputExcel-13bfa10f.js → outputExcel-D0TTNM79.js} +1 -2
- flowfile/web/static/assets/{outputParquet-9be1523a.js → outputParquet-Cz9EbRHj.js} +1 -2
- flowfile/web/static/assets/{readCsv-5a49a8c9.js → readCsv-7bd3kUMI.js} +1 -2
- flowfile/web/static/assets/{readExcel-27c30ad8.js → readExcel-Cq8CCwIv.js} +3 -4
- flowfile/web/static/assets/{readParquet-c5244ad5.css → readParquet-CRDmBrsp.css} +4 -4
- flowfile/web/static/assets/{readParquet-446bde68.js → readParquet-DjR4mRaj.js} +4 -5
- flowfile/web/static/assets/{secrets.api-34431884.js → secrets.api-C9o2KE5V.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-5754a2b1.js → selectDynamic-Bl5FVsME.js} +5 -7
- flowfile/web/static/assets/useNodeSettings-dMS9zmh_.js +69 -0
- flowfile/web/static/assets/{vue-codemirror.esm-8f46fb36.js → vue-codemirror.esm-CwaYwln0.js} +3469 -3064
- flowfile/web/static/assets/{vue-content-loader.es-808fe33a.js → vue-content-loader.es-CMoRXo7N.js} +3 -3
- flowfile/web/static/index.html +2 -3
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/METADATA +2 -1
- flowfile-0.6.1.dist-info/RECORD +417 -0
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/WHEEL +1 -1
- flowfile_core/auth/password.py +1 -0
- flowfile_core/database/init_db.py +7 -5
- flowfile_core/fileExplorer/funcs.py +2 -2
- flowfile_core/flowfile/code_generator/code_generator.py +13 -11
- flowfile_core/flowfile/filter_expressions.py +327 -0
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +61 -59
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +3 -29
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +45 -14
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +20 -3
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/streaming.py +206 -0
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +146 -24
- flowfile_core/flowfile/flow_graph.py +504 -190
- flowfile_core/flowfile/flow_node/__init__.py +32 -0
- flowfile_core/flowfile/flow_node/executor.py +404 -0
- flowfile_core/flowfile/flow_node/flow_node.py +207 -106
- flowfile_core/flowfile/flow_node/models.py +40 -0
- flowfile_core/flowfile/flow_node/output_field_config_applier.py +217 -0
- flowfile_core/flowfile/flow_node/schema_utils.py +78 -0
- flowfile_core/flowfile/flow_node/state.py +155 -0
- flowfile_core/flowfile/history_manager.py +401 -0
- flowfile_core/flowfile/manage/compatibility_enhancements.py +9 -0
- flowfile_core/flowfile/manage/io_flowfile.py +3 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +20 -4
- flowfile_core/flowfile/util/execution_orderer.py +89 -36
- flowfile_core/routes/auth.py +8 -9
- flowfile_core/routes/routes.py +320 -101
- flowfile_core/routes/user_defined_components.py +18 -16
- flowfile_core/schemas/history_schema.py +220 -0
- flowfile_core/schemas/input_schema.py +130 -6
- flowfile_core/schemas/schemas.py +9 -0
- flowfile_core/schemas/transform_schema.py +27 -5
- flowfile_core/schemas/yaml_types.py +23 -5
- flowfile_frame/adding_expr.py +18 -126
- flowfile_frame/callable_utils.py +261 -0
- flowfile_frame/database/connection_manager.py +0 -1
- flowfile_frame/expr.py +8 -4
- flowfile_frame/flow_frame.py +41 -41
- flowfile_frame/lazy.py +3 -12
- flowfile_frame/lazy_methods.py +5 -64
- flowfile_frame/utils.py +13 -32
- flowfile_worker/funcs.py +6 -4
- flowfile_worker/main.py +2 -0
- flowfile_worker/models.py +31 -11
- flowfile_worker/routes.py +60 -35
- flowfile_worker/spawner.py +7 -1
- flowfile_worker/streaming.py +335 -0
- flowfile/web/static/assets/ContextMenu-366bf1b4.js +0 -9
- flowfile/web/static/assets/ContextMenu-85cf5b44.js +0 -9
- flowfile/web/static/assets/ContextMenu-9d28ae6d.js +0 -9
- flowfile/web/static/assets/Join-28b5e18f.css +0 -109
- flowfile/web/static/assets/Read-39b63932.js +0 -222
- flowfile/web/static/assets/VueGraphicWalker-430f0b86.css +0 -6
- flowfile/web/static/assets/database_reader-ce1e55f3.svg +0 -24
- flowfile/web/static/assets/database_writer-b4ad0753.svg +0 -23
- flowfile/web/static/assets/element-icons-9c88a535.woff +0 -0
- flowfile/web/static/assets/element-icons-de5eb258.ttf +0 -0
- flowfile/web/static/assets/genericNodeSettings-0155288b.js +0 -136
- flowfile/web/static/assets/genericNodeSettings-3b2507ea.css +0 -46
- flowfile/web/static/assets/index-aeec439d.js +0 -38
- flowfile/web/static/assets/index-ca6799de.js +0 -62760
- flowfile/web/static/assets/index-d60c9dd4.css +0 -10777
- flowfile/web/static/assets/nodeInput-d478b9ac.js +0 -2
- flowfile/web/static/assets/outputCsv-cc84e09f.css +0 -2499
- flowfile-0.5.6.dist-info/RECORD +0 -407
- /flowfile/web/static/assets/{AdminView-f53bad23.css → AdminView-B2Dthl3u.css} +0 -0
- /flowfile/web/static/assets/{CloudConnectionView-cf85f943.css → CloudConnectionView-BdFYGWV7.css} +0 -0
- /flowfile/web/static/assets/{ColumnActionInput-c44b7aee.css → ColumnActionInput-dCasSIC9.css} +0 -0
- /flowfile/web/static/assets/{ColumnSelector-371637fb.css → ColumnSelector-j6sEOjo1.css} +0 -0
- /flowfile/web/static/assets/{CustomNode-edb9b939.css → CustomNode-VPlajG0j.css} +0 -0
- /flowfile/web/static/assets/{DatabaseConnectionSettings-c20a1e16.css → DatabaseConnectionSettings-B78hXYgu.css} +0 -0
- /flowfile/web/static/assets/{DatabaseView-6655afd6.css → DatabaseView-B-_adk1s.css} +0 -0
- /flowfile/web/static/assets/{DocumentationView-9ea6e871.css → DocumentationView-CL7iipFL.css} +0 -0
- /flowfile/web/static/assets/{ExploreData-10c5acc8.css → ExploreData-DHjv0Plr.css} +0 -0
- /flowfile/web/static/assets/{LoginView-d325d632.css → LoginView-DN1BXY3e.css} +0 -0
- /flowfile/web/static/assets/{PivotValidation-0e905b1a.css → PivotValidation-DK-FARWe.css} +0 -0
- /flowfile/web/static/assets/{PivotValidation-41b57ad6.css → PivotValidation-FUa9F47u.css} +0 -0
- /flowfile/web/static/assets/{PolarsCode-2b1f1f23.css → PolarsCode-G-gRSrSc.css} +0 -0
- /flowfile/web/static/assets/{SQLQueryComponent-edb90b98.css → SQLQueryComponent-oAbWw0r-.css} +0 -0
- /flowfile/web/static/assets/{SecretSelector-6329f743.css → SecretSelector-CJSadIZx.css} +0 -0
- /flowfile/web/static/assets/{SecretsView-aa291340.css → SecretsView-DbzIRAba.css} +0 -0
- /flowfile/web/static/assets/{SettingsSection-8f980839.css → SettingsSection-BGcJnH6q.css} +0 -0
- /flowfile/web/static/assets/{SettingsSection-07fbbc39.css → SettingsSection-DDWn_EGW.css} +0 -0
- /flowfile/web/static/assets/{SetupView-ec26f76a.css → SetupView-CI1nd-5Z.css} +0 -0
- /flowfile/web/static/assets/{SliderInput-f2e4f23c.css → SliderInput-BRk-q_Dk.css} +0 -0
- /flowfile/web/static/assets/{UnavailableFields-394a1f78.css → UnavailableFields-DRKDImKe.css} +0 -0
- /flowfile/web/static/assets/{Unique-2b705521.css → Unique-Absb0aON.css} +0 -0
- /flowfile/web/static/assets/{UnpivotValidation-d5ca3b7b.css → UnpivotValidation-DSBkFgS-.css} +0 -0
- /flowfile/web/static/assets/{airbyte-292aa232.png → airbyte-W0xvIXwZ.png} +0 -0
- /flowfile/web/static/assets/{cloud_storage_reader-aa1415d6.png → cloud_storage_reader-3GpSCk90.png} +0 -0
- /flowfile/web/static/assets/{cross_join-d30c0290.png → cross_join-B0qpgYoV.png} +0 -0
- /flowfile/web/static/assets/{dropDown-1d6acbd9.css → dropDown-CE0VF5_P.css} +0 -0
- /flowfile/web/static/assets/{explore_data-8a0a2861.png → explore_data-tX6olPPL.png} +0 -0
- /flowfile/web/static/assets/{fa-brands-400-808443ae.ttf → fa-brands-400-D1LuMI3I.ttf} +0 -0
- /flowfile/web/static/assets/{fa-brands-400-d7236a19.woff2 → fa-brands-400-D_cYUPeE.woff2} +0 -0
- /flowfile/web/static/assets/{fa-regular-400-e3456d12.woff2 → fa-regular-400-BjRzuEpd.woff2} +0 -0
- /flowfile/web/static/assets/{fa-regular-400-54cf6086.ttf → fa-regular-400-DZaxPHgR.ttf} +0 -0
- /flowfile/web/static/assets/{fa-solid-900-aa759986.woff2 → fa-solid-900-CTAAxXor.woff2} +0 -0
- /flowfile/web/static/assets/{fa-solid-900-d2f05935.ttf → fa-solid-900-D0aA9rwL.ttf} +0 -0
- /flowfile/web/static/assets/{fa-v4compatibility-0ce9033c.woff2 → fa-v4compatibility-C9RhG_FT.woff2} +0 -0
- /flowfile/web/static/assets/{fa-v4compatibility-30f6abf6.ttf → fa-v4compatibility-CCth-dXg.ttf} +0 -0
- /flowfile/web/static/assets/{filter-d7708bda.png → filter-WRdZyUOw.png} +0 -0
- /flowfile/web/static/assets/{formula-eeeb1611.png → formula-CgM7uHVI.png} +0 -0
- /flowfile/web/static/assets/{fullEditor-fe9f7e18.css → fullEditor-CmDI7T9F.css} +0 -0
- /flowfile/web/static/assets/{fuzzy_match-40c161b2.png → fuzzy_match-Yon3k5Tc.png} +0 -0
- /flowfile/web/static/assets/{graph_solver-8b7888b8.png → graph_solver-BlMrBttD.png} +0 -0
- /flowfile/web/static/assets/{group_by-80561fc3.png → group_by-Gici0CSS.png} +0 -0
- /flowfile/web/static/assets/{input_data-ab2eb678.png → input_data-BRdGecLc.png} +0 -0
- /flowfile/web/static/assets/{join-349043ae.png → join-BITWRu73.png} +0 -0
- /flowfile/web/static/assets/{manual_input-ae98f31d.png → manual_input-CFvo_EUS.png} +0 -0
- /flowfile/web/static/assets/{old_join-5d0eb604.png → old_join-B9bkpPqv.png} +0 -0
- /flowfile/web/static/assets/{output-06ec0371.png → output-Dp7-ZpC4.png} +0 -0
- /flowfile/web/static/assets/{outputExcel-f5d272b2.css → outputExcel-CKgRe2iT.css} +0 -0
- /flowfile/web/static/assets/{outputParquet-54597c3c.css → outputParquet-d7j407cK.css} +0 -0
- /flowfile/web/static/assets/{pivot-9660df51.png → pivot-DSxKhNlD.png} +0 -0
- /flowfile/web/static/assets/{polars_code-05ce5dc6.png → polars_code-DxiztZ1c.png} +0 -0
- /flowfile/web/static/assets/{readCsv-3bfac4c3.css → readCsv-BG-1Jilp.css} +0 -0
- /flowfile/web/static/assets/{readExcel-3db6b763.css → readExcel-DBQXKPtC.css} +0 -0
- /flowfile/web/static/assets/{record_count-dab44eb5.png → record_count-DCeaLtpS.png} +0 -0
- /flowfile/web/static/assets/{record_id-0b15856b.png → record_id-FeUjyIFh.png} +0 -0
- /flowfile/web/static/assets/{sample-693a88b5.png → sample-DeqfRiB-.png} +0 -0
- /flowfile/web/static/assets/{select-b0d0437a.png → select-D4JjbdjS.png} +0 -0
- /flowfile/web/static/assets/{selectDynamic-f2fb394f.css → selectDynamic-CjeTPUUo.css} +0 -0
- /flowfile/web/static/assets/{sort-2aa579f0.png → sort-DGwUG9WS.png} +0 -0
- /flowfile/web/static/assets/{summarize-2a099231.png → summarize-DFaNHpfp.png} +0 -0
- /flowfile/web/static/assets/{text_to_rows-859b29ea.png → text_to_rows-BdiAewrN.png} +0 -0
- /flowfile/web/static/assets/{union-2d8609f4.png → union-DCK-LSMq.png} +0 -0
- /flowfile/web/static/assets/{unique-1958b98a.png → unique-CdP3zZIq.png} +0 -0
- /flowfile/web/static/assets/{unpivot-d3cb4b5b.png → unpivot-CHttrEt8.png} +0 -0
- /flowfile/web/static/assets/{user-defined-icon-0ae16c90.png → user-defined-icon-BcIp2Vzo.png} +0 -0
- /flowfile/web/static/assets/{view-7a0f0be1.png → view-DUSRwjvq.png} +0 -0
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/entry_points.txt +0 -0
- {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
|
|
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=
|
|
201
|
-
data_type_change=
|
|
202
|
-
is_altered=
|
|
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
|
flowfile_frame/adding_expr.py
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|