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,939 @@
|
|
|
1
|
+
"""Typer wrappers for the ``kbagent semantic-layer`` command group.
|
|
2
|
+
|
|
3
|
+
Thin layer per the 3-layer architecture: parse arguments, call
|
|
4
|
+
:class:`SemanticLayerService`, format output. All business logic lives in the
|
|
5
|
+
service layer.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
from ..errors import ErrorCode
|
|
18
|
+
from ._helpers import (
|
|
19
|
+
check_cli_permission,
|
|
20
|
+
get_formatter,
|
|
21
|
+
get_service,
|
|
22
|
+
)
|
|
23
|
+
from ._semantic_layer_crud import add_app, edit_app, remove_app
|
|
24
|
+
from ._semantic_layer_helpers import _handle_service_call
|
|
25
|
+
from ._semantic_layer_reference_data import reference_data_app
|
|
26
|
+
|
|
27
|
+
semantic_layer_app = typer.Typer(
|
|
28
|
+
name="semantic-layer",
|
|
29
|
+
help=(
|
|
30
|
+
"Manage Keboola semantic layer (metastore) models -- datasets, metrics, "
|
|
31
|
+
"relationships, constraints, and glossary terms."
|
|
32
|
+
),
|
|
33
|
+
no_args_is_help=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@semantic_layer_app.callback(invoke_without_command=True)
|
|
38
|
+
def _semantic_layer_permission_check(ctx: typer.Context) -> None:
|
|
39
|
+
"""Enforce permission policy for every semantic-layer subcommand."""
|
|
40
|
+
check_cli_permission(ctx, "semantic-layer")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# semantic-layer model -- model lifecycle (list)
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
model_app = typer.Typer(
|
|
48
|
+
name="model",
|
|
49
|
+
help="Manage semantic-layer models (list models in a project).",
|
|
50
|
+
no_args_is_help=True,
|
|
51
|
+
)
|
|
52
|
+
semantic_layer_app.add_typer(model_app, name="model")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@model_app.callback(invoke_without_command=True)
|
|
56
|
+
def _model_permission_check(ctx: typer.Context) -> None:
|
|
57
|
+
"""Per-subcommand permission check for the ``model`` sub-app.
|
|
58
|
+
|
|
59
|
+
Uses the standard ``check_cli_permission`` helper which composes the
|
|
60
|
+
operation key as ``"{group}.{subcommand}"`` — so we get
|
|
61
|
+
``semantic-layer.model.list`` (read), ``semantic-layer.model.create``
|
|
62
|
+
(write), and ``semantic-layer.model.delete`` (destructive). A collapsed
|
|
63
|
+
single ``semantic-layer.model`` key would deny `model list` under
|
|
64
|
+
``--deny-writes`` even though it's read-only.
|
|
65
|
+
"""
|
|
66
|
+
check_cli_permission(ctx, "semantic-layer.model")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _print_models_table(console: Console, data: dict) -> None:
|
|
70
|
+
"""Pretty-print the list of models for a project."""
|
|
71
|
+
project = data.get("project", "")
|
|
72
|
+
models = data.get("models", [])
|
|
73
|
+
if not models:
|
|
74
|
+
console.print(f"[dim]No semantic-layer models in project '{project}'.[/dim]")
|
|
75
|
+
return
|
|
76
|
+
table = Table(title=f"Semantic-layer models in '{project}'")
|
|
77
|
+
table.add_column("Name", style="bold cyan")
|
|
78
|
+
table.add_column("UUID", style="dim")
|
|
79
|
+
table.add_column("SQL Dialect")
|
|
80
|
+
table.add_column("Description", max_width=60)
|
|
81
|
+
for m in models:
|
|
82
|
+
table.add_row(
|
|
83
|
+
m.get("name", ""),
|
|
84
|
+
m.get("id", ""),
|
|
85
|
+
m.get("sql_dialect", ""),
|
|
86
|
+
m.get("description", ""),
|
|
87
|
+
)
|
|
88
|
+
console.print(table)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@model_app.command("list")
|
|
92
|
+
def model_list(
|
|
93
|
+
ctx: typer.Context,
|
|
94
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
95
|
+
) -> None:
|
|
96
|
+
"""List all semantic-layer models in a project."""
|
|
97
|
+
formatter = get_formatter(ctx)
|
|
98
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
99
|
+
result = _handle_service_call(ctx, service.list_models, alias=project)
|
|
100
|
+
formatter.output(result, _print_models_table)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@model_app.command("create")
|
|
104
|
+
def model_create(
|
|
105
|
+
ctx: typer.Context,
|
|
106
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
107
|
+
name: str = typer.Option(..., "--name", help="Model name (unique within project)"),
|
|
108
|
+
description: str = typer.Option("", "--description", help="Optional description"),
|
|
109
|
+
sql_dialect: str = typer.Option(
|
|
110
|
+
"Snowflake", "--sql-dialect", help="SQL dialect (default: Snowflake)"
|
|
111
|
+
),
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Create a new semantic-layer model."""
|
|
114
|
+
formatter = get_formatter(ctx)
|
|
115
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
116
|
+
result = _handle_service_call(
|
|
117
|
+
ctx,
|
|
118
|
+
service.create_model,
|
|
119
|
+
alias=project,
|
|
120
|
+
name=name,
|
|
121
|
+
description=description,
|
|
122
|
+
sql_dialect=sql_dialect,
|
|
123
|
+
)
|
|
124
|
+
formatter.output(
|
|
125
|
+
result,
|
|
126
|
+
lambda c, d: c.print(
|
|
127
|
+
f"[bold green]Created model[/bold green] [cyan]{d['model']['attributes']['name']}[/cyan] "
|
|
128
|
+
f"([dim]{d['model']['id']}[/dim])"
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# semantic-layer add | edit | remove -- mounted from a sibling module to keep
|
|
135
|
+
# this commands file under the CONTRIBUTING.md hard ceiling. See
|
|
136
|
+
# :mod:`commands._semantic_layer_crud` for the sub-app implementations.
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
semantic_layer_app.add_typer(add_app, name="add")
|
|
140
|
+
semantic_layer_app.add_typer(edit_app, name="edit")
|
|
141
|
+
semantic_layer_app.add_typer(remove_app, name="remove")
|
|
142
|
+
semantic_layer_app.add_typer(reference_data_app, name="reference-data")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@model_app.command("delete")
|
|
146
|
+
def model_delete(
|
|
147
|
+
ctx: typer.Context,
|
|
148
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
149
|
+
model: str = typer.Option(..., "--model", help="Model name or UUID"),
|
|
150
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt"),
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Delete a semantic-layer model and cascade-delete its children."""
|
|
153
|
+
formatter = get_formatter(ctx)
|
|
154
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
not yes
|
|
158
|
+
and not formatter.json_mode
|
|
159
|
+
and not typer.confirm(
|
|
160
|
+
f"Delete model '{model}' in project '{project}'? "
|
|
161
|
+
"This cascade-deletes ALL child entities (datasets, metrics, "
|
|
162
|
+
"relationships, constraints, glossary terms) belonging to the model. "
|
|
163
|
+
"This is irreversible."
|
|
164
|
+
)
|
|
165
|
+
):
|
|
166
|
+
formatter.console.print("Aborted.")
|
|
167
|
+
raise typer.Exit(code=0)
|
|
168
|
+
|
|
169
|
+
result = _handle_service_call(
|
|
170
|
+
ctx,
|
|
171
|
+
service.delete_model,
|
|
172
|
+
alias=project,
|
|
173
|
+
model_name_or_uuid=model,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def _render(console: Console, data: dict) -> None:
|
|
177
|
+
cascaded = sum(data.get("cascade", {}).get("deleted", {}).values())
|
|
178
|
+
suffix = f" + cascaded {cascaded} child(ren)" if cascaded else ""
|
|
179
|
+
console.print(
|
|
180
|
+
f"[bold green]Deleted model[/bold green] "
|
|
181
|
+
f"[cyan]{data['deleted']['name']}[/cyan] "
|
|
182
|
+
f"([dim]{data['deleted']['id']}[/dim]){suffix}"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
formatter.output(result, _render)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
# semantic-layer show
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _print_show_summary(console: Console, data: dict) -> None:
|
|
194
|
+
"""Render the show payload (no --type) as a compact count table."""
|
|
195
|
+
project = data.get("project", "")
|
|
196
|
+
model = data.get("model", {})
|
|
197
|
+
console.print(
|
|
198
|
+
f"\n[bold]Model[/bold] [cyan]{model.get('name', '?')}[/cyan] "
|
|
199
|
+
f"([dim]{model.get('id', '')}[/dim]) in project [magenta]{project}[/magenta]:"
|
|
200
|
+
)
|
|
201
|
+
table = Table()
|
|
202
|
+
table.add_column("Entity", style="bold cyan")
|
|
203
|
+
table.add_column("Count", justify="right")
|
|
204
|
+
for label, key in (
|
|
205
|
+
("datasets", "datasets"),
|
|
206
|
+
("metrics", "metrics"),
|
|
207
|
+
("relationships", "relationships"),
|
|
208
|
+
("constraints", "constraints"),
|
|
209
|
+
("glossary", "glossary"),
|
|
210
|
+
):
|
|
211
|
+
if key in data:
|
|
212
|
+
table.add_row(label, str(len(data.get(key, []))))
|
|
213
|
+
console.print(table)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _print_show_detail(console: Console, data: dict) -> None:
|
|
217
|
+
"""Render the show payload with --type as a per-item table."""
|
|
218
|
+
for type_key in ("datasets", "metrics", "relationships", "constraints", "glossary"):
|
|
219
|
+
if type_key not in data:
|
|
220
|
+
continue
|
|
221
|
+
items = data[type_key]
|
|
222
|
+
if not items:
|
|
223
|
+
console.print(f"[dim]No {type_key} in this model.[/dim]")
|
|
224
|
+
continue
|
|
225
|
+
table = Table(title=type_key.capitalize())
|
|
226
|
+
# Column layout differs per type, but a generic name/id/key-attrs
|
|
227
|
+
# rendering keeps the table compact and is enough for `show`.
|
|
228
|
+
keys = list(items[0].keys())
|
|
229
|
+
# Drop modelUUID -- noisy, present on every item
|
|
230
|
+
keys = [k for k in keys if k != "modelUUID"]
|
|
231
|
+
for col in keys:
|
|
232
|
+
table.add_column(col)
|
|
233
|
+
for item in items:
|
|
234
|
+
table.add_row(*(str(item.get(c, "")) for c in keys))
|
|
235
|
+
console.print(table)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# semantic-layer import -- replay a snapshot
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _print_import_result(console: Console, data: dict) -> None:
|
|
244
|
+
console.print(
|
|
245
|
+
f"\n[bold]Import[/bold] into [magenta]{data.get('target_project')}[/magenta]"
|
|
246
|
+
f" (model {data.get('target_model')}, "
|
|
247
|
+
f"dry_run={data.get('dry_run')}, overwrite={data.get('overwrite')}):"
|
|
248
|
+
)
|
|
249
|
+
table = Table()
|
|
250
|
+
table.add_column("Type", style="bold cyan")
|
|
251
|
+
table.add_column("Created", justify="right", style="green")
|
|
252
|
+
table.add_column("Skipped", justify="right", style="yellow")
|
|
253
|
+
table.add_column("Overwritten", justify="right", style="magenta")
|
|
254
|
+
table.add_column("Failed", justify="right", style="red")
|
|
255
|
+
for plural in ("datasets", "metrics", "relationships", "glossary", "constraints"):
|
|
256
|
+
per = (data.get("imported") or {}).get(plural)
|
|
257
|
+
if per is None:
|
|
258
|
+
continue
|
|
259
|
+
table.add_row(
|
|
260
|
+
plural,
|
|
261
|
+
str(per.get("created", 0)),
|
|
262
|
+
str(per.get("skipped", 0)),
|
|
263
|
+
str(per.get("overwritten", 0)),
|
|
264
|
+
str(len(per.get("failed", []))),
|
|
265
|
+
)
|
|
266
|
+
console.print(table)
|
|
267
|
+
for plural, per in (data.get("imported") or {}).items():
|
|
268
|
+
for f in per.get("failed", []):
|
|
269
|
+
console.print(f" [red]✗ {plural}.{f.get('name')}: {f.get('reason')}[/red]")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
# semantic-layer promote -- cross-project copy
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _print_promote_result(console: Console, data: dict) -> None:
|
|
278
|
+
console.print(
|
|
279
|
+
f"\n[bold]Promote[/bold] [magenta]{data.get('from_project')}[/magenta] "
|
|
280
|
+
f"-> [magenta]{data.get('to_project')}[/magenta] "
|
|
281
|
+
f"(dry_run={data.get('dry_run')}):"
|
|
282
|
+
)
|
|
283
|
+
table = Table()
|
|
284
|
+
table.add_column("Type", style="bold cyan")
|
|
285
|
+
table.add_column("New", justify="right", style="green")
|
|
286
|
+
table.add_column("Overwritten", justify="right", style="magenta")
|
|
287
|
+
table.add_column("Identical", justify="right", style="dim")
|
|
288
|
+
table.add_column("Failed", justify="right", style="red")
|
|
289
|
+
for plural in ("datasets", "metrics", "relationships", "glossary", "constraints"):
|
|
290
|
+
per = data.get(plural)
|
|
291
|
+
if per is None:
|
|
292
|
+
continue
|
|
293
|
+
table.add_row(
|
|
294
|
+
plural,
|
|
295
|
+
str(per.get("new", 0)),
|
|
296
|
+
str(per.get("overwritten", 0)),
|
|
297
|
+
str(per.get("identical", 0)),
|
|
298
|
+
str(len(per.get("failed", []))),
|
|
299
|
+
)
|
|
300
|
+
console.print(table)
|
|
301
|
+
for plural in ("datasets", "metrics", "relationships", "glossary", "constraints"):
|
|
302
|
+
per = data.get(plural) or {}
|
|
303
|
+
for c in per.get("changes", []):
|
|
304
|
+
key = c.get("name", c.get("term", "?"))
|
|
305
|
+
keys = ", ".join(c.get("diff_keys", []))
|
|
306
|
+
console.print(f" [yellow]~ {plural}.{key}[/yellow] ([dim]{keys}[/dim])")
|
|
307
|
+
for f in per.get("failed", []):
|
|
308
|
+
console.print(f" [red]✗ {plural}.{f.get('name')}: {f.get('reason')}[/red]")
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
# semantic-layer build -- non-interactive greenfield
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _print_build_result(console: Console, data: dict) -> None:
|
|
317
|
+
fallback = data.get("fallback_used")
|
|
318
|
+
valid = data.get("validated", False)
|
|
319
|
+
console.print(
|
|
320
|
+
f"\n[bold]Build[/bold] (mode=[cyan]{fallback}[/cyan], dry_run={data.get('dry_run')})"
|
|
321
|
+
)
|
|
322
|
+
val = data.get("validation") or {}
|
|
323
|
+
errs = val.get("errors", [])
|
|
324
|
+
warns = val.get("warnings", [])
|
|
325
|
+
console.print(f" Datasets: {len(data.get('generated', {}).get('datasets', []))}")
|
|
326
|
+
console.print(f" Metrics: {len(data.get('generated', {}).get('metrics', []))}")
|
|
327
|
+
console.print(f" Relationships: {len(data.get('generated', {}).get('relationships', []))}")
|
|
328
|
+
console.print(f" Constraints: {len(data.get('generated', {}).get('constraints', []))}")
|
|
329
|
+
console.print(f" Glossary: {len(data.get('generated', {}).get('glossary', []))}")
|
|
330
|
+
if data.get("output_path"):
|
|
331
|
+
console.print(f"\nWritten to: [cyan]{data['output_path']}[/cyan]")
|
|
332
|
+
if errs:
|
|
333
|
+
console.print(f"\n[bold red]Validation: {len(errs)} error(s)[/bold red]")
|
|
334
|
+
for e in errs:
|
|
335
|
+
console.print(f" [red]✗[/red] {e['type']} {e['item']} — {e['detail']}")
|
|
336
|
+
if warns:
|
|
337
|
+
console.print(f"\n[bold yellow]Validation: {len(warns)} warning(s)[/bold yellow]")
|
|
338
|
+
if data.get("created"):
|
|
339
|
+
console.print(f"\nCreated: {data['created']}")
|
|
340
|
+
elif data.get("dry_run"):
|
|
341
|
+
console.print("\n[dim]--dry-run: no API calls were made.[/dim]")
|
|
342
|
+
elif valid:
|
|
343
|
+
console.print("\n[bold green]Pushed.[/bold green]")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# ---------------------------------------------------------------------------
|
|
347
|
+
# semantic-layer token --encrypt
|
|
348
|
+
# ---------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@semantic_layer_app.command("token")
|
|
352
|
+
def semantic_layer_token(
|
|
353
|
+
ctx: typer.Context,
|
|
354
|
+
encrypt: bool = typer.Option(
|
|
355
|
+
False, "--encrypt", help="Encrypt the project token for `user_properties` (required)"
|
|
356
|
+
),
|
|
357
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
358
|
+
component_id: str = typer.Option(
|
|
359
|
+
...,
|
|
360
|
+
"--component-id",
|
|
361
|
+
help="Keboola component id the encrypted token will be used in",
|
|
362
|
+
),
|
|
363
|
+
) -> None:
|
|
364
|
+
"""Encrypt the project's storage token for transformation `user_properties`.
|
|
365
|
+
|
|
366
|
+
Builds the ``{"#metastore_token": <token>}`` payload using the
|
|
367
|
+
project's already-stored Storage API token (no config-file digging),
|
|
368
|
+
then delegates to the existing EncryptService. Output (human) is the
|
|
369
|
+
raw envelope ready to paste into `user_properties`; JSON mode emits
|
|
370
|
+
the full `{encrypted, component_id, project}` response.
|
|
371
|
+
"""
|
|
372
|
+
formatter = get_formatter(ctx)
|
|
373
|
+
if not encrypt:
|
|
374
|
+
formatter.error(
|
|
375
|
+
message=(
|
|
376
|
+
"Currently only --encrypt mode is supported. "
|
|
377
|
+
"Pass --encrypt to encrypt the project token for user_properties."
|
|
378
|
+
),
|
|
379
|
+
error_code=ErrorCode.USAGE_ERROR,
|
|
380
|
+
)
|
|
381
|
+
raise typer.Exit(code=2)
|
|
382
|
+
|
|
383
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
384
|
+
result = _handle_service_call(
|
|
385
|
+
ctx,
|
|
386
|
+
service.encrypt_token,
|
|
387
|
+
alias=project,
|
|
388
|
+
component_id=component_id,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def _print_encrypted_token(console: Console, data: dict) -> None:
|
|
392
|
+
console.print(
|
|
393
|
+
"\n[bold green]Encrypted token[/bold green] "
|
|
394
|
+
f"for component [cyan]{data['component_id']}[/cyan] "
|
|
395
|
+
f"in project [magenta]{data['project']}[/magenta]:"
|
|
396
|
+
)
|
|
397
|
+
console.print_json(json.dumps(data["encrypted"], indent=2))
|
|
398
|
+
console.print(
|
|
399
|
+
"[dim]Paste the JSON above into the transformation's `user_properties` block.[/dim]"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
formatter.output(result, _print_encrypted_token)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@semantic_layer_app.command("build")
|
|
406
|
+
def semantic_layer_build(
|
|
407
|
+
ctx: typer.Context,
|
|
408
|
+
project: str = typer.Option(..., "--project", help="Target project alias"),
|
|
409
|
+
model: str | None = typer.Option(
|
|
410
|
+
None,
|
|
411
|
+
"--model",
|
|
412
|
+
help="Update this existing model (name or UUID). If omitted, a new model is created.",
|
|
413
|
+
),
|
|
414
|
+
tables: str | None = typer.Option(
|
|
415
|
+
None,
|
|
416
|
+
"--tables",
|
|
417
|
+
help="Comma-separated tableIds to base the model on (required).",
|
|
418
|
+
),
|
|
419
|
+
name: str | None = typer.Option(
|
|
420
|
+
None, "--name", help="Model name when creating (default: kbagent_build_model)."
|
|
421
|
+
),
|
|
422
|
+
dry_run: bool = typer.Option(
|
|
423
|
+
False, "--dry-run", help="Print the generated JSON + validation, no POST."
|
|
424
|
+
),
|
|
425
|
+
keep_on_failure: bool = typer.Option(
|
|
426
|
+
False,
|
|
427
|
+
"--keep-on-failure",
|
|
428
|
+
help=(
|
|
429
|
+
"Keep partially-pushed model + children on failure (forensics). "
|
|
430
|
+
"Default: rollback in reverse PUSH_ORDER + delete the model if "
|
|
431
|
+
"we created it."
|
|
432
|
+
),
|
|
433
|
+
),
|
|
434
|
+
output: Path | None = typer.Option(
|
|
435
|
+
None, "--output", help="Also write the generated JSON to this file."
|
|
436
|
+
),
|
|
437
|
+
) -> None:
|
|
438
|
+
"""Build a semantic-layer model from a list of storage tables (non-interactive).
|
|
439
|
+
|
|
440
|
+
NOTE: The AI Service client currently has no JSON-generation endpoint, so
|
|
441
|
+
this command falls back to a DETERMINISTIC HEURISTIC builder (one dataset
|
|
442
|
+
+ one COUNT(*) metric + one glossary entry per table; no relationships;
|
|
443
|
+
no constraints). The intent is "best starting point" — iterate with
|
|
444
|
+
`add` / `edit`. The fallback is logged in the response as
|
|
445
|
+
`fallback_used: "heuristic"` so callers can detect it.
|
|
446
|
+
"""
|
|
447
|
+
formatter = get_formatter(ctx)
|
|
448
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
449
|
+
|
|
450
|
+
if not tables:
|
|
451
|
+
formatter.error(
|
|
452
|
+
message="--tables is required (comma-separated tableIds).",
|
|
453
|
+
error_code=ErrorCode.MISSING_PARAMETER,
|
|
454
|
+
)
|
|
455
|
+
raise typer.Exit(code=2)
|
|
456
|
+
table_ids = [t.strip() for t in tables.split(",") if t.strip()]
|
|
457
|
+
if not table_ids:
|
|
458
|
+
formatter.error(
|
|
459
|
+
message="--tables contained no tableIds.",
|
|
460
|
+
error_code=ErrorCode.VALIDATION_ERROR,
|
|
461
|
+
)
|
|
462
|
+
raise typer.Exit(code=2)
|
|
463
|
+
|
|
464
|
+
result = _handle_service_call(
|
|
465
|
+
ctx,
|
|
466
|
+
service.build_model,
|
|
467
|
+
alias=project,
|
|
468
|
+
table_ids=table_ids,
|
|
469
|
+
model_name=name,
|
|
470
|
+
model_name_or_uuid=model,
|
|
471
|
+
dry_run=dry_run,
|
|
472
|
+
keep_on_failure=keep_on_failure,
|
|
473
|
+
output_path=output,
|
|
474
|
+
)
|
|
475
|
+
formatter.output(result, _print_build_result)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@semantic_layer_app.command("promote")
|
|
479
|
+
def semantic_layer_promote(
|
|
480
|
+
ctx: typer.Context,
|
|
481
|
+
from_project: str = typer.Option(..., "--from-project", help="Source project alias"),
|
|
482
|
+
to_project: str = typer.Option(..., "--to-project", help="Target project alias"),
|
|
483
|
+
from_model: str | None = typer.Option(
|
|
484
|
+
None, "--from-model", help="Source model name or UUID (defaults to sole)"
|
|
485
|
+
),
|
|
486
|
+
to_model: str | None = typer.Option(
|
|
487
|
+
None, "--to-model", help="Target model name or UUID (defaults to sole)"
|
|
488
|
+
),
|
|
489
|
+
types: str | None = typer.Option(
|
|
490
|
+
None,
|
|
491
|
+
"--types",
|
|
492
|
+
help="Comma-separated subset (datasets,metrics,relationships,glossary,constraints)",
|
|
493
|
+
),
|
|
494
|
+
dry_run: bool = typer.Option(
|
|
495
|
+
False, "--dry-run", help="Classify NEW/IDENTICAL/CHANGED without writing"
|
|
496
|
+
),
|
|
497
|
+
yes: bool = typer.Option(
|
|
498
|
+
False, "--yes", "-y", help="Skip the cross-project confirmation prompt"
|
|
499
|
+
),
|
|
500
|
+
) -> None:
|
|
501
|
+
"""Promote a model from one project to another (NEW + overwrite CHANGED; never deletes).
|
|
502
|
+
|
|
503
|
+
Default behaviour: NEW items are POSTed, CHANGED items are
|
|
504
|
+
DELETE+POSTed, IDENTICAL items are skipped. Items only present in
|
|
505
|
+
the target are never touched (additive-only).
|
|
506
|
+
"""
|
|
507
|
+
formatter = get_formatter(ctx)
|
|
508
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
509
|
+
type_list = [t.strip() for t in types.split(",") if t.strip()] if types else None
|
|
510
|
+
|
|
511
|
+
if (
|
|
512
|
+
not yes
|
|
513
|
+
and not dry_run
|
|
514
|
+
and not formatter.json_mode
|
|
515
|
+
and not typer.confirm(
|
|
516
|
+
f"Promote model from '{from_project}' to '{to_project}'? "
|
|
517
|
+
"This will overwrite CHANGED items in the target."
|
|
518
|
+
)
|
|
519
|
+
):
|
|
520
|
+
formatter.console.print("Aborted.")
|
|
521
|
+
raise typer.Exit(code=0)
|
|
522
|
+
|
|
523
|
+
result = _handle_service_call(
|
|
524
|
+
ctx,
|
|
525
|
+
service.promote_model,
|
|
526
|
+
from_project=from_project,
|
|
527
|
+
to_project=to_project,
|
|
528
|
+
from_model=from_model,
|
|
529
|
+
to_model=to_model,
|
|
530
|
+
types=type_list,
|
|
531
|
+
dry_run=dry_run,
|
|
532
|
+
)
|
|
533
|
+
formatter.output(result, _print_promote_result)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@semantic_layer_app.command("import")
|
|
537
|
+
def semantic_layer_import(
|
|
538
|
+
ctx: typer.Context,
|
|
539
|
+
project: str = typer.Option(..., "--project", help="Target project alias"),
|
|
540
|
+
file: Path = typer.Option(
|
|
541
|
+
..., "--file", help="Snapshot JSON file (output of `semantic-layer export`)"
|
|
542
|
+
),
|
|
543
|
+
model: str | None = typer.Option(
|
|
544
|
+
None, "--model", help="Target model name or UUID (defaults to the sole model)"
|
|
545
|
+
),
|
|
546
|
+
types: str | None = typer.Option(
|
|
547
|
+
None,
|
|
548
|
+
"--types",
|
|
549
|
+
help=(
|
|
550
|
+
"Comma-separated subset to import: datasets,metrics,relationships,glossary,constraints"
|
|
551
|
+
),
|
|
552
|
+
),
|
|
553
|
+
dry_run: bool = typer.Option(
|
|
554
|
+
False, "--dry-run", help="Plan the import without calling any write API"
|
|
555
|
+
),
|
|
556
|
+
overwrite: bool = typer.Option(
|
|
557
|
+
False, "--overwrite", help="DELETE+POST conflicting items (default: skip)"
|
|
558
|
+
),
|
|
559
|
+
yes: bool = typer.Option(
|
|
560
|
+
False, "--yes", "-y", help="Skip confirmation (alias for default SKIP behavior)"
|
|
561
|
+
),
|
|
562
|
+
) -> None:
|
|
563
|
+
"""Replay a snapshot into a project. Default: skip on conflict (no surprise overwrites)."""
|
|
564
|
+
formatter = get_formatter(ctx)
|
|
565
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
566
|
+
# --yes is the alias for the default skip-on-conflict mode; users can
|
|
567
|
+
# still opt into destructive overwrite via --overwrite.
|
|
568
|
+
_ = yes # explicit (no behavioural effect when --overwrite is False)
|
|
569
|
+
type_list = [t.strip() for t in types.split(",") if t.strip()] if types else None
|
|
570
|
+
result = _handle_service_call(
|
|
571
|
+
ctx,
|
|
572
|
+
service.import_snapshot,
|
|
573
|
+
alias=project,
|
|
574
|
+
file=file,
|
|
575
|
+
model_name_or_uuid=model,
|
|
576
|
+
types=type_list,
|
|
577
|
+
dry_run=dry_run,
|
|
578
|
+
overwrite=overwrite,
|
|
579
|
+
)
|
|
580
|
+
formatter.output(result, _print_import_result)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
@semantic_layer_app.command("show")
|
|
584
|
+
def semantic_layer_show(
|
|
585
|
+
ctx: typer.Context,
|
|
586
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
587
|
+
model: str | None = typer.Option(
|
|
588
|
+
None,
|
|
589
|
+
"--model",
|
|
590
|
+
help="Model name or UUID. Optional when the project has a single model.",
|
|
591
|
+
),
|
|
592
|
+
type_filter: str | None = typer.Option(
|
|
593
|
+
None,
|
|
594
|
+
"--type",
|
|
595
|
+
help=(
|
|
596
|
+
"Filter to one entity type: dataset | metric | relationship | constraint | glossary."
|
|
597
|
+
),
|
|
598
|
+
),
|
|
599
|
+
) -> None:
|
|
600
|
+
"""Show the entities in a semantic-layer model."""
|
|
601
|
+
formatter = get_formatter(ctx)
|
|
602
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
603
|
+
result = _handle_service_call(
|
|
604
|
+
ctx,
|
|
605
|
+
service.show_model,
|
|
606
|
+
alias=project,
|
|
607
|
+
model_name_or_uuid=model,
|
|
608
|
+
type_filter=type_filter,
|
|
609
|
+
)
|
|
610
|
+
if type_filter is None:
|
|
611
|
+
formatter.output(result, _print_show_summary)
|
|
612
|
+
else:
|
|
613
|
+
formatter.output(result, _print_show_detail)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
# ---------------------------------------------------------------------------
|
|
617
|
+
# semantic-layer validate [--deep]
|
|
618
|
+
# ---------------------------------------------------------------------------
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def _print_validate(console: Console, data: dict) -> None:
|
|
622
|
+
project = data.get("project", "")
|
|
623
|
+
model = data.get("model", {})
|
|
624
|
+
deep = data.get("deep", False)
|
|
625
|
+
valid = data.get("valid", False)
|
|
626
|
+
console.print(
|
|
627
|
+
f"\n[bold]Validation[/bold] for model [cyan]{model.get('name', '?')}[/cyan] "
|
|
628
|
+
f"in [magenta]{project}[/magenta]"
|
|
629
|
+
f" ({'deep' if deep else 'basic'}):"
|
|
630
|
+
)
|
|
631
|
+
errs = data.get("errors", [])
|
|
632
|
+
warns = data.get("warnings", [])
|
|
633
|
+
if errs:
|
|
634
|
+
console.print(f"\n[bold red]Errors ({len(errs)}):[/bold red]")
|
|
635
|
+
for e in errs:
|
|
636
|
+
console.print(f" [red]✗[/red] [bold]{e['type']}[/bold] {e['item']} — {e['detail']}")
|
|
637
|
+
if warns:
|
|
638
|
+
console.print(f"\n[bold yellow]Warnings ({len(warns)}):[/bold yellow]")
|
|
639
|
+
for w in warns:
|
|
640
|
+
console.print(
|
|
641
|
+
f" [yellow]![/yellow] [bold]{w['type']}[/bold] {w['item']} — {w['detail']}"
|
|
642
|
+
)
|
|
643
|
+
if valid and not warns:
|
|
644
|
+
console.print("\n[bold green]Model is clean.[/bold green]")
|
|
645
|
+
elif valid:
|
|
646
|
+
console.print(f"\n[bold green]Model is valid[/bold green] (with {len(warns)} warning(s)).")
|
|
647
|
+
else:
|
|
648
|
+
console.print(f"\n[bold red]Model has {len(errs)} error(s).[/bold red]")
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
# ---------------------------------------------------------------------------
|
|
652
|
+
# semantic-layer export
|
|
653
|
+
# ---------------------------------------------------------------------------
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def _print_export(console: Console, data: dict) -> None:
|
|
657
|
+
counts = data.get("counts", {})
|
|
658
|
+
console.print(f"\n[bold green]Exported model[/bold green] to: [cyan]{data['path']}[/cyan]")
|
|
659
|
+
table = Table()
|
|
660
|
+
table.add_column("Entity", style="bold cyan")
|
|
661
|
+
table.add_column("Count", justify="right")
|
|
662
|
+
for key in ("datasets", "metrics", "relationships", "constraints", "glossary"):
|
|
663
|
+
table.add_row(key, str(counts.get(key, 0)))
|
|
664
|
+
console.print(table)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
@semantic_layer_app.command("export")
|
|
668
|
+
def semantic_layer_export(
|
|
669
|
+
ctx: typer.Context,
|
|
670
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
671
|
+
model: str | None = typer.Option(
|
|
672
|
+
None, "--model", help="Model name or UUID (optional if project has one model)."
|
|
673
|
+
),
|
|
674
|
+
output: Path | None = typer.Option(
|
|
675
|
+
None,
|
|
676
|
+
"--output",
|
|
677
|
+
help=("Output JSON path. Defaults to ./sl_export_{model_name}_{YYYYMMDD_HHMMSS}.json."),
|
|
678
|
+
),
|
|
679
|
+
) -> None:
|
|
680
|
+
"""Snapshot a semantic-layer model to a self-describing JSON file."""
|
|
681
|
+
formatter = get_formatter(ctx)
|
|
682
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
683
|
+
result = _handle_service_call(
|
|
684
|
+
ctx,
|
|
685
|
+
service.export_model,
|
|
686
|
+
alias=project,
|
|
687
|
+
model_name_or_uuid=model,
|
|
688
|
+
output_path=output,
|
|
689
|
+
)
|
|
690
|
+
formatter.output(result, _print_export)
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
# ---------------------------------------------------------------------------
|
|
694
|
+
# semantic-layer diff
|
|
695
|
+
# ---------------------------------------------------------------------------
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _print_diff(console: Console, data: dict) -> None:
|
|
699
|
+
left = data.get("left", {})
|
|
700
|
+
right = data.get("right", {})
|
|
701
|
+
console.print(
|
|
702
|
+
f"\n[bold]Diff[/bold] left=[cyan]{left.get('source')}[/cyan]:[magenta]"
|
|
703
|
+
f"{left.get('ref')}[/magenta] "
|
|
704
|
+
f"right=[cyan]{right.get('source')}[/cyan]:[magenta]{right.get('ref')}[/magenta]"
|
|
705
|
+
)
|
|
706
|
+
for type_key in ("datasets", "metrics", "relationships", "constraints", "glossary"):
|
|
707
|
+
per = data.get(type_key, {})
|
|
708
|
+
added = per.get("added", [])
|
|
709
|
+
removed = per.get("removed", [])
|
|
710
|
+
changed = per.get("changed", [])
|
|
711
|
+
if not (added or removed or changed):
|
|
712
|
+
continue
|
|
713
|
+
console.print(f"\n[bold cyan]{type_key}[/bold cyan]:")
|
|
714
|
+
for name in added:
|
|
715
|
+
console.print(f" [green]+ {name}[/green]")
|
|
716
|
+
for name in removed:
|
|
717
|
+
console.print(f" [red]- {name}[/red]")
|
|
718
|
+
for c in changed:
|
|
719
|
+
key = c.get("name", c.get("term", "?"))
|
|
720
|
+
keys = ", ".join(c.get("diff_keys", []))
|
|
721
|
+
console.print(f" [yellow]~ {key}[/yellow] ([dim]{keys}[/dim])")
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@semantic_layer_app.command("diff")
|
|
725
|
+
def semantic_layer_diff(
|
|
726
|
+
ctx: typer.Context,
|
|
727
|
+
project_a: str | None = typer.Option(None, "--project-a", help="Left side: project alias"),
|
|
728
|
+
project_b: str | None = typer.Option(None, "--project-b", help="Right side: project alias"),
|
|
729
|
+
model_a: str | None = typer.Option(
|
|
730
|
+
None, "--model-a", help="Left side: model name/UUID (when --project-a is set)"
|
|
731
|
+
),
|
|
732
|
+
model_b: str | None = typer.Option(
|
|
733
|
+
None, "--model-b", help="Right side: model name/UUID (when --project-b is set)"
|
|
734
|
+
),
|
|
735
|
+
file_a: Path | None = typer.Option(
|
|
736
|
+
None,
|
|
737
|
+
"--file-a",
|
|
738
|
+
help="Left side: snapshot JSON path (mutually exclusive with --project-a)",
|
|
739
|
+
),
|
|
740
|
+
file_b: Path | None = typer.Option(
|
|
741
|
+
None,
|
|
742
|
+
"--file-b",
|
|
743
|
+
help="Right side: snapshot JSON path (mutually exclusive with --project-b)",
|
|
744
|
+
),
|
|
745
|
+
) -> None:
|
|
746
|
+
"""Diff two semantic-layer snapshots (project↔project, project↔file, file↔file).
|
|
747
|
+
|
|
748
|
+
Pass exactly one of ``--project-a`` / ``--file-a`` and one of
|
|
749
|
+
``--project-b`` / ``--file-b``. Output groups changes per entity type:
|
|
750
|
+
``added``, ``removed``, ``changed`` (with ``diff_keys`` listing the
|
|
751
|
+
attribute fields that differ).
|
|
752
|
+
"""
|
|
753
|
+
formatter = get_formatter(ctx)
|
|
754
|
+
|
|
755
|
+
# Mutual exclusion: exactly one source per side.
|
|
756
|
+
if (project_a is None) == (file_a is None):
|
|
757
|
+
formatter.error(
|
|
758
|
+
message="Specify exactly one of --project-a or --file-a.",
|
|
759
|
+
error_code=ErrorCode.USAGE_ERROR,
|
|
760
|
+
)
|
|
761
|
+
raise typer.Exit(code=2)
|
|
762
|
+
if (project_b is None) == (file_b is None):
|
|
763
|
+
formatter.error(
|
|
764
|
+
message="Specify exactly one of --project-b or --file-b.",
|
|
765
|
+
error_code=ErrorCode.USAGE_ERROR,
|
|
766
|
+
)
|
|
767
|
+
raise typer.Exit(code=2)
|
|
768
|
+
|
|
769
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
770
|
+
result = _handle_service_call(
|
|
771
|
+
ctx,
|
|
772
|
+
service.diff,
|
|
773
|
+
project_a=project_a,
|
|
774
|
+
project_b=project_b,
|
|
775
|
+
model_a=model_a,
|
|
776
|
+
model_b=model_b,
|
|
777
|
+
file_a=file_a,
|
|
778
|
+
file_b=file_b,
|
|
779
|
+
)
|
|
780
|
+
formatter.output(result, _print_diff)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
@semantic_layer_app.command("validate")
|
|
784
|
+
def semantic_layer_validate(
|
|
785
|
+
ctx: typer.Context,
|
|
786
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
787
|
+
model: str | None = typer.Option(
|
|
788
|
+
None, "--model", help="Model name or UUID (optional if project has one model)."
|
|
789
|
+
),
|
|
790
|
+
deep: bool = typer.Option(
|
|
791
|
+
False,
|
|
792
|
+
"--deep",
|
|
793
|
+
help=(
|
|
794
|
+
"Fetch every dataset's storage schema in parallel and add "
|
|
795
|
+
"phantom-field, metric-phantom, and agg-on-STRING checks."
|
|
796
|
+
),
|
|
797
|
+
),
|
|
798
|
+
) -> None:
|
|
799
|
+
"""Validate a semantic-layer model.
|
|
800
|
+
|
|
801
|
+
Basic checks: duplicates, dangling rel/metric, sum-on-pct, constraint
|
|
802
|
+
orphan, severity-suffix warning. With ``--deep``: also probe the actual
|
|
803
|
+
Snowflake schema for phantom fields, phantom column refs, and AGG-on-STRING.
|
|
804
|
+
"""
|
|
805
|
+
formatter = get_formatter(ctx)
|
|
806
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
807
|
+
result = _handle_service_call(
|
|
808
|
+
ctx,
|
|
809
|
+
service.validate_model,
|
|
810
|
+
alias=project,
|
|
811
|
+
model_name_or_uuid=model,
|
|
812
|
+
deep=deep,
|
|
813
|
+
)
|
|
814
|
+
formatter.output(result, _print_validate)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
# ---------------------------------------------------------------------------
|
|
818
|
+
# semantic-layer search-context / get-context
|
|
819
|
+
#
|
|
820
|
+
# Project-wide read surface that mirrors the upstream
|
|
821
|
+
# ``keboola-mcp-server`` semantic-context tools. Lets downstream callers
|
|
822
|
+
# (FIIA, scheduled agents) drop the MCP dependency for the common
|
|
823
|
+
# "is the model populated?" + "what's at this id?" lookups.
|
|
824
|
+
# ---------------------------------------------------------------------------
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def _print_search_context(console: Console, data: dict) -> None:
|
|
828
|
+
project = data.get("project", "")
|
|
829
|
+
total = data.get("total_count", 0)
|
|
830
|
+
console.print(
|
|
831
|
+
f"\n[bold]Semantic contexts[/bold] in [magenta]{project}[/magenta]: "
|
|
832
|
+
f"{total} match{'es' if total != 1 else ''}"
|
|
833
|
+
)
|
|
834
|
+
contexts = data.get("contexts", []) or []
|
|
835
|
+
if not contexts:
|
|
836
|
+
console.print("[dim](no matches)[/dim]")
|
|
837
|
+
return
|
|
838
|
+
table = Table()
|
|
839
|
+
table.add_column("Type", style="bold cyan")
|
|
840
|
+
table.add_column("Name", style="bold")
|
|
841
|
+
table.add_column("ID")
|
|
842
|
+
table.add_column("Description")
|
|
843
|
+
for c in contexts:
|
|
844
|
+
table.add_row(
|
|
845
|
+
str(c.get("type", "")),
|
|
846
|
+
str(c.get("name", "")),
|
|
847
|
+
str(c.get("id", "")),
|
|
848
|
+
str(c.get("description", ""))[:60],
|
|
849
|
+
)
|
|
850
|
+
console.print(table)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def _print_get_context(console: Console, data: dict) -> None:
|
|
854
|
+
console.print(
|
|
855
|
+
f"\n[bold]{data.get('type', '?')}[/bold] "
|
|
856
|
+
f"[cyan]{data.get('name', '')}[/cyan] "
|
|
857
|
+
f"([dim]{data.get('id', '')}[/dim]) in "
|
|
858
|
+
f"[magenta]{data.get('project', '')}[/magenta]"
|
|
859
|
+
)
|
|
860
|
+
desc = data.get("description", "")
|
|
861
|
+
if desc:
|
|
862
|
+
console.print(f"\n{desc}\n")
|
|
863
|
+
attrs = data.get("attributes") or {}
|
|
864
|
+
if attrs:
|
|
865
|
+
console.print("[bold]Attributes:[/bold]")
|
|
866
|
+
console.print(json.dumps(attrs, indent=2, sort_keys=True, default=str))
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
@semantic_layer_app.command("search-context")
|
|
870
|
+
def semantic_layer_search_context(
|
|
871
|
+
ctx: typer.Context,
|
|
872
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
873
|
+
pattern: list[str] = typer.Option(
|
|
874
|
+
["*"],
|
|
875
|
+
"--pattern",
|
|
876
|
+
help=(
|
|
877
|
+
"Glob pattern matched against entity name (case-sensitive "
|
|
878
|
+
"fnmatch). Repeatable; matches the union. Default: '*'."
|
|
879
|
+
),
|
|
880
|
+
),
|
|
881
|
+
type_filter: str = typer.Option(
|
|
882
|
+
"all",
|
|
883
|
+
"--type",
|
|
884
|
+
help=(
|
|
885
|
+
"Restrict to one type: model | dataset | metric | relationship | "
|
|
886
|
+
"constraint | glossary | all. Default: all (every child type)."
|
|
887
|
+
),
|
|
888
|
+
),
|
|
889
|
+
limit: int | None = typer.Option(
|
|
890
|
+
None,
|
|
891
|
+
"--limit",
|
|
892
|
+
help="Maximum number of results to return. Default: no cap.",
|
|
893
|
+
),
|
|
894
|
+
) -> None:
|
|
895
|
+
"""Search semantic-layer entities across a project by name pattern.
|
|
896
|
+
|
|
897
|
+
Project-wide (not model-scoped). Equivalent to the upstream
|
|
898
|
+
``keboola-mcp-server`` ``search_semantic_context`` tool. Use this as a
|
|
899
|
+
pre-flight check ("is the semantic model populated?") before kicking
|
|
900
|
+
off a downstream pipeline that depends on it.
|
|
901
|
+
"""
|
|
902
|
+
formatter = get_formatter(ctx)
|
|
903
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
904
|
+
result = _handle_service_call(
|
|
905
|
+
ctx,
|
|
906
|
+
service.search_context,
|
|
907
|
+
alias=project,
|
|
908
|
+
patterns=pattern,
|
|
909
|
+
type_filter=type_filter,
|
|
910
|
+
limit=limit,
|
|
911
|
+
)
|
|
912
|
+
formatter.output(result, _print_search_context)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
@semantic_layer_app.command("get-context")
|
|
916
|
+
def semantic_layer_get_context(
|
|
917
|
+
ctx: typer.Context,
|
|
918
|
+
project: str = typer.Option(..., "--project", help="Project alias"),
|
|
919
|
+
context_id: str = typer.Option(
|
|
920
|
+
...,
|
|
921
|
+
"--context-id",
|
|
922
|
+
help="UUID of the entity to fetch (model, dataset, metric, ...).",
|
|
923
|
+
),
|
|
924
|
+
) -> None:
|
|
925
|
+
"""Fetch a single semantic-layer entity by id, irrespective of its type.
|
|
926
|
+
|
|
927
|
+
Probes every type (model + datasets / metrics / relationships /
|
|
928
|
+
constraints / glossary) until it finds the entity, then returns the
|
|
929
|
+
full attribute dict. Exits 1 if no type matches.
|
|
930
|
+
"""
|
|
931
|
+
formatter = get_formatter(ctx)
|
|
932
|
+
service = get_service(ctx, "semantic_layer_service")
|
|
933
|
+
result = _handle_service_call(
|
|
934
|
+
ctx,
|
|
935
|
+
service.get_context,
|
|
936
|
+
alias=project,
|
|
937
|
+
context_id=context_id,
|
|
938
|
+
)
|
|
939
|
+
formatter.output(result, _print_get_context)
|