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
keboola_agent_cli/lib.py
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
"""Public, in-process library facade for Keboola (issue #415).
|
|
2
|
+
|
|
3
|
+
A stateless importable surface so any Python consumer -- a Keboola Data App, a
|
|
4
|
+
transformation, a hosted service -- can use kbagent's Query Service and Storage
|
|
5
|
+
Files *in-process*: no CLI subprocess, no ``kbagent serve`` daemon, no
|
|
6
|
+
config-dir, no ``project add`` ceremony.
|
|
7
|
+
|
|
8
|
+
from keboola_agent_cli import Client
|
|
9
|
+
|
|
10
|
+
with Client(url=KBC_URL, token=KBC_TOKEN) as kbc:
|
|
11
|
+
rows = kbc.query(workspace_id, "SELECT id, name FROM t") # list[dict]
|
|
12
|
+
meta = kbc.files.upload(b"hello", name="greeting.txt", tags=["x"])
|
|
13
|
+
data = kbc.files.read_bytes(meta.id) # bytes
|
|
14
|
+
metas = kbc.files.list(tags=["x"]) # list[FileEntry]
|
|
15
|
+
job = kbc.run_job("keboola.ex-db-snowflake", "12345", wait=True) # JobResult
|
|
16
|
+
|
|
17
|
+
The facade is a thin convenience wrapper over :class:`KeboolaClient`
|
|
18
|
+
(``client.py``); it adds the high-level shapes -- ``list[dict]`` rows, ``bytes``
|
|
19
|
+
file reads, a stable :class:`FileEntry`, and the typed result models in
|
|
20
|
+
``result_models.py`` (:class:`JobResult`, :class:`QueryResult`,
|
|
21
|
+
:class:`UploadTableResult`, :class:`ConfigDetailResult`) -- that the CLI used to
|
|
22
|
+
assemble inside its service layer. Auth is the storage token passed at
|
|
23
|
+
construction (12-factor: read it from ``KBC_TOKEN`` yourself); nothing is
|
|
24
|
+
persisted to disk.
|
|
25
|
+
|
|
26
|
+
For replay-safe job runs (issue #427), pass an ``idempotency_store`` (the facade
|
|
27
|
+
is config-dir-free, so the consumer supplies *where* to persist the dedup map --
|
|
28
|
+
typically inside its own resume-checkpoint dir) and an ``idempotency_key`` per
|
|
29
|
+
``run_job``.
|
|
30
|
+
|
|
31
|
+
Everything exported here is committed public API and changes follow semver. For
|
|
32
|
+
lower-level access (raw Queue/Storage endpoints) reach for the underlying
|
|
33
|
+
:class:`KeboolaClient` via :attr:`Client.raw`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import logging
|
|
39
|
+
import tempfile
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
from .client import KeboolaClient, _collect_inline_results
|
|
45
|
+
from .constants import (
|
|
46
|
+
DEFAULT_JOB_MODE,
|
|
47
|
+
DEFAULT_JOB_RUN_TIMEOUT,
|
|
48
|
+
DEFAULT_POLL_STRATEGY,
|
|
49
|
+
QUERY_RESULTS_DEFAULT_LIMIT,
|
|
50
|
+
)
|
|
51
|
+
from .errors import ErrorCode, KeboolaApiError
|
|
52
|
+
from .result_models import ConfigDetailResult, JobResult, QueryResult, UploadTableResult
|
|
53
|
+
from .services.job_idempotency_store import JobIdempotencyStore, run_idempotent_job
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
# The public ``Files.list`` method shadows the builtin ``list`` inside that
|
|
58
|
+
# class body, so ``list[...]`` *annotations* there would resolve to the method
|
|
59
|
+
# (under both runtime evaluation and ``ty``). Method *bodies* are unaffected --
|
|
60
|
+
# class scope is skipped in function name resolution -- so this alias is only
|
|
61
|
+
# needed for annotations inside ``Files``.
|
|
62
|
+
_list = list
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class FileEntry:
|
|
67
|
+
"""Stable, uniform shape for a Storage File across list and upload results.
|
|
68
|
+
|
|
69
|
+
Always carries the same fields regardless of whether the underlying API
|
|
70
|
+
response happened to include a (sometimes-absent) signed download URL.
|
|
71
|
+
Read the bytes with :meth:`Files.read_bytes`, never by branching on a URL.
|
|
72
|
+
``raw`` is the untouched API dict for fields the facade does not surface.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
id: int
|
|
76
|
+
name: str
|
|
77
|
+
tags: list[str]
|
|
78
|
+
created: str | None
|
|
79
|
+
size_bytes: int | None
|
|
80
|
+
is_permanent: bool
|
|
81
|
+
raw: dict[str, Any]
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def _from_api(cls, data: dict[str, Any]) -> FileEntry:
|
|
85
|
+
return cls(
|
|
86
|
+
id=int(data["id"]),
|
|
87
|
+
name=data.get("name", ""),
|
|
88
|
+
tags=list(data.get("tags") or []),
|
|
89
|
+
created=data.get("created"),
|
|
90
|
+
size_bytes=data.get("sizeBytes"),
|
|
91
|
+
is_permanent=bool(data.get("isPermanent", False)),
|
|
92
|
+
raw=data,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Files:
|
|
97
|
+
"""Storage Files operations bound to one project (and optional branch).
|
|
98
|
+
|
|
99
|
+
Obtained via :attr:`Client.files`; not constructed directly.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, client: KeboolaClient, branch_id: int | None) -> None:
|
|
103
|
+
self._client = client
|
|
104
|
+
self._branch_id = branch_id
|
|
105
|
+
|
|
106
|
+
def list(
|
|
107
|
+
self,
|
|
108
|
+
*,
|
|
109
|
+
tags: _list[str] | None = None,
|
|
110
|
+
query: str | None = None,
|
|
111
|
+
limit: int = 100,
|
|
112
|
+
offset: int = 0,
|
|
113
|
+
since_id: int | None = None,
|
|
114
|
+
) -> _list[FileEntry]:
|
|
115
|
+
"""List Storage Files as uniform :class:`FileEntry` records.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
tags: Filter to files carrying all of these tags (AND logic).
|
|
119
|
+
query: Full-text search over the file name.
|
|
120
|
+
limit: Max files to return.
|
|
121
|
+
offset: Pagination offset.
|
|
122
|
+
since_id: Return only files with an ID greater than this.
|
|
123
|
+
"""
|
|
124
|
+
raw = self._client.list_files(
|
|
125
|
+
limit=limit,
|
|
126
|
+
offset=offset,
|
|
127
|
+
tags=tags,
|
|
128
|
+
since_id=since_id,
|
|
129
|
+
query=query,
|
|
130
|
+
branch_id=self._branch_id,
|
|
131
|
+
)
|
|
132
|
+
return [FileEntry._from_api(item) for item in raw]
|
|
133
|
+
|
|
134
|
+
def upload(
|
|
135
|
+
self,
|
|
136
|
+
source: str | Path | bytes | bytearray,
|
|
137
|
+
*,
|
|
138
|
+
name: str | None = None,
|
|
139
|
+
tags: _list[str] | None = None,
|
|
140
|
+
permanent: bool = False,
|
|
141
|
+
) -> FileEntry:
|
|
142
|
+
"""Upload a local path or in-memory bytes to Storage Files.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
source: A filesystem path (``str``/``Path``) or raw ``bytes`` to
|
|
146
|
+
upload. When passing bytes, ``name`` is required (Storage needs
|
|
147
|
+
a file name and there is no path to derive it from).
|
|
148
|
+
name: Storage file name. Defaults to the path basename for path
|
|
149
|
+
sources; required for bytes sources.
|
|
150
|
+
tags: Tags to assign.
|
|
151
|
+
permanent: If True the file is not auto-expired after 15 days.
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
ValueError: If ``source`` is bytes and ``name`` is not given.
|
|
155
|
+
"""
|
|
156
|
+
if isinstance(source, (bytes, bytearray)):
|
|
157
|
+
if not name:
|
|
158
|
+
raise ValueError("name is required when uploading raw bytes")
|
|
159
|
+
# Reuse the battle-tested prepare + multi-cloud upload path
|
|
160
|
+
# (S3/GCS/Azure, issue #187 streaming) by staging the bytes in a
|
|
161
|
+
# temp dir rather than duplicating _upload_to_cloud. A TemporaryDirectory
|
|
162
|
+
# is auto-removed even if the upload raises (no leaked temp file); the
|
|
163
|
+
# staged file name is irrelevant -- `name` is what Storage records.
|
|
164
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
165
|
+
staged = Path(tmpdir) / "upload"
|
|
166
|
+
staged.write_bytes(bytes(source))
|
|
167
|
+
info = self._client.upload_file(
|
|
168
|
+
file_path=str(staged),
|
|
169
|
+
name=name,
|
|
170
|
+
tags=tags,
|
|
171
|
+
is_permanent=permanent,
|
|
172
|
+
branch_id=self._branch_id,
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
info = self._client.upload_file(
|
|
176
|
+
file_path=str(source),
|
|
177
|
+
name=name,
|
|
178
|
+
tags=tags,
|
|
179
|
+
is_permanent=permanent,
|
|
180
|
+
branch_id=self._branch_id,
|
|
181
|
+
)
|
|
182
|
+
return FileEntry._from_api(info)
|
|
183
|
+
|
|
184
|
+
def read_bytes(self, file_id: int) -> bytes:
|
|
185
|
+
"""Download a Storage File fully into memory and return its bytes.
|
|
186
|
+
|
|
187
|
+
Hides the file-info -> signed-URL -> stream dance and transparently
|
|
188
|
+
handles sliced files and gzip. The whole payload is held in RAM, so use
|
|
189
|
+
this for reasonably sized files (results, manifests, small exports); for
|
|
190
|
+
multi-GB tables stream to disk via :attr:`Client.raw` instead.
|
|
191
|
+
"""
|
|
192
|
+
info = self._client.get_file_info(file_id, branch_id=self._branch_id)
|
|
193
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
194
|
+
out = Path(tmpdir) / "download"
|
|
195
|
+
if info.get("isSliced", False):
|
|
196
|
+
self._client.download_sliced_file(info, str(out))
|
|
197
|
+
else:
|
|
198
|
+
url = info.get("url")
|
|
199
|
+
if not url:
|
|
200
|
+
raise KeboolaApiError(
|
|
201
|
+
f"Storage file {file_id} has no download URL.",
|
|
202
|
+
error_code=ErrorCode.API_ERROR,
|
|
203
|
+
)
|
|
204
|
+
self._client.download_file(url, str(out))
|
|
205
|
+
return out.read_bytes()
|
|
206
|
+
|
|
207
|
+
def delete(self, file_id: int) -> None:
|
|
208
|
+
"""Delete a Storage File."""
|
|
209
|
+
self._client.delete_file(file_id, branch_id=self._branch_id)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class Client:
|
|
213
|
+
"""Stateless in-process entry point to one Keboola project.
|
|
214
|
+
|
|
215
|
+
Holds nothing but the stack URL, token, a single :class:`KeboolaClient`
|
|
216
|
+
(which carries the shared retry/backoff), and an optional idempotency store.
|
|
217
|
+
No config-dir, no ``project add``.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
url: Stack URL, e.g. ``https://connection.keboola.com``.
|
|
221
|
+
token: Storage API token for the project.
|
|
222
|
+
branch_id: Dev branch to scope every operation to. ``None`` (default)
|
|
223
|
+
targets production: Storage Files use the production scope and
|
|
224
|
+
:meth:`query` resolves the project's default branch on first use.
|
|
225
|
+
idempotency_store: Optional default :class:`JobIdempotencyStore` used by
|
|
226
|
+
:meth:`run_job` when an ``idempotency_key`` is given (issue #427).
|
|
227
|
+
The facade is config-dir-free, so the consumer decides where the
|
|
228
|
+
dedup map lives. A per-call ``idempotency_store`` overrides this.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
def __init__(
|
|
232
|
+
self,
|
|
233
|
+
url: str,
|
|
234
|
+
token: str,
|
|
235
|
+
*,
|
|
236
|
+
branch_id: int | None = None,
|
|
237
|
+
idempotency_store: JobIdempotencyStore | None = None,
|
|
238
|
+
) -> None:
|
|
239
|
+
if not url:
|
|
240
|
+
raise ValueError("url is required")
|
|
241
|
+
if not token:
|
|
242
|
+
raise ValueError("token is required")
|
|
243
|
+
self._client = KeboolaClient(stack_url=url, token=token)
|
|
244
|
+
self._resolved_branch_id = branch_id
|
|
245
|
+
self._idempotency_store = idempotency_store
|
|
246
|
+
self.files = Files(self._client, branch_id)
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def raw(self) -> KeboolaClient:
|
|
250
|
+
"""The underlying low-level client, for endpoints the facade omits."""
|
|
251
|
+
return self._client
|
|
252
|
+
|
|
253
|
+
def _effective_branch_id(self) -> int:
|
|
254
|
+
"""Resolve the branch ID for query submission (caches the default)."""
|
|
255
|
+
if self._resolved_branch_id is not None:
|
|
256
|
+
return self._resolved_branch_id
|
|
257
|
+
for branch in self._client.list_dev_branches():
|
|
258
|
+
if branch.get("isDefault", False):
|
|
259
|
+
self._resolved_branch_id = int(branch["id"])
|
|
260
|
+
return self._resolved_branch_id
|
|
261
|
+
raise KeboolaApiError(
|
|
262
|
+
"No default branch found for this project.",
|
|
263
|
+
error_code=ErrorCode.NOT_FOUND,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def _run_query(
|
|
267
|
+
self,
|
|
268
|
+
workspace_id: int,
|
|
269
|
+
sql: str,
|
|
270
|
+
*,
|
|
271
|
+
transactional: bool,
|
|
272
|
+
limit: int,
|
|
273
|
+
) -> QueryResult:
|
|
274
|
+
"""Submit SQL, wait for completion, and collect the last result set.
|
|
275
|
+
|
|
276
|
+
Shared by :meth:`query` (which returns just the rows) and
|
|
277
|
+
:meth:`query_result` (which returns the full typed shape). Mirrors the
|
|
278
|
+
Query Service inline-results fast path: the rows of the *last*
|
|
279
|
+
result-producing statement win, statements without a result set yield
|
|
280
|
+
nothing, and an over-``limit`` result is capped with a logged warning.
|
|
281
|
+
"""
|
|
282
|
+
branch_id = self._effective_branch_id()
|
|
283
|
+
job = self._client.submit_query(
|
|
284
|
+
branch_id=branch_id,
|
|
285
|
+
workspace_id=workspace_id,
|
|
286
|
+
statements=[sql],
|
|
287
|
+
transactional=transactional,
|
|
288
|
+
)
|
|
289
|
+
job_id = str(job.get("queryJobId", job.get("id", "")))
|
|
290
|
+
completed = self._client.wait_for_query_job(job_id)
|
|
291
|
+
|
|
292
|
+
result = QueryResult()
|
|
293
|
+
for stmt in completed.get("statements", []):
|
|
294
|
+
num_rows = stmt.get("numberOfRows", stmt.get("resultRows", 0))
|
|
295
|
+
if stmt.get("status") != "completed" or not num_rows:
|
|
296
|
+
continue
|
|
297
|
+
inline = _collect_inline_results(self._client, job_id, str(stmt.get("id", "")), limit)
|
|
298
|
+
col_names = [col.get("name", "") for col in inline.columns]
|
|
299
|
+
result = QueryResult(
|
|
300
|
+
columns=col_names,
|
|
301
|
+
rows=[dict(zip(col_names, row, strict=False)) for row in inline.rows],
|
|
302
|
+
truncated=inline.truncated,
|
|
303
|
+
total_rows=inline.total_rows,
|
|
304
|
+
)
|
|
305
|
+
if inline.truncated:
|
|
306
|
+
logger.warning(
|
|
307
|
+
"query result truncated to %d rows (warehouse has %s); "
|
|
308
|
+
"raise limit= to fetch more",
|
|
309
|
+
result.row_count,
|
|
310
|
+
inline.total_rows,
|
|
311
|
+
)
|
|
312
|
+
return result
|
|
313
|
+
|
|
314
|
+
def query(
|
|
315
|
+
self,
|
|
316
|
+
workspace_id: int,
|
|
317
|
+
sql: str,
|
|
318
|
+
*,
|
|
319
|
+
transactional: bool = False,
|
|
320
|
+
limit: int = QUERY_RESULTS_DEFAULT_LIMIT,
|
|
321
|
+
) -> list[dict[str, Any]]:
|
|
322
|
+
"""Run SQL in a workspace and return rows as a list of dicts.
|
|
323
|
+
|
|
324
|
+
Submits the statement via Query Service, waits for completion, and reads
|
|
325
|
+
results inline (the fast ``/results`` path, no CSV-file materialization).
|
|
326
|
+
Each row is a dict keyed by the result column names exactly as the
|
|
327
|
+
warehouse reports them -- note Snowflake folds unquoted aliases to
|
|
328
|
+
UPPERCASE, so quote aliases if you want lowercase keys. Values are
|
|
329
|
+
returned exactly as the Query Service serializes them and are NOT
|
|
330
|
+
coerced by the facade: for Snowflake every scalar comes back as a JSON
|
|
331
|
+
string (``1`` -> ``"1"``, ``1.5`` -> ``"1.5"``, ``true`` -> ``"true"``),
|
|
332
|
+
with SQL ``NULL`` as ``None``. Cast on the caller side if you need typed
|
|
333
|
+
values.
|
|
334
|
+
|
|
335
|
+
When ``sql`` contains multiple statements, the rows of the *last*
|
|
336
|
+
statement that produced a result set are returned (so ``USE ...; SELECT
|
|
337
|
+
...`` yields the SELECT). Statements without a result set yield ``[]``.
|
|
338
|
+
|
|
339
|
+
For column order and truncation metadata, use :meth:`query_result`.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
workspace_id: Target workspace ID.
|
|
343
|
+
sql: One or more SQL statements.
|
|
344
|
+
transactional: Wrap the statements in a transaction.
|
|
345
|
+
limit: Max rows to fetch (default ``QUERY_RESULTS_DEFAULT_LIMIT``).
|
|
346
|
+
If the warehouse has more, the result is capped and a warning is
|
|
347
|
+
logged -- raise ``limit`` to fetch more.
|
|
348
|
+
"""
|
|
349
|
+
return self._run_query(workspace_id, sql, transactional=transactional, limit=limit).rows
|
|
350
|
+
|
|
351
|
+
def query_result(
|
|
352
|
+
self,
|
|
353
|
+
workspace_id: int,
|
|
354
|
+
sql: str,
|
|
355
|
+
*,
|
|
356
|
+
transactional: bool = False,
|
|
357
|
+
limit: int = QUERY_RESULTS_DEFAULT_LIMIT,
|
|
358
|
+
) -> QueryResult:
|
|
359
|
+
"""Run SQL in a workspace and return a typed :class:`QueryResult`.
|
|
360
|
+
|
|
361
|
+
Same execution as :meth:`query`, but returns the full tabular shape --
|
|
362
|
+
``columns`` (in warehouse order), ``rows`` (list of dicts), ``truncated``
|
|
363
|
+
and ``total_rows`` -- instead of just the row list. Use this when you
|
|
364
|
+
need the column ordering or want to detect a ``limit`` cap. The
|
|
365
|
+
string-typing gotcha from :meth:`query` applies to ``rows`` here too.
|
|
366
|
+
"""
|
|
367
|
+
return self._run_query(workspace_id, sql, transactional=transactional, limit=limit)
|
|
368
|
+
|
|
369
|
+
def run_job(
|
|
370
|
+
self,
|
|
371
|
+
component_id: str,
|
|
372
|
+
config_id: str,
|
|
373
|
+
*,
|
|
374
|
+
config_row_ids: list[str] | None = None,
|
|
375
|
+
variable_values_id: str | None = None,
|
|
376
|
+
branch_id: int | None = None,
|
|
377
|
+
mode: str = DEFAULT_JOB_MODE,
|
|
378
|
+
wait: bool = False,
|
|
379
|
+
timeout: float = DEFAULT_JOB_RUN_TIMEOUT,
|
|
380
|
+
poll_strategy: str = DEFAULT_POLL_STRATEGY,
|
|
381
|
+
idempotency_key: str | None = None,
|
|
382
|
+
force_rerun: bool = False,
|
|
383
|
+
idempotency_store: JobIdempotencyStore | None = None,
|
|
384
|
+
) -> JobResult:
|
|
385
|
+
"""Run a Queue API job and return a typed :class:`JobResult`.
|
|
386
|
+
|
|
387
|
+
Creates the job, and -- when ``wait=True`` -- polls until it reaches a
|
|
388
|
+
terminal state (or ``timeout`` elapses). Unlike ``JobService.run_job``
|
|
389
|
+
this thin facade does **not** auto-resolve linked variable values; pass
|
|
390
|
+
``variable_values_id`` explicitly if the config needs a values row.
|
|
391
|
+
|
|
392
|
+
Idempotency (issue #427): pass ``idempotency_key`` (plus an
|
|
393
|
+
``idempotency_store`` here or on the constructor) to make a replayed call
|
|
394
|
+
safe -- a prior still-running or non-failed job is returned (with
|
|
395
|
+
``JobResult.idempotent_replay = True``) instead of creating a duplicate.
|
|
396
|
+
A prior *failed* run is re-run; ``force_rerun=True`` always creates a
|
|
397
|
+
fresh job. The Queue API has no server-side idempotency, so this is
|
|
398
|
+
client-side and scoped to the supplied store.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
component_id: Component to run, e.g. ``keboola.ex-db-snowflake``.
|
|
402
|
+
config_id: Configuration ID to run.
|
|
403
|
+
config_row_ids: Optional row IDs (omit to run the whole config).
|
|
404
|
+
variable_values_id: Optional explicit values row for linked variables.
|
|
405
|
+
branch_id: Dev branch to run on. Defaults to the client's branch
|
|
406
|
+
(``None`` = production).
|
|
407
|
+
mode: Queue job mode (``run`` | ``debug`` | ``forceRun``).
|
|
408
|
+
wait: If True, poll until the job finishes or ``timeout`` elapses.
|
|
409
|
+
timeout: Max seconds to wait (only used when ``wait=True``).
|
|
410
|
+
poll_strategy: Wait cadence, one of ``VALID_POLL_STRATEGIES``.
|
|
411
|
+
idempotency_key: Client-supplied de-duplication token.
|
|
412
|
+
force_rerun: Ignore any stored entry for ``idempotency_key``.
|
|
413
|
+
idempotency_store: Per-call store override (else the constructor's).
|
|
414
|
+
|
|
415
|
+
Raises:
|
|
416
|
+
ValueError: If ``idempotency_key`` is given but no store is available
|
|
417
|
+
(the stateless facade cannot invent a persistence location).
|
|
418
|
+
"""
|
|
419
|
+
effective_branch = branch_id if branch_id is not None else self._resolved_branch_id
|
|
420
|
+
|
|
421
|
+
def _create() -> dict[str, Any]:
|
|
422
|
+
return self._client.create_job(
|
|
423
|
+
component_id=component_id,
|
|
424
|
+
config_id=config_id,
|
|
425
|
+
config_row_ids=config_row_ids,
|
|
426
|
+
mode=mode,
|
|
427
|
+
branch_id=effective_branch,
|
|
428
|
+
variable_values_id=variable_values_id,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
store = idempotency_store if idempotency_store is not None else self._idempotency_store
|
|
432
|
+
if idempotency_key and store is None:
|
|
433
|
+
raise ValueError(
|
|
434
|
+
"idempotency_key requires an idempotency_store -- pass one to "
|
|
435
|
+
"Client(...) or to run_job(...). The stateless facade has no "
|
|
436
|
+
"config-dir to default it to."
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
job, replayed = run_idempotent_job(
|
|
440
|
+
store=store,
|
|
441
|
+
key=idempotency_key,
|
|
442
|
+
component_id=component_id,
|
|
443
|
+
config_id=config_id,
|
|
444
|
+
branch_id=effective_branch,
|
|
445
|
+
force_rerun=force_rerun,
|
|
446
|
+
create=_create,
|
|
447
|
+
fetch=self._client.get_job_detail,
|
|
448
|
+
)
|
|
449
|
+
job_id = str(job.get("id", ""))
|
|
450
|
+
if wait and job_id:
|
|
451
|
+
job = self._client.wait_for_queue_job(
|
|
452
|
+
job_id, max_wait=timeout, poll_strategy=poll_strategy
|
|
453
|
+
)
|
|
454
|
+
if replayed:
|
|
455
|
+
job = {**job, "idempotent_replay": True}
|
|
456
|
+
return JobResult.model_validate(job)
|
|
457
|
+
|
|
458
|
+
def config_detail(
|
|
459
|
+
self,
|
|
460
|
+
component_id: str,
|
|
461
|
+
config_id: str,
|
|
462
|
+
*,
|
|
463
|
+
branch_id: int | None = None,
|
|
464
|
+
) -> ConfigDetailResult:
|
|
465
|
+
"""Fetch one configuration's detail as a typed :class:`ConfigDetailResult`.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
component_id: Owning component ID.
|
|
469
|
+
config_id: Configuration ID.
|
|
470
|
+
branch_id: Dev branch to read from. Defaults to the client's branch
|
|
471
|
+
(``None`` = production).
|
|
472
|
+
"""
|
|
473
|
+
effective_branch = branch_id if branch_id is not None else self._resolved_branch_id
|
|
474
|
+
detail = dict(
|
|
475
|
+
self._client.get_config_detail(component_id, config_id, branch_id=effective_branch)
|
|
476
|
+
)
|
|
477
|
+
detail.setdefault("component_id", component_id)
|
|
478
|
+
if effective_branch is not None:
|
|
479
|
+
detail.setdefault("branch_id", effective_branch)
|
|
480
|
+
return ConfigDetailResult.model_validate(detail)
|
|
481
|
+
|
|
482
|
+
def upload_table(
|
|
483
|
+
self,
|
|
484
|
+
table_id: str,
|
|
485
|
+
file_path: str | Path,
|
|
486
|
+
*,
|
|
487
|
+
incremental: bool = False,
|
|
488
|
+
delimiter: str = ",",
|
|
489
|
+
enclosure: str = '"',
|
|
490
|
+
branch_id: int | None = None,
|
|
491
|
+
) -> UploadTableResult:
|
|
492
|
+
"""Import a CSV into an **existing** Storage table -> :class:`UploadTableResult`.
|
|
493
|
+
|
|
494
|
+
Unlike ``StorageService.upload_table`` the facade does **not** auto-create
|
|
495
|
+
a missing bucket/table (it has no config-dir / service context); the
|
|
496
|
+
target table must already exist. Use the CLI (``kbagent storage
|
|
497
|
+
upload-table``) for the auto-create path.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
table_id: Target table ID (must exist).
|
|
501
|
+
file_path: Local CSV path.
|
|
502
|
+
incremental: Append rows (True) or full load (False).
|
|
503
|
+
delimiter: CSV column delimiter.
|
|
504
|
+
enclosure: CSV value enclosure character.
|
|
505
|
+
branch_id: Dev branch to target. Defaults to the client's branch
|
|
506
|
+
(``None`` = production).
|
|
507
|
+
"""
|
|
508
|
+
effective_branch = branch_id if branch_id is not None else self._resolved_branch_id
|
|
509
|
+
file_size_bytes = Path(file_path).stat().st_size
|
|
510
|
+
results = self._client.upload_table(
|
|
511
|
+
table_id=table_id,
|
|
512
|
+
file_path=str(file_path),
|
|
513
|
+
incremental=incremental,
|
|
514
|
+
delimiter=delimiter,
|
|
515
|
+
enclosure=enclosure,
|
|
516
|
+
branch_id=effective_branch,
|
|
517
|
+
)
|
|
518
|
+
return UploadTableResult.model_validate(
|
|
519
|
+
{
|
|
520
|
+
"table_id": table_id,
|
|
521
|
+
"incremental": incremental,
|
|
522
|
+
"file_size_bytes": file_size_bytes,
|
|
523
|
+
"imported_rows": results.get("importedRowsCount"),
|
|
524
|
+
"warnings": results.get("warnings", []),
|
|
525
|
+
}
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
def close(self) -> None:
|
|
529
|
+
"""Close the underlying HTTP client."""
|
|
530
|
+
self._client.close()
|
|
531
|
+
|
|
532
|
+
def __enter__(self) -> Client:
|
|
533
|
+
return self
|
|
534
|
+
|
|
535
|
+
def __exit__(self, *exc: object) -> None:
|
|
536
|
+
self.close()
|