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.
Files changed (306) hide show
  1. keboola_agent_cli/__init__.py +34 -0
  2. keboola_agent_cli/__main__.py +5 -0
  3. keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js +2 -0
  4. keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js.map +1 -0
  5. keboola_agent_cli/_ui_dist/assets/architecture-7EHR7CIX-hNCijx_H.js +1 -0
  6. keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js +37 -0
  7. keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js.map +1 -0
  8. keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js +2 -0
  9. keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js.map +1 -0
  10. keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js +133 -0
  11. keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js.map +1 -0
  12. keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js +11 -0
  13. keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js.map +1 -0
  14. keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js +2 -0
  15. keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js.map +1 -0
  16. keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js +2 -0
  17. keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js.map +1 -0
  18. keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js +63 -0
  19. keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js.map +1 -0
  20. keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js +2 -0
  21. keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js.map +1 -0
  22. keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js +2 -0
  23. keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js.map +1 -0
  24. keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js +3 -0
  25. keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js.map +1 -0
  26. keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js +207 -0
  27. keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js.map +1 -0
  28. keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js +232 -0
  29. keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js.map +1 -0
  30. keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js +2 -0
  31. keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js.map +1 -0
  32. keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js +124 -0
  33. keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js.map +1 -0
  34. keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js +16 -0
  35. keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js.map +1 -0
  36. keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js +2 -0
  37. keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js.map +1 -0
  38. keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js +2 -0
  39. keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js.map +1 -0
  40. keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js +160 -0
  41. keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js.map +1 -0
  42. keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js +2 -0
  43. keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js.map +1 -0
  44. keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js +71 -0
  45. keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js.map +1 -0
  46. keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js +2 -0
  47. keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js.map +1 -0
  48. keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js +2 -0
  49. keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js.map +1 -0
  50. keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js +2 -0
  51. keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js.map +1 -0
  52. keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js +2 -0
  53. keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js.map +1 -0
  54. keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js +322 -0
  55. keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js.map +1 -0
  56. keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js +5 -0
  57. keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js.map +1 -0
  58. keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js +2 -0
  59. keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js.map +1 -0
  60. keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js +2 -0
  61. keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js.map +1 -0
  62. keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js +44 -0
  63. keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js.map +1 -0
  64. keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js +11 -0
  65. keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js.map +1 -0
  66. keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js +4 -0
  67. keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js.map +1 -0
  68. keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js +25 -0
  69. keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js.map +1 -0
  70. keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js +25 -0
  71. keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js.map +1 -0
  72. keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js +2 -0
  73. keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js.map +1 -0
  74. keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js +86 -0
  75. keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js.map +1 -0
  76. keboola_agent_cli/_ui_dist/assets/eventmodeling-FCH6USID-BrJMIks8.js +1 -0
  77. keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js +163 -0
  78. keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js.map +1 -0
  79. keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js +293 -0
  80. keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js.map +1 -0
  81. keboola_agent_cli/_ui_dist/assets/gitGraph-WXDBUCRP-COacYjo-.js +1 -0
  82. keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js +107 -0
  83. keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js.map +1 -0
  84. keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js +2 -0
  85. keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js.map +1 -0
  86. keboola_agent_cli/_ui_dist/assets/index-CMq50kkV.css +1 -0
  87. keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js +118 -0
  88. keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js.map +1 -0
  89. keboola_agent_cli/_ui_dist/assets/info-J43DQDTF-DdCTRIzU.js +1 -0
  90. keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js +3 -0
  91. keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js.map +1 -0
  92. keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js +2 -0
  93. keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js.map +1 -0
  94. keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js +71 -0
  95. keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js.map +1 -0
  96. keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js +140 -0
  97. keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js.map +1 -0
  98. keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js +90 -0
  99. keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js.map +1 -0
  100. keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js +258 -0
  101. keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js.map +1 -0
  102. keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js +2 -0
  103. keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js.map +1 -0
  104. keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js +2 -0
  105. keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js.map +1 -0
  106. keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js +5 -0
  107. keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js.map +1 -0
  108. keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js +97 -0
  109. keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js.map +1 -0
  110. keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js +2 -0
  111. keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js.map +1 -0
  112. keboola_agent_cli/_ui_dist/assets/packet-YPE3B663-DLiiw_B2.js +1 -0
  113. keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js +2 -0
  114. keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js.map +1 -0
  115. keboola_agent_cli/_ui_dist/assets/pie-LRSECV5Y-CRoO8G1g.js +1 -0
  116. keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js +31 -0
  117. keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js.map +1 -0
  118. keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js +8 -0
  119. keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js.map +1 -0
  120. keboola_agent_cli/_ui_dist/assets/radar-GUYGQ44K-DAlLVJHm.js +1 -0
  121. keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js +85 -0
  122. keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js.map +1 -0
  123. keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js +2 -0
  124. keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js.map +1 -0
  125. keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js +41 -0
  126. keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js.map +1 -0
  127. keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js +163 -0
  128. keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js.map +1 -0
  129. keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js +2 -0
  130. keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js.map +1 -0
  131. keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js +2 -0
  132. keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js.map +1 -0
  133. keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js +2 -0
  134. keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js.map +1 -0
  135. keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js +121 -0
  136. keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js.map +1 -0
  137. keboola_agent_cli/_ui_dist/assets/treeView-BLDUP644-DbLYl23-.js +1 -0
  138. keboola_agent_cli/_ui_dist/assets/treemap-LRROVOQU-Bp0eGlOt.js +1 -0
  139. keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js +35 -0
  140. keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js.map +1 -0
  141. keboola_agent_cli/_ui_dist/assets/wardley-L42UT6IY-D4yH4jqS.js +1 -0
  142. keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js +79 -0
  143. keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js.map +1 -0
  144. keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js +8 -0
  145. keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js.map +1 -0
  146. keboola_agent_cli/_ui_dist/index.html +50 -0
  147. keboola_agent_cli/ai_client.py +83 -0
  148. keboola_agent_cli/auto_update.py +550 -0
  149. keboola_agent_cli/changelog.py +1198 -0
  150. keboola_agent_cli/cli.py +448 -0
  151. keboola_agent_cli/client.py +3422 -0
  152. keboola_agent_cli/commands/__init__.py +0 -0
  153. keboola_agent_cli/commands/_data_app_git.py +343 -0
  154. keboola_agent_cli/commands/_helpers.py +377 -0
  155. keboola_agent_cli/commands/_metadata_input.py +49 -0
  156. keboola_agent_cli/commands/_semantic_layer_crud.py +632 -0
  157. keboola_agent_cli/commands/_semantic_layer_helpers.py +44 -0
  158. keboola_agent_cli/commands/_semantic_layer_reference_data.py +247 -0
  159. keboola_agent_cli/commands/agent.py +968 -0
  160. keboola_agent_cli/commands/branch.py +423 -0
  161. keboola_agent_cli/commands/changelog.py +168 -0
  162. keboola_agent_cli/commands/component.py +216 -0
  163. keboola_agent_cli/commands/config.py +2442 -0
  164. keboola_agent_cli/commands/context.py +1481 -0
  165. keboola_agent_cli/commands/data_app.py +1279 -0
  166. keboola_agent_cli/commands/dev_portal.py +584 -0
  167. keboola_agent_cli/commands/doctor.py +37 -0
  168. keboola_agent_cli/commands/encrypt.py +145 -0
  169. keboola_agent_cli/commands/feature.py +311 -0
  170. keboola_agent_cli/commands/flow.py +948 -0
  171. keboola_agent_cli/commands/http_client.py +157 -0
  172. keboola_agent_cli/commands/init.py +279 -0
  173. keboola_agent_cli/commands/job.py +661 -0
  174. keboola_agent_cli/commands/kai.py +301 -0
  175. keboola_agent_cli/commands/lineage.py +1464 -0
  176. keboola_agent_cli/commands/org.py +292 -0
  177. keboola_agent_cli/commands/permissions.py +360 -0
  178. keboola_agent_cli/commands/project.py +1192 -0
  179. keboola_agent_cli/commands/repl.py +243 -0
  180. keboola_agent_cli/commands/schedule.py +340 -0
  181. keboola_agent_cli/commands/search.py +178 -0
  182. keboola_agent_cli/commands/semantic_layer.py +939 -0
  183. keboola_agent_cli/commands/serve.py +272 -0
  184. keboola_agent_cli/commands/sharing.py +340 -0
  185. keboola_agent_cli/commands/storage.py +2630 -0
  186. keboola_agent_cli/commands/stream.py +266 -0
  187. keboola_agent_cli/commands/sync.py +1277 -0
  188. keboola_agent_cli/commands/tool.py +206 -0
  189. keboola_agent_cli/commands/version.py +186 -0
  190. keboola_agent_cli/commands/workspace.py +635 -0
  191. keboola_agent_cli/config_store.py +582 -0
  192. keboola_agent_cli/constants.py +528 -0
  193. keboola_agent_cli/data_science_client.py +342 -0
  194. keboola_agent_cli/dev_portal_client.py +323 -0
  195. keboola_agent_cli/errors.py +248 -0
  196. keboola_agent_cli/http_base.py +315 -0
  197. keboola_agent_cli/json_utils.py +126 -0
  198. keboola_agent_cli/lib.py +536 -0
  199. keboola_agent_cli/manage_client.py +324 -0
  200. keboola_agent_cli/metastore_client.py +214 -0
  201. keboola_agent_cli/models.py +427 -0
  202. keboola_agent_cli/output.py +1084 -0
  203. keboola_agent_cli/permissions.py +469 -0
  204. keboola_agent_cli/py.typed +3 -0
  205. keboola_agent_cli/result_models.py +271 -0
  206. keboola_agent_cli/server/__init__.py +34 -0
  207. keboola_agent_cli/server/agent_runner.py +1289 -0
  208. keboola_agent_cli/server/agents_store.py +325 -0
  209. keboola_agent_cli/server/app.py +764 -0
  210. keboola_agent_cli/server/auth.py +117 -0
  211. keboola_agent_cli/server/dependencies.py +149 -0
  212. keboola_agent_cli/server/pricing.py +303 -0
  213. keboola_agent_cli/server/routers/__init__.py +1 -0
  214. keboola_agent_cli/server/routers/agents.py +616 -0
  215. keboola_agent_cli/server/routers/ai_chat.py +129 -0
  216. keboola_agent_cli/server/routers/branches.py +133 -0
  217. keboola_agent_cli/server/routers/components.py +48 -0
  218. keboola_agent_cli/server/routers/configs.py +507 -0
  219. keboola_agent_cli/server/routers/data_apps.py +384 -0
  220. keboola_agent_cli/server/routers/dev_portal.py +67 -0
  221. keboola_agent_cli/server/routers/encrypt.py +35 -0
  222. keboola_agent_cli/server/routers/feature.py +179 -0
  223. keboola_agent_cli/server/routers/flows.py +204 -0
  224. keboola_agent_cli/server/routers/health.py +53 -0
  225. keboola_agent_cli/server/routers/jobs.py +175 -0
  226. keboola_agent_cli/server/routers/kai.py +80 -0
  227. keboola_agent_cli/server/routers/lineage.py +226 -0
  228. keboola_agent_cli/server/routers/mcp.py +70 -0
  229. keboola_agent_cli/server/routers/members.py +170 -0
  230. keboola_agent_cli/server/routers/org.py +96 -0
  231. keboola_agent_cli/server/routers/projects.py +106 -0
  232. keboola_agent_cli/server/routers/schedules.py +54 -0
  233. keboola_agent_cli/server/routers/search.py +30 -0
  234. keboola_agent_cli/server/routers/semantic_layer.py +650 -0
  235. keboola_agent_cli/server/routers/sharing.py +86 -0
  236. keboola_agent_cli/server/routers/storage.py +574 -0
  237. keboola_agent_cli/server/routers/stream.py +100 -0
  238. keboola_agent_cli/server/routers/workspaces.py +302 -0
  239. keboola_agent_cli/server/run_broadcaster.py +329 -0
  240. keboola_agent_cli/server/sse.py +25 -0
  241. keboola_agent_cli/services/__init__.py +0 -0
  242. keboola_agent_cli/services/_encryption.py +217 -0
  243. keboola_agent_cli/services/_semantic_layer_cascade.py +147 -0
  244. keboola_agent_cli/services/_semantic_layer_crud.py +382 -0
  245. keboola_agent_cli/services/_semantic_layer_internals.py +1078 -0
  246. keboola_agent_cli/services/_semantic_layer_lookup.py +181 -0
  247. keboola_agent_cli/services/_semantic_layer_reference_data.py +217 -0
  248. keboola_agent_cli/services/_sync_bindings.py +456 -0
  249. keboola_agent_cli/services/_sync_branch.py +191 -0
  250. keboola_agent_cli/services/_sync_bulk.py +228 -0
  251. keboola_agent_cli/services/_sync_clone.py +163 -0
  252. keboola_agent_cli/services/_sync_models.py +97 -0
  253. keboola_agent_cli/services/_sync_push_ops.py +369 -0
  254. keboola_agent_cli/services/_sync_storage.py +376 -0
  255. keboola_agent_cli/services/_sync_writeback.py +167 -0
  256. keboola_agent_cli/services/agent_service.py +458 -0
  257. keboola_agent_cli/services/base.py +175 -0
  258. keboola_agent_cli/services/branch_service.py +588 -0
  259. keboola_agent_cli/services/component_service.py +694 -0
  260. keboola_agent_cli/services/config_service.py +2099 -0
  261. keboola_agent_cli/services/data_app_git_service.py +224 -0
  262. keboola_agent_cli/services/data_app_service.py +2082 -0
  263. keboola_agent_cli/services/deep_lineage_service.py +1322 -0
  264. keboola_agent_cli/services/dev_portal_service.py +345 -0
  265. keboola_agent_cli/services/doctor_service.py +445 -0
  266. keboola_agent_cli/services/encrypt_service.py +87 -0
  267. keboola_agent_cli/services/feature_service.py +268 -0
  268. keboola_agent_cli/services/flow_service.py +769 -0
  269. keboola_agent_cli/services/flow_validation.py +188 -0
  270. keboola_agent_cli/services/http_forwarder_service.py +236 -0
  271. keboola_agent_cli/services/job_idempotency_store.py +285 -0
  272. keboola_agent_cli/services/job_service.py +797 -0
  273. keboola_agent_cli/services/kai_service.py +367 -0
  274. keboola_agent_cli/services/lineage_service.py +274 -0
  275. keboola_agent_cli/services/mcp_service.py +1498 -0
  276. keboola_agent_cli/services/mcp_transport.py +259 -0
  277. keboola_agent_cli/services/member_service.py +593 -0
  278. keboola_agent_cli/services/org_service.py +619 -0
  279. keboola_agent_cli/services/project_service.py +947 -0
  280. keboola_agent_cli/services/repo_validate_service.py +767 -0
  281. keboola_agent_cli/services/schedule_service.py +731 -0
  282. keboola_agent_cli/services/search_service.py +331 -0
  283. keboola_agent_cli/services/semantic_layer_service.py +1497 -0
  284. keboola_agent_cli/services/sharing_service.py +307 -0
  285. keboola_agent_cli/services/storage_service.py +2524 -0
  286. keboola_agent_cli/services/stream_service.py +395 -0
  287. keboola_agent_cli/services/sync_service.py +2244 -0
  288. keboola_agent_cli/services/variables_service.py +447 -0
  289. keboola_agent_cli/services/version_service.py +1038 -0
  290. keboola_agent_cli/services/workspace_service.py +1103 -0
  291. keboola_agent_cli/stream_client.py +217 -0
  292. keboola_agent_cli/sync/__init__.py +1 -0
  293. keboola_agent_cli/sync/branch_mapping.py +174 -0
  294. keboola_agent_cli/sync/clone.py +211 -0
  295. keboola_agent_cli/sync/code_extraction.py +655 -0
  296. keboola_agent_cli/sync/config_format.py +290 -0
  297. keboola_agent_cli/sync/diff_engine.py +566 -0
  298. keboola_agent_cli/sync/git_utils.py +93 -0
  299. keboola_agent_cli/sync/manifest.py +162 -0
  300. keboola_agent_cli/sync/naming.py +90 -0
  301. keboola_agent_cli/sync/secrets.py +62 -0
  302. keboola_agent_cli/sync/sql_split.py +134 -0
  303. keboola_cli-0.63.4.dist-info/METADATA +308 -0
  304. keboola_cli-0.63.4.dist-info/RECORD +306 -0
  305. keboola_cli-0.63.4.dist-info/WHEEL +4 -0
  306. keboola_cli-0.63.4.dist-info/entry_points.txt +2 -0
@@ -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()