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,769 @@
1
+ """Conditional flow (keboola.flow) lifecycle service.
2
+
3
+ Provides CRUD for keboola.flow (Conditional Flow) configurations, plus
4
+ schedule bind/unbind via keboola.scheduler component configs.
5
+
6
+ keboola.orchestrator support was dropped in 0.57.0; this service targets the
7
+ single component keboola.flow. Legacy orchestrator configs are still counted
8
+ (not listed) so the CLI can warn users why a flow "disappeared".
9
+
10
+ Flows are semantic sugar over the Storage API config layer -- no separate
11
+ HTTP client is needed. Schedules are stored as keboola.scheduler configs
12
+ whose ``target`` points at the flow.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ from collections.abc import Callable
20
+ from dataclasses import dataclass
21
+ from typing import Any
22
+
23
+ from ..ai_client import AiServiceClient
24
+ from ..config_store import ConfigStore
25
+ from ..errors import ErrorCode, KeboolaApiError
26
+ from ..models import ComponentDetail, ProjectConfig
27
+ from .base import BaseService, ClientFactory
28
+ from .flow_validation import find_unreachable_phases, validate_conditional_flow
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ FLOW_COMPONENT_ID = "keboola.flow"
33
+ LEGACY_FLOW_COMPONENT_ID = "keboola.orchestrator"
34
+ SCHEDULER_COMPONENT_ID = "keboola.scheduler"
35
+
36
+ AiClientFactory = Callable[[str, str], AiServiceClient]
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class FlowSchemaFetch:
41
+ """Outcome of fetching the live keboola.flow JSON Schema from the stack.
42
+
43
+ ``schema`` holds the JSON Schema dict on success and is ``None`` when it
44
+ could not be obtained; ``reason`` explains the failure (``None`` on
45
+ success). A ``None`` schema must NOT block a write -- callers degrade to
46
+ semantic-only validation and surface ``reason`` as a warning.
47
+ """
48
+
49
+ schema: dict[str, Any] | None
50
+ reason: str | None
51
+
52
+
53
+ def default_ai_client_factory(stack_url: str, token: str) -> AiServiceClient:
54
+ """Default factory: build an ``AiServiceClient`` for the given project."""
55
+ return AiServiceClient(stack_url=stack_url, token=token)
56
+
57
+
58
+ # ---------------------------------------------------------------------------
59
+ # Internal helpers
60
+ # ---------------------------------------------------------------------------
61
+
62
+
63
+ def _parse_configuration(raw: Any) -> dict[str, Any]:
64
+ """Return a parsed configuration dict regardless of whether raw is str or dict."""
65
+ if isinstance(raw, str):
66
+ try:
67
+ return json.loads(raw)
68
+ except json.JSONDecodeError:
69
+ return {}
70
+ return raw or {}
71
+
72
+
73
+ def _collect_schedules_by_parent(
74
+ client: Any, branch_id: int | None
75
+ ) -> dict[tuple[str, str], list[dict[str, Any]]]:
76
+ """Fetch every keboola.scheduler config for a project and group by target.
77
+
78
+ Returns a dict keyed by ``(parent_component_id, parent_config_id)`` so
79
+ ``list_flows`` can look up schedules per flow with a single in-memory
80
+ lookup. Used by the ``--with-schedules`` enrichment path.
81
+
82
+ A missing scheduler component (404 NOT_FOUND) yields an empty map.
83
+ """
84
+ try:
85
+ all_sched = client.list_component_configs(SCHEDULER_COMPONENT_ID, branch_id=branch_id)
86
+ except KeboolaApiError as exc:
87
+ if exc.error_code == ErrorCode.NOT_FOUND:
88
+ return {}
89
+ raise
90
+
91
+ grouped: dict[tuple[str, str], list[dict[str, Any]]] = {}
92
+ for sched in all_sched:
93
+ body = _parse_configuration(sched.get("configuration"))
94
+ target = body.get("target") or {}
95
+ sched_info = body.get("schedule") or {}
96
+
97
+ parent_key = (
98
+ str(target.get("componentId", "")),
99
+ str(target.get("configurationId", "")),
100
+ )
101
+ if not parent_key[0] or not parent_key[1]:
102
+ continue
103
+
104
+ state_raw = sched_info.get("state")
105
+ if isinstance(state_raw, dict):
106
+ enabled = bool(state_raw.get("enabled", False))
107
+ else:
108
+ enabled = str(state_raw).lower() == "enabled"
109
+
110
+ grouped.setdefault(parent_key, []).append(
111
+ {
112
+ "schedule_id": str(sched.get("id", "")),
113
+ "cron": str(sched_info.get("cronTab", "")),
114
+ "timezone": str(sched_info.get("timezone", "UTC")),
115
+ "enabled": enabled,
116
+ }
117
+ )
118
+ return grouped
119
+
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # Service
123
+ # ---------------------------------------------------------------------------
124
+
125
+
126
+ class FlowService(BaseService):
127
+ """Business logic for conditional flow (keboola.flow) CRUD.
128
+
129
+ All schedule operations use keboola.scheduler component configs --
130
+ no separate Scheduler Service HTTP client required.
131
+
132
+ The structural conditional-flow JSON Schema is fetched at runtime from the
133
+ stack's component registry (AI Service ``configurationSchema`` for
134
+ ``keboola.flow``) via ``ai_client_factory`` -- it is never bundled.
135
+ """
136
+
137
+ def __init__(
138
+ self,
139
+ config_store: ConfigStore,
140
+ client_factory: ClientFactory | None = None,
141
+ ai_client_factory: AiClientFactory | None = None,
142
+ ) -> None:
143
+ super().__init__(config_store, client_factory)
144
+ self._ai_client_factory = ai_client_factory or default_ai_client_factory
145
+
146
+ # ── schema fetch ─────────────────────────────────────────────────
147
+
148
+ def _fetch_flow_schema(self, project: ProjectConfig) -> FlowSchemaFetch:
149
+ """Fetch the live keboola.flow JSON Schema from the AI Service.
150
+
151
+ Returns a ``FlowSchemaFetch`` with ``schema`` set on success, or
152
+ ``schema=None`` + a ``reason`` when the schema cannot be obtained
153
+ (network error, KeboolaApiError, malformed or empty schema). A ``None``
154
+ schema must NOT block a write -- the caller degrades to semantic-only
155
+ validation and surfaces ``reason`` as a warning.
156
+ """
157
+ ai_client = self._ai_client_factory(project.stack_url, project.token)
158
+ try:
159
+ raw = ai_client.get_component_detail(FLOW_COMPONENT_ID)
160
+ except KeboolaApiError as exc:
161
+ return FlowSchemaFetch(schema=None, reason=exc.message)
162
+ except Exception as exc:
163
+ # Intentionally broad: ANY schema-fetch failure must degrade to
164
+ # semantic-only validation, never block the write. Narrowing to
165
+ # OSError-style transport errors would miss httpx exceptions
166
+ # (httpx.HTTPError does not subclass OSError) and re-raise them.
167
+ return FlowSchemaFetch(schema=None, reason=str(exc))
168
+ finally:
169
+ ai_client.close()
170
+
171
+ try:
172
+ detail = ComponentDetail(**raw)
173
+ except (TypeError, ValueError) as exc:
174
+ return FlowSchemaFetch(
175
+ schema=None, reason=f"component detail could not be parsed ({exc})"
176
+ )
177
+
178
+ schema = detail.configuration_schema
179
+ if not schema:
180
+ return FlowSchemaFetch(
181
+ schema=None, reason="AI Service returned no configurationSchema for keboola.flow"
182
+ )
183
+ return FlowSchemaFetch(schema=schema, reason=None)
184
+
185
+ def fetch_flow_schema(self, alias: str) -> FlowSchemaFetch:
186
+ """Public schema fetch for a project alias (used by ``flow validate
187
+ --project`` and ``flow schema --full --project``).
188
+
189
+ Returns a ``FlowSchemaFetch`` (``schema`` set on success, or
190
+ ``schema=None`` + ``reason`` on any failure) -- the caller decides how
191
+ to surface the reason.
192
+ """
193
+ projects = self.resolve_projects([alias])
194
+ project = projects[alias]
195
+ return self._fetch_flow_schema(project)
196
+
197
+ # ── list ────────────────────────────────────────────────────────
198
+
199
+ def list_flows(
200
+ self,
201
+ aliases: list[str] | None = None,
202
+ branch_id: int | None = None,
203
+ with_schedules: bool = False,
204
+ ) -> dict[str, Any]:
205
+ """List conditional flows (keboola.flow) across projects.
206
+
207
+ Only ``keboola.flow`` configs are returned. Legacy
208
+ ``keboola.orchestrator`` configs are counted (not listed) and surfaced
209
+ as ``legacy_orchestrator_count`` so the CLI can warn users why a flow
210
+ "disappeared" (orchestrator support was dropped in 0.57.0).
211
+
212
+ When ``with_schedules`` is True, each flow row is enriched with a
213
+ ``schedules`` list pulled from the same project's
214
+ ``keboola.scheduler`` configs. The enrichment costs **one** extra
215
+ ``list_component_configs`` call per project (NOT per flow) -- the
216
+ join happens in memory by (component_id, config_id) key.
217
+
218
+ Args:
219
+ aliases: Project aliases to query; None means every project.
220
+ branch_id: Dev-branch override for single-project fan-out.
221
+ with_schedules: When True, populate ``schedules`` on each row.
222
+
223
+ Returns:
224
+ Dict with keys:
225
+ - "flows": list of keboola.flow dicts (project_alias,
226
+ component_id, config_id, name, description, is_disabled, and
227
+ ``schedules`` when ``with_schedules`` is True)
228
+ - "errors": list of error dicts
229
+ - "legacy_orchestrator_count": total legacy orchestrator
230
+ configs found across the queried projects (not listed)
231
+ """
232
+ projects = self.resolve_projects(aliases)
233
+
234
+ def worker(alias: str, project: ProjectConfig) -> tuple[Any, ...]:
235
+ client = self._client_factory(project.stack_url, project.token)
236
+ effective_branch = branch_id or project.active_branch_id
237
+ try:
238
+ flows: list[dict[str, Any]] = []
239
+ try:
240
+ configs = client.list_component_configs(
241
+ FLOW_COMPONENT_ID, branch_id=effective_branch
242
+ )
243
+ except KeboolaApiError as exc:
244
+ if exc.error_code == ErrorCode.NOT_FOUND:
245
+ configs = []
246
+ else:
247
+ raise
248
+ for cfg in configs:
249
+ flow_row: dict[str, Any] = {
250
+ "project_alias": alias,
251
+ "component_id": FLOW_COMPONENT_ID,
252
+ "config_id": str(cfg.get("id", "")),
253
+ "name": cfg.get("name", ""),
254
+ "description": cfg.get("description", ""),
255
+ "is_disabled": cfg.get("isDisabled", False),
256
+ }
257
+ if with_schedules:
258
+ flow_row["schedules"] = []
259
+ flows.append(flow_row)
260
+
261
+ # Count (do not list) legacy orchestrator configs so the CLI can warn.
262
+ try:
263
+ legacy = client.list_component_configs(
264
+ LEGACY_FLOW_COMPONENT_ID, branch_id=effective_branch
265
+ )
266
+ legacy_count = len(legacy)
267
+ except KeboolaApiError as exc:
268
+ if exc.error_code == ErrorCode.NOT_FOUND:
269
+ legacy_count = 0
270
+ else:
271
+ raise
272
+
273
+ # One extra list call per project, then a map-join in memory.
274
+ if with_schedules and flows:
275
+ schedules_by_parent = _collect_schedules_by_parent(client, effective_branch)
276
+ for flow_row in flows:
277
+ key = (flow_row["component_id"], flow_row["config_id"])
278
+ flow_row["schedules"] = schedules_by_parent.get(key, [])
279
+
280
+ return (alias, flows, legacy_count)
281
+ except KeboolaApiError as exc:
282
+ return (
283
+ alias,
284
+ {
285
+ "project_alias": alias,
286
+ "error_code": exc.error_code,
287
+ "message": exc.message,
288
+ },
289
+ )
290
+ except Exception as exc:
291
+ return (
292
+ alias,
293
+ {
294
+ "project_alias": alias,
295
+ "error_code": "UNEXPECTED_ERROR",
296
+ "message": str(exc),
297
+ },
298
+ )
299
+ finally:
300
+ client.close()
301
+
302
+ successes, errors = self._run_parallel(projects, worker)
303
+
304
+ all_flows: list[dict[str, Any]] = []
305
+ legacy_total = 0
306
+ for _, flows, legacy_count in successes:
307
+ all_flows.extend(flows)
308
+ legacy_total += legacy_count
309
+ all_flows.sort(key=lambda f: (f["project_alias"], f["name"].lower()))
310
+ errors.sort(key=lambda e: e.get("project_alias", ""))
311
+
312
+ return {
313
+ "flows": all_flows,
314
+ "errors": errors,
315
+ "legacy_orchestrator_count": legacy_total,
316
+ }
317
+
318
+ # ── detail ──────────────────────────────────────────────────────
319
+
320
+ def get_flow_detail(
321
+ self,
322
+ alias: str,
323
+ config_id: str,
324
+ branch_id: int | None = None,
325
+ ) -> dict[str, Any]:
326
+ """Return full flow detail including phases, tasks, and schedule info.
327
+
328
+ Raises:
329
+ ConfigError: If alias is not found.
330
+ KeboolaApiError: On API failure.
331
+ """
332
+ projects = self.resolve_projects([alias])
333
+ project = projects[alias]
334
+ effective_branch = branch_id or project.active_branch_id
335
+
336
+ client = self._client_factory(project.stack_url, project.token)
337
+ try:
338
+ detail = client.get_config_detail(
339
+ FLOW_COMPONENT_ID, config_id, branch_id=effective_branch
340
+ )
341
+ finally:
342
+ client.close()
343
+
344
+ body = _parse_configuration(detail.get("configuration"))
345
+ phases = body.get("phases", [])
346
+ tasks = body.get("tasks", [])
347
+
348
+ detail["project_alias"] = alias
349
+ detail["component_id"] = FLOW_COMPONENT_ID
350
+ detail["branch_id"] = effective_branch
351
+ detail["phases"] = phases
352
+ detail["tasks"] = tasks
353
+ detail["phase_count"] = len(phases)
354
+ detail["task_count"] = len(tasks)
355
+ return detail
356
+
357
+ # ── create ──────────────────────────────────────────────────────
358
+
359
+ def create_flow(
360
+ self,
361
+ alias: str,
362
+ name: str,
363
+ description: str = "",
364
+ phases: list[dict[str, Any]] | None = None,
365
+ tasks: list[dict[str, Any]] | None = None,
366
+ branch_id: int | None = None,
367
+ ) -> dict[str, Any]:
368
+ """Create a new conditional-flow (keboola.flow) configuration.
369
+
370
+ Args:
371
+ alias: Project alias.
372
+ name: Flow name.
373
+ description: Optional description.
374
+ phases: Phase definitions (validated against the CF schema).
375
+ tasks: Task definitions (validated against the CF schema).
376
+ branch_id: Dev branch override.
377
+
378
+ Raises:
379
+ KeboolaApiError: On API failure or definition validation error
380
+ (error_code='INVALID_FLOW_DEFINITION').
381
+ """
382
+ phases = phases or []
383
+ tasks = tasks or []
384
+
385
+ projects = self.resolve_projects([alias])
386
+ project = projects[alias]
387
+ effective_branch = branch_id or project.active_branch_id
388
+
389
+ fetch = self._fetch_flow_schema(project)
390
+ warnings: list[str] = []
391
+ if fetch.schema is None:
392
+ warnings.append(f"structural schema validation skipped: {fetch.reason}")
393
+
394
+ definition_errors = validate_conditional_flow(phases, tasks, fetch.schema)
395
+ if definition_errors:
396
+ raise KeboolaApiError(
397
+ message="Flow definition is invalid: " + "; ".join(definition_errors),
398
+ status_code=400,
399
+ error_code=ErrorCode.INVALID_FLOW_DEFINITION,
400
+ retryable=False,
401
+ )
402
+ warnings.extend(
403
+ f"Phase '{pid}' is unreachable from the entry phase"
404
+ for pid in find_unreachable_phases(phases)
405
+ )
406
+
407
+ configuration: dict[str, Any] = {"phases": phases, "tasks": tasks}
408
+
409
+ client = self._client_factory(project.stack_url, project.token)
410
+ try:
411
+ result = client.create_config(
412
+ component_id=FLOW_COMPONENT_ID,
413
+ name=name,
414
+ configuration=configuration,
415
+ description=description,
416
+ branch_id=effective_branch,
417
+ )
418
+ finally:
419
+ client.close()
420
+
421
+ result["project_alias"] = alias
422
+ result["branch_id"] = effective_branch
423
+ result["phase_count"] = len(phases)
424
+ result["task_count"] = len(tasks)
425
+ result["warnings"] = warnings
426
+ return result
427
+
428
+ # ── update ──────────────────────────────────────────────────────
429
+
430
+ def update_flow(
431
+ self,
432
+ alias: str,
433
+ config_id: str,
434
+ name: str | None = None,
435
+ description: str | None = None,
436
+ phases: list[dict[str, Any]] | None = None,
437
+ tasks: list[dict[str, Any]] | None = None,
438
+ branch_id: int | None = None,
439
+ ) -> dict[str, Any]:
440
+ """Update an existing conditional-flow (keboola.flow) configuration.
441
+
442
+ When phases and/or tasks are provided, validation runs on the merged
443
+ body (the unspecified side is fetched from the current config) so a
444
+ half-config is never validated.
445
+
446
+ Raises:
447
+ KeboolaApiError: On API failure or definition validation error
448
+ (error_code='INVALID_FLOW_DEFINITION').
449
+ """
450
+ projects = self.resolve_projects([alias])
451
+ project = projects[alias]
452
+ effective_branch = branch_id or project.active_branch_id
453
+
454
+ warnings: list[str] = []
455
+ client = self._client_factory(project.stack_url, project.token)
456
+ try:
457
+ configuration: dict[str, Any] | None = None
458
+ if phases is not None or tasks is not None:
459
+ current = client.get_config_detail(
460
+ FLOW_COMPONENT_ID, config_id, branch_id=effective_branch
461
+ )
462
+ current_body = _parse_configuration(current.get("configuration"))
463
+ merged_phases = phases if phases is not None else current_body.get("phases", [])
464
+ merged_tasks = tasks if tasks is not None else current_body.get("tasks", [])
465
+
466
+ fetch = self._fetch_flow_schema(project)
467
+ if fetch.schema is None:
468
+ warnings.append(f"structural schema validation skipped: {fetch.reason}")
469
+
470
+ definition_errors = validate_conditional_flow(
471
+ merged_phases, merged_tasks, fetch.schema
472
+ )
473
+ if definition_errors:
474
+ raise KeboolaApiError(
475
+ message="Flow definition is invalid: " + "; ".join(definition_errors),
476
+ status_code=400,
477
+ error_code=ErrorCode.INVALID_FLOW_DEFINITION,
478
+ retryable=False,
479
+ )
480
+
481
+ warnings.extend(
482
+ f"Phase '{pid}' is unreachable from the entry phase"
483
+ for pid in find_unreachable_phases(merged_phases)
484
+ )
485
+
486
+ configuration = dict(current_body)
487
+ configuration["phases"] = merged_phases
488
+ configuration["tasks"] = merged_tasks
489
+
490
+ result = client.update_config(
491
+ component_id=FLOW_COMPONENT_ID,
492
+ config_id=config_id,
493
+ name=name,
494
+ description=description,
495
+ configuration=configuration,
496
+ change_description="Updated via kbagent flow update",
497
+ branch_id=effective_branch,
498
+ )
499
+ finally:
500
+ client.close()
501
+
502
+ result["project_alias"] = alias
503
+ result["branch_id"] = effective_branch
504
+ result["warnings"] = warnings
505
+ return result
506
+
507
+ # ── delete ──────────────────────────────────────────────────────
508
+
509
+ def delete_flow(
510
+ self,
511
+ alias: str,
512
+ config_id: str,
513
+ branch_id: int | None = None,
514
+ ) -> dict[str, Any]:
515
+ """Delete a conditional-flow (keboola.flow) configuration.
516
+
517
+ Does NOT automatically remove associated keboola.scheduler configs.
518
+ Use remove_flow_schedule() first if needed.
519
+ """
520
+ projects = self.resolve_projects([alias])
521
+ project = projects[alias]
522
+ effective_branch = branch_id or project.active_branch_id
523
+
524
+ client = self._client_factory(project.stack_url, project.token)
525
+ try:
526
+ client.delete_config(
527
+ component_id=FLOW_COMPONENT_ID,
528
+ config_id=config_id,
529
+ branch_id=effective_branch,
530
+ )
531
+ finally:
532
+ client.close()
533
+
534
+ return {
535
+ "status": "deleted",
536
+ "project_alias": alias,
537
+ "component_id": FLOW_COMPONENT_ID,
538
+ "config_id": config_id,
539
+ "branch_id": effective_branch,
540
+ }
541
+
542
+ # ── schedule ────────────────────────────────────────────────────
543
+
544
+ def list_flow_schedules(
545
+ self,
546
+ alias: str,
547
+ config_id: str,
548
+ branch_id: int | None = None,
549
+ ) -> dict[str, Any]:
550
+ """List keboola.scheduler configs that target this flow.
551
+
552
+ Fetches all keboola.scheduler configs and filters by
553
+ target.componentId == keboola.flow + target.configurationId.
554
+ """
555
+ projects = self.resolve_projects([alias])
556
+ project = projects[alias]
557
+ effective_branch = branch_id or project.active_branch_id
558
+
559
+ client = self._client_factory(project.stack_url, project.token)
560
+ try:
561
+ try:
562
+ all_sched = client.list_component_configs(
563
+ SCHEDULER_COMPONENT_ID, branch_id=effective_branch
564
+ )
565
+ except KeboolaApiError as exc:
566
+ if exc.error_code == ErrorCode.NOT_FOUND:
567
+ all_sched = []
568
+ else:
569
+ raise
570
+ finally:
571
+ client.close()
572
+
573
+ schedules: list[dict[str, Any]] = []
574
+ for sched in all_sched:
575
+ body = _parse_configuration(sched.get("configuration"))
576
+ target = body.get("target") or {}
577
+ if target.get("componentId") == FLOW_COMPONENT_ID and str(
578
+ target.get("configurationId", "")
579
+ ) == str(config_id):
580
+ sched_info = body.get("schedule") or {}
581
+ schedules.append(
582
+ {
583
+ "schedule_id": str(sched.get("id", "")),
584
+ "name": sched.get("name", ""),
585
+ "cron_tab": sched_info.get("cronTab", ""),
586
+ "timezone": sched_info.get("timezone", "UTC"),
587
+ "state": sched_info.get("state", "disabled"),
588
+ }
589
+ )
590
+
591
+ return {
592
+ "project_alias": alias,
593
+ "component_id": FLOW_COMPONENT_ID,
594
+ "config_id": config_id,
595
+ "schedules": schedules,
596
+ }
597
+
598
+ def set_flow_schedule(
599
+ self,
600
+ alias: str,
601
+ config_id: str,
602
+ cron_tab: str,
603
+ timezone: str = "UTC",
604
+ enabled: bool = True,
605
+ schedule_name: str | None = None,
606
+ branch_id: int | None = None,
607
+ ) -> dict[str, Any]:
608
+ """Upsert a keboola.scheduler config that targets this flow.
609
+
610
+ If a schedule already exists for this flow it is updated in-place
611
+ (idempotent). If none exists a new one is created. This prevents
612
+ duplicate schedules when called repeatedly.
613
+
614
+ The schedule is stored as a keboola.scheduler configuration whose
615
+ ``target`` points at the keboola.flow component + config.
616
+
617
+ Args:
618
+ alias: Project alias.
619
+ config_id: Flow configuration ID.
620
+ cron_tab: Cron expression (e.g. '0 6 * * *').
621
+ timezone: IANA timezone (default 'UTC').
622
+ enabled: Whether the schedule is active.
623
+ schedule_name: Optional scheduler config name.
624
+ branch_id: Dev branch override.
625
+ """
626
+ projects = self.resolve_projects([alias])
627
+ project = projects[alias]
628
+ effective_branch = branch_id or project.active_branch_id
629
+
630
+ client = self._client_factory(project.stack_url, project.token)
631
+ try:
632
+ if not schedule_name:
633
+ try:
634
+ detail = client.get_config_detail(
635
+ FLOW_COMPONENT_ID, config_id, branch_id=effective_branch
636
+ )
637
+ schedule_name = f"{detail.get('name', config_id)} (Schedule)"
638
+ except KeboolaApiError:
639
+ schedule_name = f"{config_id} (Schedule)"
640
+
641
+ configuration = {
642
+ "schedule": {
643
+ "cronTab": cron_tab,
644
+ "timezone": timezone,
645
+ "state": "enabled" if enabled else "disabled",
646
+ },
647
+ "target": {
648
+ "mode": "run",
649
+ "componentId": FLOW_COMPONENT_ID,
650
+ "configurationId": config_id,
651
+ },
652
+ }
653
+
654
+ # Upsert: update existing schedule if one exists
655
+ try:
656
+ existing = client.list_component_configs(
657
+ SCHEDULER_COMPONENT_ID, branch_id=effective_branch
658
+ )
659
+ except KeboolaApiError as exc:
660
+ if exc.error_code == ErrorCode.NOT_FOUND:
661
+ existing = []
662
+ else:
663
+ raise
664
+
665
+ existing_id: str | None = None
666
+ for sched in existing:
667
+ body = _parse_configuration(sched.get("configuration"))
668
+ target = body.get("target") or {}
669
+ if target.get("componentId") == FLOW_COMPONENT_ID and str(
670
+ target.get("configurationId", "")
671
+ ) == str(config_id):
672
+ existing_id = str(sched.get("id", ""))
673
+ break
674
+
675
+ if existing_id:
676
+ result = client.update_config(
677
+ component_id=SCHEDULER_COMPONENT_ID,
678
+ config_id=existing_id,
679
+ name=schedule_name,
680
+ configuration=configuration,
681
+ branch_id=effective_branch,
682
+ )
683
+ status = "updated"
684
+ else:
685
+ result = client.create_config(
686
+ component_id=SCHEDULER_COMPONENT_ID,
687
+ name=schedule_name,
688
+ configuration=configuration,
689
+ branch_id=effective_branch,
690
+ )
691
+ status = "created"
692
+ finally:
693
+ client.close()
694
+
695
+ return {
696
+ "status": status,
697
+ "project_alias": alias,
698
+ "schedule_id": str(result.get("id", existing_id or "")),
699
+ "schedule_name": schedule_name,
700
+ "component_id": FLOW_COMPONENT_ID,
701
+ "config_id": config_id,
702
+ "cron_tab": cron_tab,
703
+ "timezone": timezone,
704
+ "state": "enabled" if enabled else "disabled",
705
+ "branch_id": effective_branch,
706
+ }
707
+
708
+ def remove_flow_schedule(
709
+ self,
710
+ alias: str,
711
+ config_id: str,
712
+ branch_id: int | None = None,
713
+ ) -> dict[str, Any]:
714
+ """Delete all keboola.scheduler configs that target this flow.
715
+
716
+ Idempotent: if no schedules exist, returns deleted_count=0.
717
+ """
718
+ projects = self.resolve_projects([alias])
719
+ project = projects[alias]
720
+ effective_branch = branch_id or project.active_branch_id
721
+
722
+ client = self._client_factory(project.stack_url, project.token)
723
+ try:
724
+ try:
725
+ all_sched = client.list_component_configs(
726
+ SCHEDULER_COMPONENT_ID, branch_id=effective_branch
727
+ )
728
+ except KeboolaApiError as exc:
729
+ if exc.error_code == ErrorCode.NOT_FOUND:
730
+ all_sched = []
731
+ else:
732
+ raise
733
+
734
+ deleted: list[str] = []
735
+ errors: list[str] = []
736
+ for sched in all_sched:
737
+ body = _parse_configuration(sched.get("configuration"))
738
+ target = body.get("target") or {}
739
+ if target.get("componentId") == FLOW_COMPONENT_ID and str(
740
+ target.get("configurationId", "")
741
+ ) == str(config_id):
742
+ sched_id = str(sched.get("id", ""))
743
+ try:
744
+ client.delete_config(
745
+ SCHEDULER_COMPONENT_ID, sched_id, branch_id=effective_branch
746
+ )
747
+ deleted.append(sched_id)
748
+ except KeboolaApiError as exc:
749
+ errors.append(f"{sched_id}: {exc.message}")
750
+ finally:
751
+ client.close()
752
+
753
+ if errors and not deleted:
754
+ raise KeboolaApiError(
755
+ message=f"Failed to delete schedules: {'; '.join(errors)}",
756
+ status_code=0,
757
+ error_code=ErrorCode.SCHEDULE_DELETE_FAILED,
758
+ retryable=False,
759
+ )
760
+
761
+ return {
762
+ "status": "removed",
763
+ "project_alias": alias,
764
+ "component_id": FLOW_COMPONENT_ID,
765
+ "config_id": config_id,
766
+ "deleted_schedule_ids": deleted,
767
+ "deleted_count": len(deleted),
768
+ "branch_id": effective_branch,
769
+ }