keboola-cli 0.63.4__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.
- keboola_agent_cli/__init__.py +34 -0
- keboola_agent_cli/__main__.py +5 -0
- keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js +2 -0
- keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/architecture-7EHR7CIX-hNCijx_H.js +1 -0
- keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js +37 -0
- keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js +2 -0
- keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js +133 -0
- keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js +11 -0
- keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js +2 -0
- keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js +63 -0
- keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js +3 -0
- keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js +207 -0
- keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js +232 -0
- keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js +124 -0
- keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js +16 -0
- keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js +160 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js +71 -0
- keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js +2 -0
- keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js +2 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js +2 -0
- keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js +2 -0
- keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js +322 -0
- keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js +5 -0
- keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js +2 -0
- keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js +2 -0
- keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js +44 -0
- keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js +11 -0
- keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js +4 -0
- keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js +25 -0
- keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js +25 -0
- keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js +2 -0
- keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js +86 -0
- keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/eventmodeling-FCH6USID-BrJMIks8.js +1 -0
- keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js +163 -0
- keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js +293 -0
- keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/gitGraph-WXDBUCRP-COacYjo-.js +1 -0
- keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js +107 -0
- keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js +2 -0
- keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/index-CMq50kkV.css +1 -0
- keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js +118 -0
- keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/info-J43DQDTF-DdCTRIzU.js +1 -0
- keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js +3 -0
- keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js +2 -0
- keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js +71 -0
- keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js +140 -0
- keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js +90 -0
- keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js +258 -0
- keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js +2 -0
- keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js +2 -0
- keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js +5 -0
- keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js +97 -0
- keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js +2 -0
- keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/packet-YPE3B663-DLiiw_B2.js +1 -0
- keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js +2 -0
- keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/pie-LRSECV5Y-CRoO8G1g.js +1 -0
- keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js +31 -0
- keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js +8 -0
- keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/radar-GUYGQ44K-DAlLVJHm.js +1 -0
- keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js +85 -0
- keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js +2 -0
- keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js +41 -0
- keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js +163 -0
- keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js +2 -0
- keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js +2 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js +2 -0
- keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js +121 -0
- keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/treeView-BLDUP644-DbLYl23-.js +1 -0
- keboola_agent_cli/_ui_dist/assets/treemap-LRROVOQU-Bp0eGlOt.js +1 -0
- keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js +35 -0
- keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/wardley-L42UT6IY-D4yH4jqS.js +1 -0
- keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js +79 -0
- keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js.map +1 -0
- keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js +8 -0
- keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js.map +1 -0
- keboola_agent_cli/_ui_dist/index.html +50 -0
- keboola_agent_cli/ai_client.py +83 -0
- keboola_agent_cli/auto_update.py +550 -0
- keboola_agent_cli/changelog.py +1198 -0
- keboola_agent_cli/cli.py +448 -0
- keboola_agent_cli/client.py +3422 -0
- keboola_agent_cli/commands/__init__.py +0 -0
- keboola_agent_cli/commands/_data_app_git.py +343 -0
- keboola_agent_cli/commands/_helpers.py +377 -0
- keboola_agent_cli/commands/_metadata_input.py +49 -0
- keboola_agent_cli/commands/_semantic_layer_crud.py +632 -0
- keboola_agent_cli/commands/_semantic_layer_helpers.py +44 -0
- keboola_agent_cli/commands/_semantic_layer_reference_data.py +247 -0
- keboola_agent_cli/commands/agent.py +968 -0
- keboola_agent_cli/commands/branch.py +423 -0
- keboola_agent_cli/commands/changelog.py +168 -0
- keboola_agent_cli/commands/component.py +216 -0
- keboola_agent_cli/commands/config.py +2442 -0
- keboola_agent_cli/commands/context.py +1481 -0
- keboola_agent_cli/commands/data_app.py +1279 -0
- keboola_agent_cli/commands/dev_portal.py +584 -0
- keboola_agent_cli/commands/doctor.py +37 -0
- keboola_agent_cli/commands/encrypt.py +145 -0
- keboola_agent_cli/commands/feature.py +311 -0
- keboola_agent_cli/commands/flow.py +948 -0
- keboola_agent_cli/commands/http_client.py +157 -0
- keboola_agent_cli/commands/init.py +279 -0
- keboola_agent_cli/commands/job.py +661 -0
- keboola_agent_cli/commands/kai.py +301 -0
- keboola_agent_cli/commands/lineage.py +1464 -0
- keboola_agent_cli/commands/org.py +292 -0
- keboola_agent_cli/commands/permissions.py +360 -0
- keboola_agent_cli/commands/project.py +1192 -0
- keboola_agent_cli/commands/repl.py +243 -0
- keboola_agent_cli/commands/schedule.py +340 -0
- keboola_agent_cli/commands/search.py +178 -0
- keboola_agent_cli/commands/semantic_layer.py +939 -0
- keboola_agent_cli/commands/serve.py +272 -0
- keboola_agent_cli/commands/sharing.py +340 -0
- keboola_agent_cli/commands/storage.py +2630 -0
- keboola_agent_cli/commands/stream.py +266 -0
- keboola_agent_cli/commands/sync.py +1277 -0
- keboola_agent_cli/commands/tool.py +206 -0
- keboola_agent_cli/commands/version.py +186 -0
- keboola_agent_cli/commands/workspace.py +635 -0
- keboola_agent_cli/config_store.py +582 -0
- keboola_agent_cli/constants.py +528 -0
- keboola_agent_cli/data_science_client.py +342 -0
- keboola_agent_cli/dev_portal_client.py +323 -0
- keboola_agent_cli/errors.py +248 -0
- keboola_agent_cli/http_base.py +315 -0
- keboola_agent_cli/json_utils.py +126 -0
- keboola_agent_cli/lib.py +536 -0
- keboola_agent_cli/manage_client.py +324 -0
- keboola_agent_cli/metastore_client.py +214 -0
- keboola_agent_cli/models.py +427 -0
- keboola_agent_cli/output.py +1084 -0
- keboola_agent_cli/permissions.py +469 -0
- keboola_agent_cli/py.typed +3 -0
- keboola_agent_cli/result_models.py +271 -0
- keboola_agent_cli/server/__init__.py +34 -0
- keboola_agent_cli/server/agent_runner.py +1289 -0
- keboola_agent_cli/server/agents_store.py +325 -0
- keboola_agent_cli/server/app.py +764 -0
- keboola_agent_cli/server/auth.py +117 -0
- keboola_agent_cli/server/dependencies.py +149 -0
- keboola_agent_cli/server/pricing.py +303 -0
- keboola_agent_cli/server/routers/__init__.py +1 -0
- keboola_agent_cli/server/routers/agents.py +616 -0
- keboola_agent_cli/server/routers/ai_chat.py +129 -0
- keboola_agent_cli/server/routers/branches.py +133 -0
- keboola_agent_cli/server/routers/components.py +48 -0
- keboola_agent_cli/server/routers/configs.py +507 -0
- keboola_agent_cli/server/routers/data_apps.py +384 -0
- keboola_agent_cli/server/routers/dev_portal.py +67 -0
- keboola_agent_cli/server/routers/encrypt.py +35 -0
- keboola_agent_cli/server/routers/feature.py +179 -0
- keboola_agent_cli/server/routers/flows.py +204 -0
- keboola_agent_cli/server/routers/health.py +53 -0
- keboola_agent_cli/server/routers/jobs.py +175 -0
- keboola_agent_cli/server/routers/kai.py +80 -0
- keboola_agent_cli/server/routers/lineage.py +226 -0
- keboola_agent_cli/server/routers/mcp.py +70 -0
- keboola_agent_cli/server/routers/members.py +170 -0
- keboola_agent_cli/server/routers/org.py +96 -0
- keboola_agent_cli/server/routers/projects.py +106 -0
- keboola_agent_cli/server/routers/schedules.py +54 -0
- keboola_agent_cli/server/routers/search.py +30 -0
- keboola_agent_cli/server/routers/semantic_layer.py +650 -0
- keboola_agent_cli/server/routers/sharing.py +86 -0
- keboola_agent_cli/server/routers/storage.py +574 -0
- keboola_agent_cli/server/routers/stream.py +100 -0
- keboola_agent_cli/server/routers/workspaces.py +302 -0
- keboola_agent_cli/server/run_broadcaster.py +329 -0
- keboola_agent_cli/server/sse.py +25 -0
- keboola_agent_cli/services/__init__.py +0 -0
- keboola_agent_cli/services/_encryption.py +217 -0
- keboola_agent_cli/services/_semantic_layer_cascade.py +147 -0
- keboola_agent_cli/services/_semantic_layer_crud.py +382 -0
- keboola_agent_cli/services/_semantic_layer_internals.py +1078 -0
- keboola_agent_cli/services/_semantic_layer_lookup.py +181 -0
- keboola_agent_cli/services/_semantic_layer_reference_data.py +217 -0
- keboola_agent_cli/services/_sync_bindings.py +456 -0
- keboola_agent_cli/services/_sync_branch.py +191 -0
- keboola_agent_cli/services/_sync_bulk.py +228 -0
- keboola_agent_cli/services/_sync_clone.py +163 -0
- keboola_agent_cli/services/_sync_models.py +97 -0
- keboola_agent_cli/services/_sync_push_ops.py +369 -0
- keboola_agent_cli/services/_sync_storage.py +376 -0
- keboola_agent_cli/services/_sync_writeback.py +167 -0
- keboola_agent_cli/services/agent_service.py +458 -0
- keboola_agent_cli/services/base.py +175 -0
- keboola_agent_cli/services/branch_service.py +588 -0
- keboola_agent_cli/services/component_service.py +694 -0
- keboola_agent_cli/services/config_service.py +2099 -0
- keboola_agent_cli/services/data_app_git_service.py +224 -0
- keboola_agent_cli/services/data_app_service.py +2082 -0
- keboola_agent_cli/services/deep_lineage_service.py +1322 -0
- keboola_agent_cli/services/dev_portal_service.py +345 -0
- keboola_agent_cli/services/doctor_service.py +445 -0
- keboola_agent_cli/services/encrypt_service.py +87 -0
- keboola_agent_cli/services/feature_service.py +268 -0
- keboola_agent_cli/services/flow_service.py +769 -0
- keboola_agent_cli/services/flow_validation.py +188 -0
- keboola_agent_cli/services/http_forwarder_service.py +236 -0
- keboola_agent_cli/services/job_idempotency_store.py +285 -0
- keboola_agent_cli/services/job_service.py +797 -0
- keboola_agent_cli/services/kai_service.py +367 -0
- keboola_agent_cli/services/lineage_service.py +274 -0
- keboola_agent_cli/services/mcp_service.py +1498 -0
- keboola_agent_cli/services/mcp_transport.py +259 -0
- keboola_agent_cli/services/member_service.py +593 -0
- keboola_agent_cli/services/org_service.py +619 -0
- keboola_agent_cli/services/project_service.py +947 -0
- keboola_agent_cli/services/repo_validate_service.py +767 -0
- keboola_agent_cli/services/schedule_service.py +731 -0
- keboola_agent_cli/services/search_service.py +331 -0
- keboola_agent_cli/services/semantic_layer_service.py +1497 -0
- keboola_agent_cli/services/sharing_service.py +307 -0
- keboola_agent_cli/services/storage_service.py +2524 -0
- keboola_agent_cli/services/stream_service.py +395 -0
- keboola_agent_cli/services/sync_service.py +2244 -0
- keboola_agent_cli/services/variables_service.py +447 -0
- keboola_agent_cli/services/version_service.py +1038 -0
- keboola_agent_cli/services/workspace_service.py +1103 -0
- keboola_agent_cli/stream_client.py +217 -0
- keboola_agent_cli/sync/__init__.py +1 -0
- keboola_agent_cli/sync/branch_mapping.py +174 -0
- keboola_agent_cli/sync/clone.py +211 -0
- keboola_agent_cli/sync/code_extraction.py +655 -0
- keboola_agent_cli/sync/config_format.py +290 -0
- keboola_agent_cli/sync/diff_engine.py +566 -0
- keboola_agent_cli/sync/git_utils.py +93 -0
- keboola_agent_cli/sync/manifest.py +162 -0
- keboola_agent_cli/sync/naming.py +90 -0
- keboola_agent_cli/sync/secrets.py +62 -0
- keboola_agent_cli/sync/sql_split.py +134 -0
- keboola_cli-0.63.4.dist-info/METADATA +308 -0
- keboola_cli-0.63.4.dist-info/RECORD +306 -0
- keboola_cli-0.63.4.dist-info/WHEEL +4 -0
- keboola_cli-0.63.4.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
"""Conditional flow (keboola.flow) lifecycle service.
|
|
2
|
+
|
|
3
|
+
Provides CRUD for keboola.flow (Conditional Flow) configurations, plus
|
|
4
|
+
schedule bind/unbind via keboola.scheduler component configs.
|
|
5
|
+
|
|
6
|
+
keboola.orchestrator support was dropped in 0.57.0; this service targets the
|
|
7
|
+
single component keboola.flow. Legacy orchestrator configs are still counted
|
|
8
|
+
(not listed) so the CLI can warn users why a flow "disappeared".
|
|
9
|
+
|
|
10
|
+
Flows are semantic sugar over the Storage API config layer -- no separate
|
|
11
|
+
HTTP client is needed. Schedules are stored as keboola.scheduler configs
|
|
12
|
+
whose ``target`` points at the flow.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from ..ai_client import AiServiceClient
|
|
24
|
+
from ..config_store import ConfigStore
|
|
25
|
+
from ..errors import ErrorCode, KeboolaApiError
|
|
26
|
+
from ..models import ComponentDetail, ProjectConfig
|
|
27
|
+
from .base import BaseService, ClientFactory
|
|
28
|
+
from .flow_validation import find_unreachable_phases, validate_conditional_flow
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
FLOW_COMPONENT_ID = "keboola.flow"
|
|
33
|
+
LEGACY_FLOW_COMPONENT_ID = "keboola.orchestrator"
|
|
34
|
+
SCHEDULER_COMPONENT_ID = "keboola.scheduler"
|
|
35
|
+
|
|
36
|
+
AiClientFactory = Callable[[str, str], AiServiceClient]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class FlowSchemaFetch:
|
|
41
|
+
"""Outcome of fetching the live keboola.flow JSON Schema from the stack.
|
|
42
|
+
|
|
43
|
+
``schema`` holds the JSON Schema dict on success and is ``None`` when it
|
|
44
|
+
could not be obtained; ``reason`` explains the failure (``None`` on
|
|
45
|
+
success). A ``None`` schema must NOT block a write -- callers degrade to
|
|
46
|
+
semantic-only validation and surface ``reason`` as a warning.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
schema: dict[str, Any] | None
|
|
50
|
+
reason: str | None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def default_ai_client_factory(stack_url: str, token: str) -> AiServiceClient:
|
|
54
|
+
"""Default factory: build an ``AiServiceClient`` for the given project."""
|
|
55
|
+
return AiServiceClient(stack_url=stack_url, token=token)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# Internal helpers
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _parse_configuration(raw: Any) -> dict[str, Any]:
|
|
64
|
+
"""Return a parsed configuration dict regardless of whether raw is str or dict."""
|
|
65
|
+
if isinstance(raw, str):
|
|
66
|
+
try:
|
|
67
|
+
return json.loads(raw)
|
|
68
|
+
except json.JSONDecodeError:
|
|
69
|
+
return {}
|
|
70
|
+
return raw or {}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _collect_schedules_by_parent(
|
|
74
|
+
client: Any, branch_id: int | None
|
|
75
|
+
) -> dict[tuple[str, str], list[dict[str, Any]]]:
|
|
76
|
+
"""Fetch every keboola.scheduler config for a project and group by target.
|
|
77
|
+
|
|
78
|
+
Returns a dict keyed by ``(parent_component_id, parent_config_id)`` so
|
|
79
|
+
``list_flows`` can look up schedules per flow with a single in-memory
|
|
80
|
+
lookup. Used by the ``--with-schedules`` enrichment path.
|
|
81
|
+
|
|
82
|
+
A missing scheduler component (404 NOT_FOUND) yields an empty map.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
all_sched = client.list_component_configs(SCHEDULER_COMPONENT_ID, branch_id=branch_id)
|
|
86
|
+
except KeboolaApiError as exc:
|
|
87
|
+
if exc.error_code == ErrorCode.NOT_FOUND:
|
|
88
|
+
return {}
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
grouped: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
|
92
|
+
for sched in all_sched:
|
|
93
|
+
body = _parse_configuration(sched.get("configuration"))
|
|
94
|
+
target = body.get("target") or {}
|
|
95
|
+
sched_info = body.get("schedule") or {}
|
|
96
|
+
|
|
97
|
+
parent_key = (
|
|
98
|
+
str(target.get("componentId", "")),
|
|
99
|
+
str(target.get("configurationId", "")),
|
|
100
|
+
)
|
|
101
|
+
if not parent_key[0] or not parent_key[1]:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
state_raw = sched_info.get("state")
|
|
105
|
+
if isinstance(state_raw, dict):
|
|
106
|
+
enabled = bool(state_raw.get("enabled", False))
|
|
107
|
+
else:
|
|
108
|
+
enabled = str(state_raw).lower() == "enabled"
|
|
109
|
+
|
|
110
|
+
grouped.setdefault(parent_key, []).append(
|
|
111
|
+
{
|
|
112
|
+
"schedule_id": str(sched.get("id", "")),
|
|
113
|
+
"cron": str(sched_info.get("cronTab", "")),
|
|
114
|
+
"timezone": str(sched_info.get("timezone", "UTC")),
|
|
115
|
+
"enabled": enabled,
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
return grouped
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Service
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class FlowService(BaseService):
|
|
127
|
+
"""Business logic for conditional flow (keboola.flow) CRUD.
|
|
128
|
+
|
|
129
|
+
All schedule operations use keboola.scheduler component configs --
|
|
130
|
+
no separate Scheduler Service HTTP client required.
|
|
131
|
+
|
|
132
|
+
The structural conditional-flow JSON Schema is fetched at runtime from the
|
|
133
|
+
stack's component registry (AI Service ``configurationSchema`` for
|
|
134
|
+
``keboola.flow``) via ``ai_client_factory`` -- it is never bundled.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
config_store: ConfigStore,
|
|
140
|
+
client_factory: ClientFactory | None = None,
|
|
141
|
+
ai_client_factory: AiClientFactory | None = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
super().__init__(config_store, client_factory)
|
|
144
|
+
self._ai_client_factory = ai_client_factory or default_ai_client_factory
|
|
145
|
+
|
|
146
|
+
# ── schema fetch ─────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
def _fetch_flow_schema(self, project: ProjectConfig) -> FlowSchemaFetch:
|
|
149
|
+
"""Fetch the live keboola.flow JSON Schema from the AI Service.
|
|
150
|
+
|
|
151
|
+
Returns a ``FlowSchemaFetch`` with ``schema`` set on success, or
|
|
152
|
+
``schema=None`` + a ``reason`` when the schema cannot be obtained
|
|
153
|
+
(network error, KeboolaApiError, malformed or empty schema). A ``None``
|
|
154
|
+
schema must NOT block a write -- the caller degrades to semantic-only
|
|
155
|
+
validation and surfaces ``reason`` as a warning.
|
|
156
|
+
"""
|
|
157
|
+
ai_client = self._ai_client_factory(project.stack_url, project.token)
|
|
158
|
+
try:
|
|
159
|
+
raw = ai_client.get_component_detail(FLOW_COMPONENT_ID)
|
|
160
|
+
except KeboolaApiError as exc:
|
|
161
|
+
return FlowSchemaFetch(schema=None, reason=exc.message)
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
# Intentionally broad: ANY schema-fetch failure must degrade to
|
|
164
|
+
# semantic-only validation, never block the write. Narrowing to
|
|
165
|
+
# OSError-style transport errors would miss httpx exceptions
|
|
166
|
+
# (httpx.HTTPError does not subclass OSError) and re-raise them.
|
|
167
|
+
return FlowSchemaFetch(schema=None, reason=str(exc))
|
|
168
|
+
finally:
|
|
169
|
+
ai_client.close()
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
detail = ComponentDetail(**raw)
|
|
173
|
+
except (TypeError, ValueError) as exc:
|
|
174
|
+
return FlowSchemaFetch(
|
|
175
|
+
schema=None, reason=f"component detail could not be parsed ({exc})"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
schema = detail.configuration_schema
|
|
179
|
+
if not schema:
|
|
180
|
+
return FlowSchemaFetch(
|
|
181
|
+
schema=None, reason="AI Service returned no configurationSchema for keboola.flow"
|
|
182
|
+
)
|
|
183
|
+
return FlowSchemaFetch(schema=schema, reason=None)
|
|
184
|
+
|
|
185
|
+
def fetch_flow_schema(self, alias: str) -> FlowSchemaFetch:
|
|
186
|
+
"""Public schema fetch for a project alias (used by ``flow validate
|
|
187
|
+
--project`` and ``flow schema --full --project``).
|
|
188
|
+
|
|
189
|
+
Returns a ``FlowSchemaFetch`` (``schema`` set on success, or
|
|
190
|
+
``schema=None`` + ``reason`` on any failure) -- the caller decides how
|
|
191
|
+
to surface the reason.
|
|
192
|
+
"""
|
|
193
|
+
projects = self.resolve_projects([alias])
|
|
194
|
+
project = projects[alias]
|
|
195
|
+
return self._fetch_flow_schema(project)
|
|
196
|
+
|
|
197
|
+
# ── list ────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
def list_flows(
|
|
200
|
+
self,
|
|
201
|
+
aliases: list[str] | None = None,
|
|
202
|
+
branch_id: int | None = None,
|
|
203
|
+
with_schedules: bool = False,
|
|
204
|
+
) -> dict[str, Any]:
|
|
205
|
+
"""List conditional flows (keboola.flow) across projects.
|
|
206
|
+
|
|
207
|
+
Only ``keboola.flow`` configs are returned. Legacy
|
|
208
|
+
``keboola.orchestrator`` configs are counted (not listed) and surfaced
|
|
209
|
+
as ``legacy_orchestrator_count`` so the CLI can warn users why a flow
|
|
210
|
+
"disappeared" (orchestrator support was dropped in 0.57.0).
|
|
211
|
+
|
|
212
|
+
When ``with_schedules`` is True, each flow row is enriched with a
|
|
213
|
+
``schedules`` list pulled from the same project's
|
|
214
|
+
``keboola.scheduler`` configs. The enrichment costs **one** extra
|
|
215
|
+
``list_component_configs`` call per project (NOT per flow) -- the
|
|
216
|
+
join happens in memory by (component_id, config_id) key.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
aliases: Project aliases to query; None means every project.
|
|
220
|
+
branch_id: Dev-branch override for single-project fan-out.
|
|
221
|
+
with_schedules: When True, populate ``schedules`` on each row.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Dict with keys:
|
|
225
|
+
- "flows": list of keboola.flow dicts (project_alias,
|
|
226
|
+
component_id, config_id, name, description, is_disabled, and
|
|
227
|
+
``schedules`` when ``with_schedules`` is True)
|
|
228
|
+
- "errors": list of error dicts
|
|
229
|
+
- "legacy_orchestrator_count": total legacy orchestrator
|
|
230
|
+
configs found across the queried projects (not listed)
|
|
231
|
+
"""
|
|
232
|
+
projects = self.resolve_projects(aliases)
|
|
233
|
+
|
|
234
|
+
def worker(alias: str, project: ProjectConfig) -> tuple[Any, ...]:
|
|
235
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
236
|
+
effective_branch = branch_id or project.active_branch_id
|
|
237
|
+
try:
|
|
238
|
+
flows: list[dict[str, Any]] = []
|
|
239
|
+
try:
|
|
240
|
+
configs = client.list_component_configs(
|
|
241
|
+
FLOW_COMPONENT_ID, branch_id=effective_branch
|
|
242
|
+
)
|
|
243
|
+
except KeboolaApiError as exc:
|
|
244
|
+
if exc.error_code == ErrorCode.NOT_FOUND:
|
|
245
|
+
configs = []
|
|
246
|
+
else:
|
|
247
|
+
raise
|
|
248
|
+
for cfg in configs:
|
|
249
|
+
flow_row: dict[str, Any] = {
|
|
250
|
+
"project_alias": alias,
|
|
251
|
+
"component_id": FLOW_COMPONENT_ID,
|
|
252
|
+
"config_id": str(cfg.get("id", "")),
|
|
253
|
+
"name": cfg.get("name", ""),
|
|
254
|
+
"description": cfg.get("description", ""),
|
|
255
|
+
"is_disabled": cfg.get("isDisabled", False),
|
|
256
|
+
}
|
|
257
|
+
if with_schedules:
|
|
258
|
+
flow_row["schedules"] = []
|
|
259
|
+
flows.append(flow_row)
|
|
260
|
+
|
|
261
|
+
# Count (do not list) legacy orchestrator configs so the CLI can warn.
|
|
262
|
+
try:
|
|
263
|
+
legacy = client.list_component_configs(
|
|
264
|
+
LEGACY_FLOW_COMPONENT_ID, branch_id=effective_branch
|
|
265
|
+
)
|
|
266
|
+
legacy_count = len(legacy)
|
|
267
|
+
except KeboolaApiError as exc:
|
|
268
|
+
if exc.error_code == ErrorCode.NOT_FOUND:
|
|
269
|
+
legacy_count = 0
|
|
270
|
+
else:
|
|
271
|
+
raise
|
|
272
|
+
|
|
273
|
+
# One extra list call per project, then a map-join in memory.
|
|
274
|
+
if with_schedules and flows:
|
|
275
|
+
schedules_by_parent = _collect_schedules_by_parent(client, effective_branch)
|
|
276
|
+
for flow_row in flows:
|
|
277
|
+
key = (flow_row["component_id"], flow_row["config_id"])
|
|
278
|
+
flow_row["schedules"] = schedules_by_parent.get(key, [])
|
|
279
|
+
|
|
280
|
+
return (alias, flows, legacy_count)
|
|
281
|
+
except KeboolaApiError as exc:
|
|
282
|
+
return (
|
|
283
|
+
alias,
|
|
284
|
+
{
|
|
285
|
+
"project_alias": alias,
|
|
286
|
+
"error_code": exc.error_code,
|
|
287
|
+
"message": exc.message,
|
|
288
|
+
},
|
|
289
|
+
)
|
|
290
|
+
except Exception as exc:
|
|
291
|
+
return (
|
|
292
|
+
alias,
|
|
293
|
+
{
|
|
294
|
+
"project_alias": alias,
|
|
295
|
+
"error_code": "UNEXPECTED_ERROR",
|
|
296
|
+
"message": str(exc),
|
|
297
|
+
},
|
|
298
|
+
)
|
|
299
|
+
finally:
|
|
300
|
+
client.close()
|
|
301
|
+
|
|
302
|
+
successes, errors = self._run_parallel(projects, worker)
|
|
303
|
+
|
|
304
|
+
all_flows: list[dict[str, Any]] = []
|
|
305
|
+
legacy_total = 0
|
|
306
|
+
for _, flows, legacy_count in successes:
|
|
307
|
+
all_flows.extend(flows)
|
|
308
|
+
legacy_total += legacy_count
|
|
309
|
+
all_flows.sort(key=lambda f: (f["project_alias"], f["name"].lower()))
|
|
310
|
+
errors.sort(key=lambda e: e.get("project_alias", ""))
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
"flows": all_flows,
|
|
314
|
+
"errors": errors,
|
|
315
|
+
"legacy_orchestrator_count": legacy_total,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# ── detail ──────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
def get_flow_detail(
|
|
321
|
+
self,
|
|
322
|
+
alias: str,
|
|
323
|
+
config_id: str,
|
|
324
|
+
branch_id: int | None = None,
|
|
325
|
+
) -> dict[str, Any]:
|
|
326
|
+
"""Return full flow detail including phases, tasks, and schedule info.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
ConfigError: If alias is not found.
|
|
330
|
+
KeboolaApiError: On API failure.
|
|
331
|
+
"""
|
|
332
|
+
projects = self.resolve_projects([alias])
|
|
333
|
+
project = projects[alias]
|
|
334
|
+
effective_branch = branch_id or project.active_branch_id
|
|
335
|
+
|
|
336
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
337
|
+
try:
|
|
338
|
+
detail = client.get_config_detail(
|
|
339
|
+
FLOW_COMPONENT_ID, config_id, branch_id=effective_branch
|
|
340
|
+
)
|
|
341
|
+
finally:
|
|
342
|
+
client.close()
|
|
343
|
+
|
|
344
|
+
body = _parse_configuration(detail.get("configuration"))
|
|
345
|
+
phases = body.get("phases", [])
|
|
346
|
+
tasks = body.get("tasks", [])
|
|
347
|
+
|
|
348
|
+
detail["project_alias"] = alias
|
|
349
|
+
detail["component_id"] = FLOW_COMPONENT_ID
|
|
350
|
+
detail["branch_id"] = effective_branch
|
|
351
|
+
detail["phases"] = phases
|
|
352
|
+
detail["tasks"] = tasks
|
|
353
|
+
detail["phase_count"] = len(phases)
|
|
354
|
+
detail["task_count"] = len(tasks)
|
|
355
|
+
return detail
|
|
356
|
+
|
|
357
|
+
# ── create ──────────────────────────────────────────────────────
|
|
358
|
+
|
|
359
|
+
def create_flow(
|
|
360
|
+
self,
|
|
361
|
+
alias: str,
|
|
362
|
+
name: str,
|
|
363
|
+
description: str = "",
|
|
364
|
+
phases: list[dict[str, Any]] | None = None,
|
|
365
|
+
tasks: list[dict[str, Any]] | None = None,
|
|
366
|
+
branch_id: int | None = None,
|
|
367
|
+
) -> dict[str, Any]:
|
|
368
|
+
"""Create a new conditional-flow (keboola.flow) configuration.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
alias: Project alias.
|
|
372
|
+
name: Flow name.
|
|
373
|
+
description: Optional description.
|
|
374
|
+
phases: Phase definitions (validated against the CF schema).
|
|
375
|
+
tasks: Task definitions (validated against the CF schema).
|
|
376
|
+
branch_id: Dev branch override.
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
KeboolaApiError: On API failure or definition validation error
|
|
380
|
+
(error_code='INVALID_FLOW_DEFINITION').
|
|
381
|
+
"""
|
|
382
|
+
phases = phases or []
|
|
383
|
+
tasks = tasks or []
|
|
384
|
+
|
|
385
|
+
projects = self.resolve_projects([alias])
|
|
386
|
+
project = projects[alias]
|
|
387
|
+
effective_branch = branch_id or project.active_branch_id
|
|
388
|
+
|
|
389
|
+
fetch = self._fetch_flow_schema(project)
|
|
390
|
+
warnings: list[str] = []
|
|
391
|
+
if fetch.schema is None:
|
|
392
|
+
warnings.append(f"structural schema validation skipped: {fetch.reason}")
|
|
393
|
+
|
|
394
|
+
definition_errors = validate_conditional_flow(phases, tasks, fetch.schema)
|
|
395
|
+
if definition_errors:
|
|
396
|
+
raise KeboolaApiError(
|
|
397
|
+
message="Flow definition is invalid: " + "; ".join(definition_errors),
|
|
398
|
+
status_code=400,
|
|
399
|
+
error_code=ErrorCode.INVALID_FLOW_DEFINITION,
|
|
400
|
+
retryable=False,
|
|
401
|
+
)
|
|
402
|
+
warnings.extend(
|
|
403
|
+
f"Phase '{pid}' is unreachable from the entry phase"
|
|
404
|
+
for pid in find_unreachable_phases(phases)
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
configuration: dict[str, Any] = {"phases": phases, "tasks": tasks}
|
|
408
|
+
|
|
409
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
410
|
+
try:
|
|
411
|
+
result = client.create_config(
|
|
412
|
+
component_id=FLOW_COMPONENT_ID,
|
|
413
|
+
name=name,
|
|
414
|
+
configuration=configuration,
|
|
415
|
+
description=description,
|
|
416
|
+
branch_id=effective_branch,
|
|
417
|
+
)
|
|
418
|
+
finally:
|
|
419
|
+
client.close()
|
|
420
|
+
|
|
421
|
+
result["project_alias"] = alias
|
|
422
|
+
result["branch_id"] = effective_branch
|
|
423
|
+
result["phase_count"] = len(phases)
|
|
424
|
+
result["task_count"] = len(tasks)
|
|
425
|
+
result["warnings"] = warnings
|
|
426
|
+
return result
|
|
427
|
+
|
|
428
|
+
# ── update ──────────────────────────────────────────────────────
|
|
429
|
+
|
|
430
|
+
def update_flow(
|
|
431
|
+
self,
|
|
432
|
+
alias: str,
|
|
433
|
+
config_id: str,
|
|
434
|
+
name: str | None = None,
|
|
435
|
+
description: str | None = None,
|
|
436
|
+
phases: list[dict[str, Any]] | None = None,
|
|
437
|
+
tasks: list[dict[str, Any]] | None = None,
|
|
438
|
+
branch_id: int | None = None,
|
|
439
|
+
) -> dict[str, Any]:
|
|
440
|
+
"""Update an existing conditional-flow (keboola.flow) configuration.
|
|
441
|
+
|
|
442
|
+
When phases and/or tasks are provided, validation runs on the merged
|
|
443
|
+
body (the unspecified side is fetched from the current config) so a
|
|
444
|
+
half-config is never validated.
|
|
445
|
+
|
|
446
|
+
Raises:
|
|
447
|
+
KeboolaApiError: On API failure or definition validation error
|
|
448
|
+
(error_code='INVALID_FLOW_DEFINITION').
|
|
449
|
+
"""
|
|
450
|
+
projects = self.resolve_projects([alias])
|
|
451
|
+
project = projects[alias]
|
|
452
|
+
effective_branch = branch_id or project.active_branch_id
|
|
453
|
+
|
|
454
|
+
warnings: list[str] = []
|
|
455
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
456
|
+
try:
|
|
457
|
+
configuration: dict[str, Any] | None = None
|
|
458
|
+
if phases is not None or tasks is not None:
|
|
459
|
+
current = client.get_config_detail(
|
|
460
|
+
FLOW_COMPONENT_ID, config_id, branch_id=effective_branch
|
|
461
|
+
)
|
|
462
|
+
current_body = _parse_configuration(current.get("configuration"))
|
|
463
|
+
merged_phases = phases if phases is not None else current_body.get("phases", [])
|
|
464
|
+
merged_tasks = tasks if tasks is not None else current_body.get("tasks", [])
|
|
465
|
+
|
|
466
|
+
fetch = self._fetch_flow_schema(project)
|
|
467
|
+
if fetch.schema is None:
|
|
468
|
+
warnings.append(f"structural schema validation skipped: {fetch.reason}")
|
|
469
|
+
|
|
470
|
+
definition_errors = validate_conditional_flow(
|
|
471
|
+
merged_phases, merged_tasks, fetch.schema
|
|
472
|
+
)
|
|
473
|
+
if definition_errors:
|
|
474
|
+
raise KeboolaApiError(
|
|
475
|
+
message="Flow definition is invalid: " + "; ".join(definition_errors),
|
|
476
|
+
status_code=400,
|
|
477
|
+
error_code=ErrorCode.INVALID_FLOW_DEFINITION,
|
|
478
|
+
retryable=False,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
warnings.extend(
|
|
482
|
+
f"Phase '{pid}' is unreachable from the entry phase"
|
|
483
|
+
for pid in find_unreachable_phases(merged_phases)
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
configuration = dict(current_body)
|
|
487
|
+
configuration["phases"] = merged_phases
|
|
488
|
+
configuration["tasks"] = merged_tasks
|
|
489
|
+
|
|
490
|
+
result = client.update_config(
|
|
491
|
+
component_id=FLOW_COMPONENT_ID,
|
|
492
|
+
config_id=config_id,
|
|
493
|
+
name=name,
|
|
494
|
+
description=description,
|
|
495
|
+
configuration=configuration,
|
|
496
|
+
change_description="Updated via kbagent flow update",
|
|
497
|
+
branch_id=effective_branch,
|
|
498
|
+
)
|
|
499
|
+
finally:
|
|
500
|
+
client.close()
|
|
501
|
+
|
|
502
|
+
result["project_alias"] = alias
|
|
503
|
+
result["branch_id"] = effective_branch
|
|
504
|
+
result["warnings"] = warnings
|
|
505
|
+
return result
|
|
506
|
+
|
|
507
|
+
# ── delete ──────────────────────────────────────────────────────
|
|
508
|
+
|
|
509
|
+
def delete_flow(
|
|
510
|
+
self,
|
|
511
|
+
alias: str,
|
|
512
|
+
config_id: str,
|
|
513
|
+
branch_id: int | None = None,
|
|
514
|
+
) -> dict[str, Any]:
|
|
515
|
+
"""Delete a conditional-flow (keboola.flow) configuration.
|
|
516
|
+
|
|
517
|
+
Does NOT automatically remove associated keboola.scheduler configs.
|
|
518
|
+
Use remove_flow_schedule() first if needed.
|
|
519
|
+
"""
|
|
520
|
+
projects = self.resolve_projects([alias])
|
|
521
|
+
project = projects[alias]
|
|
522
|
+
effective_branch = branch_id or project.active_branch_id
|
|
523
|
+
|
|
524
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
525
|
+
try:
|
|
526
|
+
client.delete_config(
|
|
527
|
+
component_id=FLOW_COMPONENT_ID,
|
|
528
|
+
config_id=config_id,
|
|
529
|
+
branch_id=effective_branch,
|
|
530
|
+
)
|
|
531
|
+
finally:
|
|
532
|
+
client.close()
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
"status": "deleted",
|
|
536
|
+
"project_alias": alias,
|
|
537
|
+
"component_id": FLOW_COMPONENT_ID,
|
|
538
|
+
"config_id": config_id,
|
|
539
|
+
"branch_id": effective_branch,
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
# ── schedule ────────────────────────────────────────────────────
|
|
543
|
+
|
|
544
|
+
def list_flow_schedules(
|
|
545
|
+
self,
|
|
546
|
+
alias: str,
|
|
547
|
+
config_id: str,
|
|
548
|
+
branch_id: int | None = None,
|
|
549
|
+
) -> dict[str, Any]:
|
|
550
|
+
"""List keboola.scheduler configs that target this flow.
|
|
551
|
+
|
|
552
|
+
Fetches all keboola.scheduler configs and filters by
|
|
553
|
+
target.componentId == keboola.flow + target.configurationId.
|
|
554
|
+
"""
|
|
555
|
+
projects = self.resolve_projects([alias])
|
|
556
|
+
project = projects[alias]
|
|
557
|
+
effective_branch = branch_id or project.active_branch_id
|
|
558
|
+
|
|
559
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
560
|
+
try:
|
|
561
|
+
try:
|
|
562
|
+
all_sched = client.list_component_configs(
|
|
563
|
+
SCHEDULER_COMPONENT_ID, branch_id=effective_branch
|
|
564
|
+
)
|
|
565
|
+
except KeboolaApiError as exc:
|
|
566
|
+
if exc.error_code == ErrorCode.NOT_FOUND:
|
|
567
|
+
all_sched = []
|
|
568
|
+
else:
|
|
569
|
+
raise
|
|
570
|
+
finally:
|
|
571
|
+
client.close()
|
|
572
|
+
|
|
573
|
+
schedules: list[dict[str, Any]] = []
|
|
574
|
+
for sched in all_sched:
|
|
575
|
+
body = _parse_configuration(sched.get("configuration"))
|
|
576
|
+
target = body.get("target") or {}
|
|
577
|
+
if target.get("componentId") == FLOW_COMPONENT_ID and str(
|
|
578
|
+
target.get("configurationId", "")
|
|
579
|
+
) == str(config_id):
|
|
580
|
+
sched_info = body.get("schedule") or {}
|
|
581
|
+
schedules.append(
|
|
582
|
+
{
|
|
583
|
+
"schedule_id": str(sched.get("id", "")),
|
|
584
|
+
"name": sched.get("name", ""),
|
|
585
|
+
"cron_tab": sched_info.get("cronTab", ""),
|
|
586
|
+
"timezone": sched_info.get("timezone", "UTC"),
|
|
587
|
+
"state": sched_info.get("state", "disabled"),
|
|
588
|
+
}
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
"project_alias": alias,
|
|
593
|
+
"component_id": FLOW_COMPONENT_ID,
|
|
594
|
+
"config_id": config_id,
|
|
595
|
+
"schedules": schedules,
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
def set_flow_schedule(
|
|
599
|
+
self,
|
|
600
|
+
alias: str,
|
|
601
|
+
config_id: str,
|
|
602
|
+
cron_tab: str,
|
|
603
|
+
timezone: str = "UTC",
|
|
604
|
+
enabled: bool = True,
|
|
605
|
+
schedule_name: str | None = None,
|
|
606
|
+
branch_id: int | None = None,
|
|
607
|
+
) -> dict[str, Any]:
|
|
608
|
+
"""Upsert a keboola.scheduler config that targets this flow.
|
|
609
|
+
|
|
610
|
+
If a schedule already exists for this flow it is updated in-place
|
|
611
|
+
(idempotent). If none exists a new one is created. This prevents
|
|
612
|
+
duplicate schedules when called repeatedly.
|
|
613
|
+
|
|
614
|
+
The schedule is stored as a keboola.scheduler configuration whose
|
|
615
|
+
``target`` points at the keboola.flow component + config.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
alias: Project alias.
|
|
619
|
+
config_id: Flow configuration ID.
|
|
620
|
+
cron_tab: Cron expression (e.g. '0 6 * * *').
|
|
621
|
+
timezone: IANA timezone (default 'UTC').
|
|
622
|
+
enabled: Whether the schedule is active.
|
|
623
|
+
schedule_name: Optional scheduler config name.
|
|
624
|
+
branch_id: Dev branch override.
|
|
625
|
+
"""
|
|
626
|
+
projects = self.resolve_projects([alias])
|
|
627
|
+
project = projects[alias]
|
|
628
|
+
effective_branch = branch_id or project.active_branch_id
|
|
629
|
+
|
|
630
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
631
|
+
try:
|
|
632
|
+
if not schedule_name:
|
|
633
|
+
try:
|
|
634
|
+
detail = client.get_config_detail(
|
|
635
|
+
FLOW_COMPONENT_ID, config_id, branch_id=effective_branch
|
|
636
|
+
)
|
|
637
|
+
schedule_name = f"{detail.get('name', config_id)} (Schedule)"
|
|
638
|
+
except KeboolaApiError:
|
|
639
|
+
schedule_name = f"{config_id} (Schedule)"
|
|
640
|
+
|
|
641
|
+
configuration = {
|
|
642
|
+
"schedule": {
|
|
643
|
+
"cronTab": cron_tab,
|
|
644
|
+
"timezone": timezone,
|
|
645
|
+
"state": "enabled" if enabled else "disabled",
|
|
646
|
+
},
|
|
647
|
+
"target": {
|
|
648
|
+
"mode": "run",
|
|
649
|
+
"componentId": FLOW_COMPONENT_ID,
|
|
650
|
+
"configurationId": config_id,
|
|
651
|
+
},
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
# Upsert: update existing schedule if one exists
|
|
655
|
+
try:
|
|
656
|
+
existing = client.list_component_configs(
|
|
657
|
+
SCHEDULER_COMPONENT_ID, branch_id=effective_branch
|
|
658
|
+
)
|
|
659
|
+
except KeboolaApiError as exc:
|
|
660
|
+
if exc.error_code == ErrorCode.NOT_FOUND:
|
|
661
|
+
existing = []
|
|
662
|
+
else:
|
|
663
|
+
raise
|
|
664
|
+
|
|
665
|
+
existing_id: str | None = None
|
|
666
|
+
for sched in existing:
|
|
667
|
+
body = _parse_configuration(sched.get("configuration"))
|
|
668
|
+
target = body.get("target") or {}
|
|
669
|
+
if target.get("componentId") == FLOW_COMPONENT_ID and str(
|
|
670
|
+
target.get("configurationId", "")
|
|
671
|
+
) == str(config_id):
|
|
672
|
+
existing_id = str(sched.get("id", ""))
|
|
673
|
+
break
|
|
674
|
+
|
|
675
|
+
if existing_id:
|
|
676
|
+
result = client.update_config(
|
|
677
|
+
component_id=SCHEDULER_COMPONENT_ID,
|
|
678
|
+
config_id=existing_id,
|
|
679
|
+
name=schedule_name,
|
|
680
|
+
configuration=configuration,
|
|
681
|
+
branch_id=effective_branch,
|
|
682
|
+
)
|
|
683
|
+
status = "updated"
|
|
684
|
+
else:
|
|
685
|
+
result = client.create_config(
|
|
686
|
+
component_id=SCHEDULER_COMPONENT_ID,
|
|
687
|
+
name=schedule_name,
|
|
688
|
+
configuration=configuration,
|
|
689
|
+
branch_id=effective_branch,
|
|
690
|
+
)
|
|
691
|
+
status = "created"
|
|
692
|
+
finally:
|
|
693
|
+
client.close()
|
|
694
|
+
|
|
695
|
+
return {
|
|
696
|
+
"status": status,
|
|
697
|
+
"project_alias": alias,
|
|
698
|
+
"schedule_id": str(result.get("id", existing_id or "")),
|
|
699
|
+
"schedule_name": schedule_name,
|
|
700
|
+
"component_id": FLOW_COMPONENT_ID,
|
|
701
|
+
"config_id": config_id,
|
|
702
|
+
"cron_tab": cron_tab,
|
|
703
|
+
"timezone": timezone,
|
|
704
|
+
"state": "enabled" if enabled else "disabled",
|
|
705
|
+
"branch_id": effective_branch,
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
def remove_flow_schedule(
|
|
709
|
+
self,
|
|
710
|
+
alias: str,
|
|
711
|
+
config_id: str,
|
|
712
|
+
branch_id: int | None = None,
|
|
713
|
+
) -> dict[str, Any]:
|
|
714
|
+
"""Delete all keboola.scheduler configs that target this flow.
|
|
715
|
+
|
|
716
|
+
Idempotent: if no schedules exist, returns deleted_count=0.
|
|
717
|
+
"""
|
|
718
|
+
projects = self.resolve_projects([alias])
|
|
719
|
+
project = projects[alias]
|
|
720
|
+
effective_branch = branch_id or project.active_branch_id
|
|
721
|
+
|
|
722
|
+
client = self._client_factory(project.stack_url, project.token)
|
|
723
|
+
try:
|
|
724
|
+
try:
|
|
725
|
+
all_sched = client.list_component_configs(
|
|
726
|
+
SCHEDULER_COMPONENT_ID, branch_id=effective_branch
|
|
727
|
+
)
|
|
728
|
+
except KeboolaApiError as exc:
|
|
729
|
+
if exc.error_code == ErrorCode.NOT_FOUND:
|
|
730
|
+
all_sched = []
|
|
731
|
+
else:
|
|
732
|
+
raise
|
|
733
|
+
|
|
734
|
+
deleted: list[str] = []
|
|
735
|
+
errors: list[str] = []
|
|
736
|
+
for sched in all_sched:
|
|
737
|
+
body = _parse_configuration(sched.get("configuration"))
|
|
738
|
+
target = body.get("target") or {}
|
|
739
|
+
if target.get("componentId") == FLOW_COMPONENT_ID and str(
|
|
740
|
+
target.get("configurationId", "")
|
|
741
|
+
) == str(config_id):
|
|
742
|
+
sched_id = str(sched.get("id", ""))
|
|
743
|
+
try:
|
|
744
|
+
client.delete_config(
|
|
745
|
+
SCHEDULER_COMPONENT_ID, sched_id, branch_id=effective_branch
|
|
746
|
+
)
|
|
747
|
+
deleted.append(sched_id)
|
|
748
|
+
except KeboolaApiError as exc:
|
|
749
|
+
errors.append(f"{sched_id}: {exc.message}")
|
|
750
|
+
finally:
|
|
751
|
+
client.close()
|
|
752
|
+
|
|
753
|
+
if errors and not deleted:
|
|
754
|
+
raise KeboolaApiError(
|
|
755
|
+
message=f"Failed to delete schedules: {'; '.join(errors)}",
|
|
756
|
+
status_code=0,
|
|
757
|
+
error_code=ErrorCode.SCHEDULE_DELETE_FAILED,
|
|
758
|
+
retryable=False,
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
"status": "removed",
|
|
763
|
+
"project_alias": alias,
|
|
764
|
+
"component_id": FLOW_COMPONENT_ID,
|
|
765
|
+
"config_id": config_id,
|
|
766
|
+
"deleted_schedule_ids": deleted,
|
|
767
|
+
"deleted_count": len(deleted),
|
|
768
|
+
"branch_id": effective_branch,
|
|
769
|
+
}
|