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,217 @@
|
|
|
1
|
+
"""Keboola Stream (Data Streams) API client with retry, timeouts, token masking.
|
|
2
|
+
|
|
3
|
+
This module talks to the Keboola **Stream control-plane API** for managing
|
|
4
|
+
Data Streams sources and sinks (list / create / detail / delete). The base URL
|
|
5
|
+
is derived from the Storage API stack URL by replacing 'connection.' with
|
|
6
|
+
'stream.' in the hostname (the same scheme used for 'ai.'/'queue.'), and the
|
|
7
|
+
request is authenticated with the per-project Storage API token
|
|
8
|
+
(``X-StorageApi-Token``) -- no manage token is involved.
|
|
9
|
+
|
|
10
|
+
Important: the OTLP *ingestion* endpoint (``stream-in.<region>/otlp/...``) is a
|
|
11
|
+
separate data-plane host and is NOT derived here -- it is returned by the API in
|
|
12
|
+
the source's ``otlp.url`` field. This client only speaks to the control plane.
|
|
13
|
+
|
|
14
|
+
Source-create and source-delete are asynchronous: the API returns a ``Task``
|
|
15
|
+
(202 Accepted); :meth:`wait_for_task` polls ``GET /v1/tasks/{taskId}`` until the
|
|
16
|
+
task reports ``isFinished``.
|
|
17
|
+
|
|
18
|
+
Inherits shared retry/error logic from :class:`BaseHttpClient`.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import time
|
|
25
|
+
from typing import Any
|
|
26
|
+
from urllib.parse import quote, urlparse
|
|
27
|
+
|
|
28
|
+
from .constants import STREAM_API_TIMEOUT, STREAM_TASK_POLL_INTERVAL, STREAM_TASK_TIMEOUT
|
|
29
|
+
from .errors import ErrorCode, KeboolaApiError
|
|
30
|
+
from .http_base import BaseHttpClient
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StreamClient(BaseHttpClient):
|
|
36
|
+
"""HTTP client for the Keboola Stream (Data Streams) control-plane API.
|
|
37
|
+
|
|
38
|
+
Provides source CRUD, sink listing, and async-task polling, with the
|
|
39
|
+
retry/backoff (429/5xx), timeouts, and token masking inherited from
|
|
40
|
+
:class:`BaseHttpClient`.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, stack_url: str, token: str) -> None:
|
|
44
|
+
self._stack_url = stack_url.rstrip("/")
|
|
45
|
+
stream_base_url = self._derive_service_url(self._stack_url, "stream")
|
|
46
|
+
headers = {
|
|
47
|
+
"X-StorageApi-Token": token,
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
}
|
|
50
|
+
super().__init__(
|
|
51
|
+
base_url=stream_base_url,
|
|
52
|
+
token=token,
|
|
53
|
+
headers=headers,
|
|
54
|
+
timeout=STREAM_API_TIMEOUT,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def __enter__(self) -> StreamClient:
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __exit__(self, *args: Any) -> None:
|
|
61
|
+
self.close()
|
|
62
|
+
|
|
63
|
+
# ------------------------------------------------------------------
|
|
64
|
+
# Sources
|
|
65
|
+
# ------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
def list_sources(self, branch_id: str) -> dict[str, Any]:
|
|
68
|
+
"""List sources in a branch (``GET /v1/branches/{branch}/sources``)."""
|
|
69
|
+
path = f"/v1/branches/{quote(branch_id, safe='')}/sources"
|
|
70
|
+
response = self._do_request("GET", path)
|
|
71
|
+
return response.json()
|
|
72
|
+
|
|
73
|
+
def get_source(self, branch_id: str, source_id: str) -> dict[str, Any]:
|
|
74
|
+
"""Fetch one source (``GET /v1/branches/{branch}/sources/{id}``)."""
|
|
75
|
+
path = f"/v1/branches/{quote(branch_id, safe='')}/sources/{quote(source_id, safe='')}"
|
|
76
|
+
response = self._do_request("GET", path)
|
|
77
|
+
return response.json()
|
|
78
|
+
|
|
79
|
+
def create_source(
|
|
80
|
+
self,
|
|
81
|
+
branch_id: str,
|
|
82
|
+
name: str,
|
|
83
|
+
source_type: str,
|
|
84
|
+
source_id: str | None = None,
|
|
85
|
+
description: str | None = None,
|
|
86
|
+
) -> dict[str, Any]:
|
|
87
|
+
"""Create a source. Returns the async ``Task`` (poll with wait_for_task)."""
|
|
88
|
+
path = f"/v1/branches/{quote(branch_id, safe='')}/sources"
|
|
89
|
+
payload: dict[str, Any] = {"name": name, "type": source_type}
|
|
90
|
+
if source_id is not None:
|
|
91
|
+
payload["sourceId"] = source_id
|
|
92
|
+
if description is not None:
|
|
93
|
+
payload["description"] = description
|
|
94
|
+
response = self._do_request("POST", path, json=payload)
|
|
95
|
+
return response.json()
|
|
96
|
+
|
|
97
|
+
def delete_source(self, branch_id: str, source_id: str) -> dict[str, Any]:
|
|
98
|
+
"""Delete a source. Returns the async ``Task`` (poll with wait_for_task)."""
|
|
99
|
+
path = f"/v1/branches/{quote(branch_id, safe='')}/sources/{quote(source_id, safe='')}"
|
|
100
|
+
response = self._do_request("DELETE", path)
|
|
101
|
+
return response.json()
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
# Sinks
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
def list_sinks(self, branch_id: str, source_id: str) -> dict[str, Any]:
|
|
108
|
+
"""List a source's sinks (``GET .../sources/{id}/sinks``)."""
|
|
109
|
+
path = f"/v1/branches/{quote(branch_id, safe='')}/sources/{quote(source_id, safe='')}/sinks"
|
|
110
|
+
response = self._do_request("GET", path)
|
|
111
|
+
return response.json()
|
|
112
|
+
|
|
113
|
+
def create_sink(
|
|
114
|
+
self,
|
|
115
|
+
branch_id: str,
|
|
116
|
+
source_id: str,
|
|
117
|
+
*,
|
|
118
|
+
name: str,
|
|
119
|
+
table_id: str,
|
|
120
|
+
columns: list[dict[str, Any]],
|
|
121
|
+
allowed_signals: list[str] | None = None,
|
|
122
|
+
sink_id: str | None = None,
|
|
123
|
+
) -> dict[str, Any]:
|
|
124
|
+
"""Create a table sink on a source. Returns the async ``Task``.
|
|
125
|
+
|
|
126
|
+
``columns`` is the table mapping column list (see the Stream API
|
|
127
|
+
``TableColumn`` schema). ``allowed_signals`` restricts which OTLP signals
|
|
128
|
+
route to this sink (logs/metrics/traces); omit to accept all.
|
|
129
|
+
"""
|
|
130
|
+
path = f"/v1/branches/{quote(branch_id, safe='')}/sources/{quote(source_id, safe='')}/sinks"
|
|
131
|
+
payload: dict[str, Any] = {
|
|
132
|
+
"type": "table",
|
|
133
|
+
"name": name,
|
|
134
|
+
"table": {
|
|
135
|
+
"type": "keboola",
|
|
136
|
+
"tableId": table_id,
|
|
137
|
+
"mapping": {"columns": columns},
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
if allowed_signals is not None:
|
|
141
|
+
payload["allowedSignals"] = allowed_signals
|
|
142
|
+
if sink_id is not None:
|
|
143
|
+
payload["sinkId"] = sink_id
|
|
144
|
+
response = self._do_request("POST", path, json=payload)
|
|
145
|
+
return response.json()
|
|
146
|
+
|
|
147
|
+
# ------------------------------------------------------------------
|
|
148
|
+
# Tasks (async create/delete)
|
|
149
|
+
# ------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
def get_task(self, task_id: str) -> dict[str, Any]:
|
|
152
|
+
"""Fetch a task by id (``GET /v1/tasks/{taskId}``)."""
|
|
153
|
+
path = f"/v1/tasks/{quote(task_id, safe='')}"
|
|
154
|
+
response = self._do_request("GET", path)
|
|
155
|
+
return response.json()
|
|
156
|
+
|
|
157
|
+
def wait_for_task(
|
|
158
|
+
self,
|
|
159
|
+
task: dict[str, Any],
|
|
160
|
+
timeout: float = STREAM_TASK_TIMEOUT,
|
|
161
|
+
poll_interval: float = STREAM_TASK_POLL_INTERVAL,
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""Poll a ``Task`` to completion and return the finished task.
|
|
164
|
+
|
|
165
|
+
Accepts the Task dict returned by :meth:`create_source` /
|
|
166
|
+
:meth:`delete_source`. Polls its canonical poll URL (the task's ``url``
|
|
167
|
+
reduced to a path, falling back to ``/v1/tasks/{taskId}``) until
|
|
168
|
+
``isFinished`` is true, then raises :class:`KeboolaApiError` if the task
|
|
169
|
+
failed.
|
|
170
|
+
"""
|
|
171
|
+
if task.get("isFinished"):
|
|
172
|
+
return self._check_task_result(task)
|
|
173
|
+
|
|
174
|
+
poll_path = self._task_poll_path(task)
|
|
175
|
+
deadline = time.monotonic() + timeout
|
|
176
|
+
latest = task
|
|
177
|
+
while time.monotonic() < deadline:
|
|
178
|
+
time.sleep(poll_interval)
|
|
179
|
+
response = self._do_request("GET", poll_path)
|
|
180
|
+
latest = response.json()
|
|
181
|
+
if latest.get("isFinished"):
|
|
182
|
+
return self._check_task_result(latest)
|
|
183
|
+
|
|
184
|
+
raise KeboolaApiError(
|
|
185
|
+
message=(
|
|
186
|
+
f"Stream task '{latest.get('taskId', '?')}' did not finish within "
|
|
187
|
+
f"{timeout:.0f}s (last status: {latest.get('status', 'unknown')})"
|
|
188
|
+
),
|
|
189
|
+
error_code=ErrorCode.TIMEOUT,
|
|
190
|
+
retryable=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def _task_poll_path(self, task: dict[str, Any]) -> str:
|
|
194
|
+
"""Resolve the path to poll for ``task``.
|
|
195
|
+
|
|
196
|
+
Prefers the task's ``url`` field reduced to a path relative to the
|
|
197
|
+
Stream base; falls back to ``/v1/tasks/{taskId}``.
|
|
198
|
+
"""
|
|
199
|
+
url = task.get("url")
|
|
200
|
+
if isinstance(url, str) and url:
|
|
201
|
+
parsed = urlparse(url)
|
|
202
|
+
if parsed.path:
|
|
203
|
+
return parsed.path
|
|
204
|
+
task_id = task.get("taskId", "")
|
|
205
|
+
return f"/v1/tasks/{quote(str(task_id), safe='')}"
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def _check_task_result(task: dict[str, Any]) -> dict[str, Any]:
|
|
209
|
+
"""Return a finished task, raising if it ended in error."""
|
|
210
|
+
error = task.get("error")
|
|
211
|
+
status = task.get("status")
|
|
212
|
+
if error or status == "error":
|
|
213
|
+
raise KeboolaApiError(
|
|
214
|
+
message=f"Stream task failed: {error or status}",
|
|
215
|
+
error_code=ErrorCode.API_ERROR,
|
|
216
|
+
)
|
|
217
|
+
return task
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Sync package: filesystem serialization for Keboola configurations."""
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Branch mapping for git-to-Keboola branch mapping.
|
|
2
|
+
|
|
3
|
+
Manages .keboola/branch-mapping.json which maps git branch names
|
|
4
|
+
to Keboola development branch IDs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ..constants import BRANCH_MAPPING_FILENAME, KEBOOLA_DIR_NAME
|
|
14
|
+
from ..errors import ConfigError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _coerce_keboola_id(raw: Any) -> int | None:
|
|
18
|
+
"""Coerce a raw ``id`` field from JSON to ``int | None``.
|
|
19
|
+
|
|
20
|
+
Older kbagent versions (<= 0.30.3) wrote branch IDs as strings (e.g.
|
|
21
|
+
``"99999"``) due to issue #267. ``None`` means production. Empty
|
|
22
|
+
string is also treated as production for legacy tolerance.
|
|
23
|
+
|
|
24
|
+
Raises ``ValueError`` with a descriptive message if *raw* is neither
|
|
25
|
+
None, empty, nor parseable as an int (e.g. a hand-edited
|
|
26
|
+
``branch-mapping.json`` containing ``"id": "not-a-number"``). The
|
|
27
|
+
caller (typically ``BranchMapping.from_dict``) should let this
|
|
28
|
+
bubble up to ``load_branch_mapping`` which converts it to a
|
|
29
|
+
ConfigError surface (issue #269 sec-20).
|
|
30
|
+
"""
|
|
31
|
+
if raw is None or raw == "":
|
|
32
|
+
return None
|
|
33
|
+
try:
|
|
34
|
+
return int(raw)
|
|
35
|
+
except (TypeError, ValueError) as exc:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"Invalid branch ID in branch-mapping.json: {raw!r}. "
|
|
38
|
+
f"Expected null or an integer; got {type(raw).__name__}."
|
|
39
|
+
) from exc
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BranchMappingEntry:
|
|
43
|
+
"""A single git branch -> Keboola branch mapping."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, keboola_id: int | None, name: str):
|
|
46
|
+
self.keboola_id = keboola_id # None = production
|
|
47
|
+
self.name = name
|
|
48
|
+
|
|
49
|
+
def is_production(self) -> bool:
|
|
50
|
+
return self.keboola_id is None
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict[str, Any]:
|
|
53
|
+
return {"id": self.keboola_id, "name": self.name}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BranchMapping:
|
|
57
|
+
"""Manages git-to-Keboola branch mappings."""
|
|
58
|
+
|
|
59
|
+
def __init__(self) -> None:
|
|
60
|
+
self.version: int = 1
|
|
61
|
+
self.mappings: dict[str, BranchMappingEntry] = {}
|
|
62
|
+
|
|
63
|
+
def get(self, git_branch: str) -> BranchMappingEntry | None:
|
|
64
|
+
return self.mappings.get(git_branch)
|
|
65
|
+
|
|
66
|
+
def set(self, git_branch: str, keboola_id: int | None, name: str) -> None:
|
|
67
|
+
self.mappings[git_branch] = BranchMappingEntry(keboola_id, name)
|
|
68
|
+
|
|
69
|
+
def remove(self, git_branch: str) -> bool:
|
|
70
|
+
if git_branch in self.mappings:
|
|
71
|
+
del self.mappings[git_branch]
|
|
72
|
+
return True
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def to_dict(self) -> dict[str, Any]:
|
|
76
|
+
return {
|
|
77
|
+
"version": self.version,
|
|
78
|
+
"mappings": {k: v.to_dict() for k, v in self.mappings.items()},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_dict(cls, data: dict[str, Any]) -> BranchMapping:
|
|
83
|
+
mapping = cls()
|
|
84
|
+
mapping.version = data.get("version", 1)
|
|
85
|
+
for git_branch, entry in data.get("mappings", {}).items():
|
|
86
|
+
mapping.mappings[git_branch] = BranchMappingEntry(
|
|
87
|
+
keboola_id=_coerce_keboola_id(entry.get("id")),
|
|
88
|
+
name=entry.get("name", ""),
|
|
89
|
+
)
|
|
90
|
+
return mapping
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def load_branch_mapping(project_root: Path) -> BranchMapping:
|
|
94
|
+
"""Load .keboola/branch-mapping.json.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
FileNotFoundError: If the mapping file does not exist.
|
|
98
|
+
ConfigError: If the JSON cannot be parsed or contains a malformed
|
|
99
|
+
branch ID. The descriptive message names the offending file
|
|
100
|
+
so the user can find and fix it. Surfacing this as ConfigError
|
|
101
|
+
(rather than raw ValueError) lets CLI commands catch it via
|
|
102
|
+
the existing ``except ConfigError`` handler and emit a clean
|
|
103
|
+
JSON error envelope with exit code 5 instead of a Python
|
|
104
|
+
traceback (issue #269 sec-20 + smoke-test follow-up).
|
|
105
|
+
"""
|
|
106
|
+
path = project_root / KEBOOLA_DIR_NAME / BRANCH_MAPPING_FILENAME
|
|
107
|
+
if not path.exists():
|
|
108
|
+
raise FileNotFoundError(f"Branch mapping not found at {path}")
|
|
109
|
+
try:
|
|
110
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
111
|
+
return BranchMapping.from_dict(data)
|
|
112
|
+
except ValueError as exc:
|
|
113
|
+
# _coerce_keboola_id and json.loads both raise ValueError on
|
|
114
|
+
# malformed input; wrap with path context and convert to
|
|
115
|
+
# ConfigError so the CLI surfaces a clean error envelope.
|
|
116
|
+
raise ConfigError(f"Failed to parse {path}: {exc}") from exc
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def save_branch_mapping(project_root: Path, mapping: BranchMapping) -> None:
|
|
120
|
+
"""Save branch mapping to .keboola/branch-mapping.json."""
|
|
121
|
+
path = project_root / KEBOOLA_DIR_NAME / BRANCH_MAPPING_FILENAME
|
|
122
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
path.write_text(
|
|
124
|
+
json.dumps(mapping.to_dict(), indent=4, ensure_ascii=False) + "\n",
|
|
125
|
+
encoding="utf-8",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def find_sync_workspace(start: Path | None = None) -> Path | None:
|
|
130
|
+
"""Locate the nearest enclosing sync workspace.
|
|
131
|
+
|
|
132
|
+
Walks up from *start* (or the current working directory) and returns
|
|
133
|
+
the first directory that contains a ``.keboola/branch-mapping.json``
|
|
134
|
+
file, or ``None`` if none is found before the filesystem root.
|
|
135
|
+
"""
|
|
136
|
+
cursor = (start or Path.cwd()).resolve()
|
|
137
|
+
for candidate in [cursor, *cursor.parents]:
|
|
138
|
+
if (candidate / KEBOOLA_DIR_NAME / BRANCH_MAPPING_FILENAME).exists():
|
|
139
|
+
return candidate
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def cleanup_branch_id_from_mapping(branch_id: int) -> dict[str, Any] | None:
|
|
144
|
+
"""Remove every git-branch entry that maps to *branch_id* from the
|
|
145
|
+
nearest enclosing sync workspace, if one exists.
|
|
146
|
+
|
|
147
|
+
Designed to be a best-effort cleanup hook for ``branch delete`` and
|
|
148
|
+
``branch merge``: locates ``.keboola/branch-mapping.json`` via
|
|
149
|
+
:func:`find_sync_workspace`, removes any entries whose ``keboola_id``
|
|
150
|
+
equals *branch_id*, and persists the change. Returns a dict
|
|
151
|
+
describing what was unlinked, or ``None`` if no workspace was found
|
|
152
|
+
or no entry referenced the branch (no-op).
|
|
153
|
+
"""
|
|
154
|
+
project_root = find_sync_workspace()
|
|
155
|
+
if project_root is None:
|
|
156
|
+
return None
|
|
157
|
+
try:
|
|
158
|
+
mapping = load_branch_mapping(project_root)
|
|
159
|
+
except (FileNotFoundError, ConfigError, ValueError):
|
|
160
|
+
# ValueError preserved for backward compat with callers that may
|
|
161
|
+
# still trigger it via custom paths; ConfigError is the new shape
|
|
162
|
+
# raised by load_branch_mapping itself.
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
removed: list[str] = []
|
|
166
|
+
for git_branch, entry in list(mapping.mappings.items()):
|
|
167
|
+
if entry.keboola_id == branch_id:
|
|
168
|
+
mapping.remove(git_branch)
|
|
169
|
+
removed.append(git_branch)
|
|
170
|
+
|
|
171
|
+
if not removed:
|
|
172
|
+
return None
|
|
173
|
+
save_branch_mapping(project_root, mapping)
|
|
174
|
+
return {"project_root": str(project_root), "git_branches_unlinked": removed}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Pure helpers for the ``sync clone`` composite (#426).
|
|
2
|
+
|
|
3
|
+
A clone copies a *reference* synced project tree into a fresh target, applies
|
|
4
|
+
declarative overrides, and re-points the manifest so a subsequent ``sync push``
|
|
5
|
+
CREATEs everything fresh in the target project. Cloning into a **fresh** target
|
|
6
|
+
project needs no id surgery: the reference's config ids do not exist in the
|
|
7
|
+
target remote, so the diff classifies every config as ``added`` and the push
|
|
8
|
+
assigns new ULIDs -- and because ``created_id_map`` is keyed by the reference id
|
|
9
|
+
(the manifest entry's id before writeback), the Phase-C variable links and the
|
|
10
|
+
Phase-D flow task ``configId``s remap reference->ULID automatically.
|
|
11
|
+
|
|
12
|
+
These functions are deliberately side-effecting but **pure of API calls**: they
|
|
13
|
+
only touch the on-disk tree + the in-memory manifest, so they are unit-testable
|
|
14
|
+
without a client. ``SyncService.clone_project`` orchestrates them and drives the
|
|
15
|
+
diff/push.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import shutil
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
import yaml
|
|
25
|
+
|
|
26
|
+
from ..constants import CONFIG_FILENAME
|
|
27
|
+
|
|
28
|
+
# Duplicated here (not imported from sync_service) to avoid a circular import --
|
|
29
|
+
# clone.py is imported *by* sync_service.
|
|
30
|
+
VARIABLES_COMPONENT_ID = "keboola.variables"
|
|
31
|
+
|
|
32
|
+
# Default branch dir name when a config's branch is not listed in the manifest
|
|
33
|
+
# (non-git-branching projects pull a single "main/" tree).
|
|
34
|
+
_DEFAULT_BRANCH_DIR = "main"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def branch_path_map(manifest: Any) -> dict[int, str]:
|
|
38
|
+
"""Map ``branch_id -> on-disk branch dir name`` from the manifest branches."""
|
|
39
|
+
return {b.id: b.path for b in manifest.branches}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _read_yaml(path: Path) -> dict[str, Any]:
|
|
43
|
+
loaded = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
44
|
+
return loaded if isinstance(loaded, dict) else {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _write_yaml(path: Path, data: dict[str, Any]) -> None:
|
|
48
|
+
path.write_text(
|
|
49
|
+
yaml.dump(data, sort_keys=False, allow_unicode=True, default_flow_style=False),
|
|
50
|
+
encoding="utf-8",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def copy_reference_tree(source_dir: Path, target_dir: Path) -> None:
|
|
55
|
+
"""Copy a reference project tree to a fresh target dir (incl. code files).
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
FileExistsError: if ``target_dir`` already exists (the caller decides
|
|
59
|
+
whether an existing target means "already cloned, just push").
|
|
60
|
+
"""
|
|
61
|
+
if target_dir.exists():
|
|
62
|
+
raise FileExistsError(f"Target directory already exists: {target_dir}")
|
|
63
|
+
shutil.copytree(source_dir, target_dir)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def repoint_manifest_project(manifest: Any, *, project_id: int, api_host: str) -> None:
|
|
67
|
+
"""Re-point the manifest's project block at the clone's target project."""
|
|
68
|
+
manifest.project.id = project_id
|
|
69
|
+
manifest.project.api_host = api_host
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _config_dir(target_dir: Path, branch_map: dict[int, str], branch_id: int, path: str) -> Path:
|
|
73
|
+
return target_dir / branch_map.get(branch_id, _DEFAULT_BRANCH_DIR) / path
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _remap_bucket_in_table_id(table_id: Any, bucket_map: dict[str, str]) -> Any:
|
|
77
|
+
"""Rewrite the bucket prefix of a storage table id via ``bucket_map``.
|
|
78
|
+
|
|
79
|
+
A table id is ``<stage>.<bucket>.<table>`` whose bucket id is
|
|
80
|
+
``<stage>.<bucket>``. A bucket-level reference (``in.c-foo``) is mapped
|
|
81
|
+
whole; a table reference (``in.c-foo.users``) has only its bucket prefix
|
|
82
|
+
swapped. Unmatched / non-string values pass through unchanged.
|
|
83
|
+
"""
|
|
84
|
+
if not isinstance(table_id, str):
|
|
85
|
+
return table_id
|
|
86
|
+
for old, new in bucket_map.items():
|
|
87
|
+
if table_id == old:
|
|
88
|
+
return new
|
|
89
|
+
if table_id.startswith(old + "."):
|
|
90
|
+
return new + table_id[len(old) :]
|
|
91
|
+
return table_id
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _rewrite_buckets_in_config(config_dir: Path, bucket_map: dict[str, str]) -> int:
|
|
95
|
+
config_file = config_dir / CONFIG_FILENAME
|
|
96
|
+
if not config_file.exists():
|
|
97
|
+
return 0
|
|
98
|
+
data = _read_yaml(config_file)
|
|
99
|
+
rewrites = 0
|
|
100
|
+
for section, key in (("input", "source"), ("output", "destination")):
|
|
101
|
+
mapping = data.get(section)
|
|
102
|
+
if not isinstance(mapping, dict):
|
|
103
|
+
continue
|
|
104
|
+
for table in mapping.get("tables") or []:
|
|
105
|
+
if not isinstance(table, dict) or key not in table:
|
|
106
|
+
continue
|
|
107
|
+
new_value = _remap_bucket_in_table_id(table[key], bucket_map)
|
|
108
|
+
if new_value != table[key]:
|
|
109
|
+
table[key] = new_value
|
|
110
|
+
rewrites += 1
|
|
111
|
+
if rewrites:
|
|
112
|
+
_write_yaml(config_file, data)
|
|
113
|
+
return rewrites
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def apply_bucket_map(target_dir: Path, manifest: Any, bucket_map: dict[str, str]) -> int:
|
|
117
|
+
"""Rewrite bucket ids in every config's storage input/output mappings.
|
|
118
|
+
|
|
119
|
+
Returns the number of table references rewritten. A no-op when
|
|
120
|
+
``bucket_map`` is empty.
|
|
121
|
+
"""
|
|
122
|
+
if not bucket_map:
|
|
123
|
+
return 0
|
|
124
|
+
branch_map = branch_path_map(manifest)
|
|
125
|
+
rewrites = 0
|
|
126
|
+
for cfg in manifest.configurations:
|
|
127
|
+
rewrites += _rewrite_buckets_in_config(
|
|
128
|
+
_config_dir(target_dir, branch_map, cfg.branch_id, cfg.path), bucket_map
|
|
129
|
+
)
|
|
130
|
+
for row in cfg.rows:
|
|
131
|
+
rewrites += _rewrite_buckets_in_config(
|
|
132
|
+
_config_dir(target_dir, branch_map, cfg.branch_id, row.path), bucket_map
|
|
133
|
+
)
|
|
134
|
+
return rewrites
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _override_values_in_row(row_dir: Path, variable_values: dict[str, str]) -> int:
|
|
138
|
+
config_file = row_dir / CONFIG_FILENAME
|
|
139
|
+
if not config_file.exists():
|
|
140
|
+
return 0
|
|
141
|
+
data = _read_yaml(config_file)
|
|
142
|
+
# keboola.variables rows hoist their ``values`` array to the YAML top level.
|
|
143
|
+
values = data.get("values")
|
|
144
|
+
if not isinstance(values, list):
|
|
145
|
+
return 0
|
|
146
|
+
overridden = 0
|
|
147
|
+
for item in values:
|
|
148
|
+
if isinstance(item, dict) and item.get("name") in variable_values:
|
|
149
|
+
new_value = str(variable_values[item["name"]])
|
|
150
|
+
if item.get("value") != new_value:
|
|
151
|
+
item["value"] = new_value
|
|
152
|
+
overridden += 1
|
|
153
|
+
if overridden:
|
|
154
|
+
_write_yaml(config_file, data)
|
|
155
|
+
return overridden
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def apply_variable_values(target_dir: Path, manifest: Any, variable_values: dict[str, str]) -> int:
|
|
159
|
+
"""Override ``keboola.variables`` row values by variable name.
|
|
160
|
+
|
|
161
|
+
Returns the number of values overridden. A no-op when ``variable_values``
|
|
162
|
+
is empty. Values are written as strings (the Keboola variables contract).
|
|
163
|
+
"""
|
|
164
|
+
if not variable_values:
|
|
165
|
+
return 0
|
|
166
|
+
branch_map = branch_path_map(manifest)
|
|
167
|
+
overridden = 0
|
|
168
|
+
for cfg in manifest.configurations:
|
|
169
|
+
if cfg.component_id != VARIABLES_COMPONENT_ID:
|
|
170
|
+
continue
|
|
171
|
+
for row in cfg.rows:
|
|
172
|
+
overridden += _override_values_in_row(
|
|
173
|
+
_config_dir(target_dir, branch_map, cfg.branch_id, row.path), variable_values
|
|
174
|
+
)
|
|
175
|
+
return overridden
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def apply_instance_rename(target_dir: Path, manifest: Any, renames: dict[str, str]) -> int:
|
|
179
|
+
"""Rename config-path prefixes on disk and in the manifest.
|
|
180
|
+
|
|
181
|
+
``renames`` maps an old path prefix to a new one (e.g.
|
|
182
|
+
``{"extractor/keboola.ex-http/Acme": "extractor/keboola.ex-http/Globex"}``).
|
|
183
|
+
For every config (and its rows) whose manifest ``path`` equals the prefix or
|
|
184
|
+
starts with ``prefix + "/"``, the on-disk subtree is moved once and the
|
|
185
|
+
manifest paths are rewritten. Returns the number of configs whose path
|
|
186
|
+
changed. A no-op when ``renames`` is empty.
|
|
187
|
+
"""
|
|
188
|
+
if not renames:
|
|
189
|
+
return 0
|
|
190
|
+
branch_map = branch_path_map(manifest)
|
|
191
|
+
moved: set[tuple[str, str]] = set()
|
|
192
|
+
renamed = 0
|
|
193
|
+
for old, new in renames.items():
|
|
194
|
+
for cfg in manifest.configurations:
|
|
195
|
+
if not (cfg.path == old or cfg.path.startswith(old + "/")):
|
|
196
|
+
continue
|
|
197
|
+
branch_dir = branch_map.get(cfg.branch_id, _DEFAULT_BRANCH_DIR)
|
|
198
|
+
move_key = (branch_dir, old)
|
|
199
|
+
if move_key not in moved:
|
|
200
|
+
src = target_dir / branch_dir / old
|
|
201
|
+
dst = target_dir / branch_dir / new
|
|
202
|
+
if src.exists():
|
|
203
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
204
|
+
shutil.move(str(src), str(dst))
|
|
205
|
+
moved.add(move_key)
|
|
206
|
+
cfg.path = new + cfg.path[len(old) :]
|
|
207
|
+
for row in cfg.rows:
|
|
208
|
+
if row.path == old or row.path.startswith(old + "/"):
|
|
209
|
+
row.path = new + row.path[len(old) :]
|
|
210
|
+
renamed += 1
|
|
211
|
+
return renamed
|