databay-cli 0.1.0__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 (403) hide show
  1. databay/__init__.py +5 -0
  2. databay/__main__.py +7 -0
  3. databay/agent_skill.py +184 -0
  4. databay/api.py +626 -0
  5. databay/cli/__init__.py +0 -0
  6. databay/cli/auth.py +377 -0
  7. databay/cli/build.py +89 -0
  8. databay/cli/commands/__init__.py +0 -0
  9. databay/cli/commands/alerting_webhook.py +19 -0
  10. databay/cli/commands/alerting_webhook_tag.py +14 -0
  11. databay/cli/commands/alerting_webhook_type.py +14 -0
  12. databay/cli/commands/alerting_webhook_undelivered.py +15 -0
  13. databay/cli/commands/announcement.py +17 -0
  14. databay/cli/commands/api_key.py +18 -0
  15. databay/cli/commands/api_key_project.py +16 -0
  16. databay/cli/commands/backfill_reason_code.py +14 -0
  17. databay/cli/commands/backfill_run.py +17 -0
  18. databay/cli/commands/canvas.py +16 -0
  19. databay/cli/commands/canvas_graph.py +15 -0
  20. databay/cli/commands/canvas_table_check.py +15 -0
  21. databay/cli/commands/dashboards.py +21 -0
  22. databay/cli/commands/dashboards_card.py +20 -0
  23. databay/cli/commands/dashboards_dataset.py +14 -0
  24. databay/cli/commands/dashboards_folder.py +16 -0
  25. databay/cli/commands/dashboards_home.py +17 -0
  26. databay/cli/commands/dashboards_placement.py +16 -0
  27. databay/cli/commands/db.py +19 -0
  28. databay/cli/commands/db_column.py +14 -0
  29. databay/cli/commands/docs_article.py +16 -0
  30. databay/cli/commands/dq_result.py +14 -0
  31. databay/cli/commands/dq_rule.py +16 -0
  32. databay/cli/commands/feature.py +14 -0
  33. databay/cli/commands/google_ads_source.py +14 -0
  34. databay/cli/commands/google_analytics_source.py +14 -0
  35. databay/cli/commands/instagram_account.py +14 -0
  36. databay/cli/commands/instagram_source.py +14 -0
  37. databay/cli/commands/linkedin_account.py +14 -0
  38. databay/cli/commands/linkedin_source.py +14 -0
  39. databay/cli/commands/me.py +14 -0
  40. databay/cli/commands/meta_ads_source.py +14 -0
  41. databay/cli/commands/metric_models.py +26 -0
  42. databay/cli/commands/metrics.py +14 -0
  43. databay/cli/commands/models.py +16 -0
  44. databay/cli/commands/nlq.py +15 -0
  45. databay/cli/commands/notebook_credential.py +16 -0
  46. databay/cli/commands/org.py +14 -0
  47. databay/cli/commands/org_member.py +17 -0
  48. databay/cli/commands/org_offboarding.py +14 -0
  49. databay/cli/commands/pipe.py +26 -0
  50. databay/cli/commands/pipe_backfill_run.py +14 -0
  51. databay/cli/commands/pipe_config_option.py +14 -0
  52. databay/cli/commands/pipe_destination.py +16 -0
  53. databay/cli/commands/pipe_leftover.py +16 -0
  54. databay/cli/commands/pipe_sink_type.py +14 -0
  55. databay/cli/commands/pipe_source_type.py +14 -0
  56. databay/cli/commands/pipe_status_bucket.py +14 -0
  57. databay/cli/commands/pipe_stuck_delete.py +14 -0
  58. databay/cli/commands/plan.py +14 -0
  59. databay/cli/commands/project.py +21 -0
  60. databay/cli/commands/project_budget.py +15 -0
  61. databay/cli/commands/project_member.py +18 -0
  62. databay/cli/commands/project_privilege.py +16 -0
  63. databay/cli/commands/project_share.py +16 -0
  64. databay/cli/commands/provision.py +14 -0
  65. databay/cli/commands/provisioning.py +14 -0
  66. databay/cli/commands/registry.py +14 -0
  67. databay/cli/commands/resource_home.py +14 -0
  68. databay/cli/commands/schema_change.py +18 -0
  69. databay/cli/commands/schema_history.py +17 -0
  70. databay/cli/commands/schema_version.py +16 -0
  71. databay/cli/commands/shopify_store.py +14 -0
  72. databay/cli/commands/sink_data_type.py +14 -0
  73. databay/cli/commands/source.py +24 -0
  74. databay/cli/commands/source_catalog.py +15 -0
  75. databay/cli/commands/source_check_run.py +17 -0
  76. databay/cli/commands/source_hidden_type.py +14 -0
  77. databay/cli/commands/source_name.py +14 -0
  78. databay/cli/commands/source_pipe.py +14 -0
  79. databay/cli/commands/source_publication.py +16 -0
  80. databay/cli/commands/source_readiness_override.py +15 -0
  81. databay/cli/commands/source_setup_choice.py +16 -0
  82. databay/cli/commands/source_stream.py +15 -0
  83. databay/cli/commands/source_table.py +14 -0
  84. databay/cli/commands/ssbi_job.py +18 -0
  85. databay/cli/commands/transform_type.py +14 -0
  86. databay/cli/commands/usage.py +14 -0
  87. databay/cli/commands/warehouse.py +21 -0
  88. databay/cli/commands/warehouse_access_assignment.py +16 -0
  89. databay/cli/commands/warehouse_access_role.py +22 -0
  90. databay/cli/commands/warehouse_access_settings.py +17 -0
  91. databay/cli/commands/warehouse_login.py +20 -0
  92. databay/cli/commands/warehouse_principal.py +17 -0
  93. databay/cli/commands/warehouse_service_account.py +17 -0
  94. databay/cli/commands/warehouse_service_account_credential.py +18 -0
  95. databay/cli/common.py +93 -0
  96. databay/cli/console.py +121 -0
  97. databay/cli/flags.py +183 -0
  98. databay/cli/handwritten.py +22 -0
  99. databay/cli/main.py +198 -0
  100. databay/cli/metrics.py +171 -0
  101. databay/cli/org.py +193 -0
  102. databay/cli/output.py +134 -0
  103. databay/cli/pipe_wait.py +357 -0
  104. databay/cli/profile.py +230 -0
  105. databay/cli/runner.py +432 -0
  106. databay/cli/skill.py +64 -0
  107. databay/cli/sql.py +84 -0
  108. databay/compat.py +161 -0
  109. databay/credentials.py +217 -0
  110. databay/error_catalog.py +226 -0
  111. databay/error_codes.py +3265 -0
  112. databay/errors.py +222 -0
  113. databay/feature_header.py +40 -0
  114. databay/invoke.py +225 -0
  115. databay/locks.py +41 -0
  116. databay/login_flow.py +245 -0
  117. databay/model_base.py +40 -0
  118. databay/models/__init__.py +0 -0
  119. databay/models/alerting_webhook.py +46 -0
  120. databay/models/alerting_webhook_tag.py +11 -0
  121. databay/models/alerting_webhook_type.py +11 -0
  122. databay/models/alerting_webhook_undelivered.py +23 -0
  123. databay/models/announcement.py +37 -0
  124. databay/models/api_key.py +81 -0
  125. databay/models/api_key_project.py +29 -0
  126. databay/models/backfill_reason_code.py +11 -0
  127. databay/models/backfill_run.py +41 -0
  128. databay/models/canvas.py +68 -0
  129. databay/models/canvas_graph.py +86 -0
  130. databay/models/canvas_table_check.py +181 -0
  131. databay/models/dashboards.py +79 -0
  132. databay/models/dashboards_card.py +37 -0
  133. databay/models/dashboards_dataset.py +27 -0
  134. databay/models/dashboards_folder.py +18 -0
  135. databay/models/dashboards_home.py +28 -0
  136. databay/models/db.py +34 -0
  137. databay/models/db_column.py +11 -0
  138. databay/models/docs_article.py +59 -0
  139. databay/models/dq_result.py +27 -0
  140. databay/models/dq_rule.py +43 -0
  141. databay/models/feature.py +15 -0
  142. databay/models/google_ads_source.py +11 -0
  143. databay/models/google_analytics_source.py +11 -0
  144. databay/models/instagram_source.py +11 -0
  145. databay/models/linkedin_source.py +11 -0
  146. databay/models/me.py +71 -0
  147. databay/models/meta_ads_source.py +11 -0
  148. databay/models/metric_models.py +274 -0
  149. databay/models/metrics.py +158 -0
  150. databay/models/models.py +73 -0
  151. databay/models/nlq.py +92 -0
  152. databay/models/notebook_credential.py +51 -0
  153. databay/models/org.py +44 -0
  154. databay/models/org_member.py +123 -0
  155. databay/models/org_offboarding.py +36 -0
  156. databay/models/pipe.py +340 -0
  157. databay/models/pipe_config_option.py +25 -0
  158. databay/models/pipe_destination.py +128 -0
  159. databay/models/pipe_leftover.py +51 -0
  160. databay/models/pipe_sink_type.py +11 -0
  161. databay/models/pipe_source_type.py +11 -0
  162. databay/models/pipe_status_bucket.py +13 -0
  163. databay/models/pipe_stuck_delete.py +25 -0
  164. databay/models/plan.py +37 -0
  165. databay/models/project.py +154 -0
  166. databay/models/project_budget.py +22 -0
  167. databay/models/project_member.py +67 -0
  168. databay/models/project_privilege.py +21 -0
  169. databay/models/project_share.py +76 -0
  170. databay/models/provision.py +12 -0
  171. databay/models/provisioning.py +20 -0
  172. databay/models/registry.py +18 -0
  173. databay/models/resource_home.py +35 -0
  174. databay/models/schema_change.py +96 -0
  175. databay/models/schema_history.py +32 -0
  176. databay/models/schema_version.py +23 -0
  177. databay/models/shared.py +875 -0
  178. databay/models/shopify_store.py +19 -0
  179. databay/models/sink_data_type.py +11 -0
  180. databay/models/source.py +263 -0
  181. databay/models/source_catalog.py +82 -0
  182. databay/models/source_check_run.py +63 -0
  183. databay/models/source_hidden_type.py +13 -0
  184. databay/models/source_name.py +18 -0
  185. databay/models/source_pipe.py +16 -0
  186. databay/models/source_publication.py +30 -0
  187. databay/models/source_readiness_override.py +13 -0
  188. databay/models/source_setup_choice.py +33 -0
  189. databay/models/source_stream.py +65 -0
  190. databay/models/source_table.py +27 -0
  191. databay/models/sql.py +31 -0
  192. databay/models/ssbi_job.py +97 -0
  193. databay/models/transform_type.py +40 -0
  194. databay/models/usage.py +112 -0
  195. databay/models/warehouse.py +38 -0
  196. databay/models/warehouse_access_assignment.py +70 -0
  197. databay/models/warehouse_access_role.py +148 -0
  198. databay/models/warehouse_access_settings.py +35 -0
  199. databay/models/warehouse_login.py +20 -0
  200. databay/models/warehouse_principal.py +49 -0
  201. databay/models/warehouse_service_account.py +35 -0
  202. databay/models/warehouse_service_account_credential.py +11 -0
  203. databay/oidc.py +205 -0
  204. databay/operation.py +191 -0
  205. databay/operations/__init__.py +0 -0
  206. databay/operations/alerting_webhook.py +317 -0
  207. databay/operations/alerting_webhook_tag.py +35 -0
  208. databay/operations/alerting_webhook_type.py +35 -0
  209. databay/operations/alerting_webhook_undelivered.py +74 -0
  210. databay/operations/announcement.py +135 -0
  211. databay/operations/api_key.py +198 -0
  212. databay/operations/api_key_project.py +130 -0
  213. databay/operations/backfill_reason_code.py +35 -0
  214. databay/operations/backfill_run.py +245 -0
  215. databay/operations/canvas.py +136 -0
  216. databay/operations/canvas_graph.py +102 -0
  217. databay/operations/canvas_table_check.py +101 -0
  218. databay/operations/dashboards.py +441 -0
  219. databay/operations/dashboards_card.py +406 -0
  220. databay/operations/dashboards_dataset.py +50 -0
  221. databay/operations/dashboards_folder.py +146 -0
  222. databay/operations/dashboards_home.py +176 -0
  223. databay/operations/dashboards_placement.py +152 -0
  224. databay/operations/db.py +337 -0
  225. databay/operations/db_column.py +57 -0
  226. databay/operations/docs_article.py +101 -0
  227. databay/operations/dq_result.py +43 -0
  228. databay/operations/dq_rule.py +155 -0
  229. databay/operations/feature.py +35 -0
  230. databay/operations/google_ads_source.py +40 -0
  231. databay/operations/google_analytics_source.py +40 -0
  232. databay/operations/instagram_account.py +47 -0
  233. databay/operations/instagram_source.py +40 -0
  234. databay/operations/linkedin_account.py +47 -0
  235. databay/operations/linkedin_source.py +40 -0
  236. databay/operations/me.py +57 -0
  237. databay/operations/meta_ads_source.py +40 -0
  238. databay/operations/metric_models.py +612 -0
  239. databay/operations/metrics.py +218 -0
  240. databay/operations/models.py +123 -0
  241. databay/operations/nlq.py +100 -0
  242. databay/operations/notebook_credential.py +95 -0
  243. databay/operations/org.py +79 -0
  244. databay/operations/org_member.py +192 -0
  245. databay/operations/org_offboarding.py +43 -0
  246. databay/operations/pipe.py +882 -0
  247. databay/operations/pipe_backfill_run.py +72 -0
  248. databay/operations/pipe_config_option.py +50 -0
  249. databay/operations/pipe_destination.py +178 -0
  250. databay/operations/pipe_leftover.py +110 -0
  251. databay/operations/pipe_sink_type.py +35 -0
  252. databay/operations/pipe_source_type.py +35 -0
  253. databay/operations/pipe_status_bucket.py +35 -0
  254. databay/operations/pipe_stuck_delete.py +52 -0
  255. databay/operations/plan.py +35 -0
  256. databay/operations/project.py +346 -0
  257. databay/operations/project_budget.py +135 -0
  258. databay/operations/project_member.py +226 -0
  259. databay/operations/project_privilege.py +143 -0
  260. databay/operations/project_share.py +160 -0
  261. databay/operations/provision.py +35 -0
  262. databay/operations/provisioning.py +35 -0
  263. databay/operations/registry.py +35 -0
  264. databay/operations/resource_home.py +80 -0
  265. databay/operations/schema_change.py +222 -0
  266. databay/operations/schema_history.py +136 -0
  267. databay/operations/schema_version.py +115 -0
  268. databay/operations/shopify_store.py +40 -0
  269. databay/operations/sink_data_type.py +43 -0
  270. databay/operations/source.py +1207 -0
  271. databay/operations/source_catalog.py +135 -0
  272. databay/operations/source_check_run.py +242 -0
  273. databay/operations/source_hidden_type.py +59 -0
  274. databay/operations/source_name.py +69 -0
  275. databay/operations/source_pipe.py +57 -0
  276. databay/operations/source_publication.py +139 -0
  277. databay/operations/source_readiness_override.py +96 -0
  278. databay/operations/source_setup_choice.py +161 -0
  279. databay/operations/source_stream.py +99 -0
  280. databay/operations/source_table.py +59 -0
  281. databay/operations/sql.py +54 -0
  282. databay/operations/ssbi_job.py +242 -0
  283. databay/operations/transform_type.py +35 -0
  284. databay/operations/usage.py +35 -0
  285. databay/operations/warehouse.py +343 -0
  286. databay/operations/warehouse_access_assignment.py +179 -0
  287. databay/operations/warehouse_access_role.py +473 -0
  288. databay/operations/warehouse_access_settings.py +172 -0
  289. databay/operations/warehouse_login.py +308 -0
  290. databay/operations/warehouse_principal.py +174 -0
  291. databay/operations/warehouse_service_account.py +206 -0
  292. databay/operations/warehouse_service_account_credential.py +285 -0
  293. databay/paths.py +32 -0
  294. databay/platform_check.py +20 -0
  295. databay/profiles.py +181 -0
  296. databay/redact.py +22 -0
  297. databay/reference.py +218 -0
  298. databay/resource.py +167 -0
  299. databay/resources/__init__.py +0 -0
  300. databay/resources/alerting_webhook.py +84 -0
  301. databay/resources/alerting_webhook_tag.py +21 -0
  302. databay/resources/alerting_webhook_type.py +21 -0
  303. databay/resources/alerting_webhook_undelivered.py +35 -0
  304. databay/resources/announcement.py +45 -0
  305. databay/resources/api_key.py +56 -0
  306. databay/resources/api_key_project.py +47 -0
  307. databay/resources/backfill_reason_code.py +21 -0
  308. databay/resources/backfill_run.py +78 -0
  309. databay/resources/canvas.py +43 -0
  310. databay/resources/canvas_graph.py +38 -0
  311. databay/resources/canvas_table_check.py +38 -0
  312. databay/resources/dashboards.py +105 -0
  313. databay/resources/dashboards_card.py +97 -0
  314. databay/resources/dashboards_dataset.py +27 -0
  315. databay/resources/dashboards_folder.py +47 -0
  316. databay/resources/dashboards_home.py +59 -0
  317. databay/resources/dashboards_placement.py +47 -0
  318. databay/resources/db.py +87 -0
  319. databay/resources/db_column.py +28 -0
  320. databay/resources/docs_article.py +37 -0
  321. databay/resources/dq_result.py +25 -0
  322. databay/resources/dq_rule.py +48 -0
  323. databay/resources/feature.py +21 -0
  324. databay/resources/google_ads_source.py +25 -0
  325. databay/resources/google_analytics_source.py +25 -0
  326. databay/resources/instagram_account.py +27 -0
  327. databay/resources/instagram_source.py +25 -0
  328. databay/resources/linkedin_account.py +27 -0
  329. databay/resources/linkedin_source.py +25 -0
  330. databay/resources/me.py +27 -0
  331. databay/resources/meta_ads_source.py +25 -0
  332. databay/resources/metric_models.py +154 -0
  333. databay/resources/metrics.py +57 -0
  334. databay/resources/models.py +42 -0
  335. databay/resources/nlq.py +34 -0
  336. databay/resources/notebook_credential.py +41 -0
  337. databay/resources/org.py +29 -0
  338. databay/resources/org_member.py +53 -0
  339. databay/resources/org_offboarding.py +25 -0
  340. databay/resources/pipe.py +208 -0
  341. databay/resources/pipe_backfill_run.py +28 -0
  342. databay/resources/pipe_config_option.py +26 -0
  343. databay/resources/pipe_destination.py +49 -0
  344. databay/resources/pipe_leftover.py +42 -0
  345. databay/resources/pipe_sink_type.py +21 -0
  346. databay/resources/pipe_source_type.py +21 -0
  347. databay/resources/pipe_status_bucket.py +21 -0
  348. databay/resources/pipe_stuck_delete.py +27 -0
  349. databay/resources/plan.py +21 -0
  350. databay/resources/project.py +92 -0
  351. databay/resources/project_budget.py +38 -0
  352. databay/resources/project_member.py +68 -0
  353. databay/resources/project_privilege.py +48 -0
  354. databay/resources/project_share.py +47 -0
  355. databay/resources/provision.py +21 -0
  356. databay/resources/provisioning.py +21 -0
  357. databay/resources/registry.py +21 -0
  358. databay/resources/resource_home.py +29 -0
  359. databay/resources/schema_change.py +62 -0
  360. databay/resources/schema_history.py +52 -0
  361. databay/resources/schema_version.py +43 -0
  362. databay/resources/shopify_store.py +25 -0
  363. databay/resources/sink_data_type.py +25 -0
  364. databay/resources/source.py +132 -0
  365. databay/resources/source_catalog.py +34 -0
  366. databay/resources/source_check_run.py +67 -0
  367. databay/resources/source_hidden_type.py +25 -0
  368. databay/resources/source_name.py +27 -0
  369. databay/resources/source_pipe.py +28 -0
  370. databay/resources/source_publication.py +47 -0
  371. databay/resources/source_readiness_override.py +37 -0
  372. databay/resources/source_setup_choice.py +50 -0
  373. databay/resources/source_stream.py +38 -0
  374. databay/resources/source_table.py +27 -0
  375. databay/resources/sql.py +27 -0
  376. databay/resources/ssbi_job.py +69 -0
  377. databay/resources/transform_type.py +21 -0
  378. databay/resources/usage.py +21 -0
  379. databay/resources/warehouse.py +136 -0
  380. databay/resources/warehouse_access_assignment.py +51 -0
  381. databay/resources/warehouse_access_role.py +118 -0
  382. databay/resources/warehouse_access_settings.py +56 -0
  383. databay/resources/warehouse_login.py +81 -0
  384. databay/resources/warehouse_principal.py +51 -0
  385. databay/resources/warehouse_service_account.py +61 -0
  386. databay/resources/warehouse_service_account_credential.py +78 -0
  387. databay/sdk.py +77 -0
  388. databay/settings.py +90 -0
  389. databay/skill/SKILL.md +357 -0
  390. databay/skill/references/commands.md +7025 -0
  391. databay/skill/references/errors.md +727 -0
  392. databay/spec.py +362 -0
  393. databay/stored_token.py +134 -0
  394. databay/tokens.py +108 -0
  395. databay/transport.py +357 -0
  396. databay/update_check.py +151 -0
  397. databay/version.py +6 -0
  398. databay/waits.py +330 -0
  399. databay_cli-0.1.0.dist-info/METADATA +42 -0
  400. databay_cli-0.1.0.dist-info/RECORD +403 -0
  401. databay_cli-0.1.0.dist-info/WHEEL +4 -0
  402. databay_cli-0.1.0.dist-info/entry_points.txt +2 -0
  403. databay_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
databay/waits.py ADDED
@@ -0,0 +1,330 @@
1
+ """
2
+ Waiting on what an operation started, by polling its declared status operation.
3
+
4
+ Each status value is pending, success or failure, as the ``Wait`` declares; a value it does not
5
+ name (a newer server's) stops the wait with ``unexpected_wait_state``. A 404 is
6
+ ``wait_target_gone``, or ``wait_feature_disabled`` when it says the feature is off. 5xx, 408, 429 and
7
+ network failures are retried within the timeout until ``max_transient_in_a_row`` in a row; any other
8
+ error answer stops the wait with its own error.
9
+ Running out of time is ``wait_pending``: the same wait can be resumed.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import importlib
15
+ import time
16
+ from collections.abc import Callable
17
+ from dataclasses import dataclass
18
+ from typing import TYPE_CHECKING, Any, Literal
19
+
20
+ from databay.errors import (
21
+ DatabayError,
22
+ DatabayFeatureDisabled,
23
+ DatabayNetworkError,
24
+ DatabayResponseInvalid,
25
+ DatabayWaitError,
26
+ )
27
+ from databay.invoke import CallValues, build_request, decode, json_text, pointer
28
+ from databay.transport import error_from, is_transient, retry_after_seconds
29
+
30
+ if TYPE_CHECKING:
31
+ from databay.invoke import Session
32
+ from databay.operation import Operation, Wait
33
+ from databay.transport import Request
34
+
35
+ #: ``not_started``: the trigger's answer names nothing to wait on.
36
+ Outcome = Literal["success", "pending", "failure", "not_started"]
37
+
38
+
39
+ @dataclass(slots=True)
40
+ class WaitOutcome:
41
+ outcome: Outcome
42
+ #: The status value last seen, as the declaration spells it; ``None`` before the first answer.
43
+ state: str | None
44
+ #: The last status answer, as JSON.
45
+ status: Any
46
+ elapsed_s: float
47
+ #: The status operation's parameters, by name: what resuming the wait polls again.
48
+ bound: dict[str, str]
49
+ status_operation: str
50
+ #: Why a successful wait ended, when it can end more than one way.
51
+ reason: str | None = None
52
+
53
+ def to_dict(self) -> dict[str, Any]:
54
+ data = {
55
+ "outcome": self.outcome,
56
+ "state": self.state,
57
+ "elapsed_s": round(self.elapsed_s, 1),
58
+ "status_operation": self.status_operation,
59
+ "status": self.status,
60
+ }
61
+ return data if self.reason is None else {**data, "reason": self.reason}
62
+
63
+
64
+ def operation(operation_id: str) -> Operation:
65
+ """The generated ``Operation`` with this id."""
66
+ from databay.spec import OPERATIONS # noqa: PLC0415
67
+
68
+ module, attribute = OPERATIONS[operation_id]
69
+ return getattr(importlib.import_module(f"databay.operations.{module}"), attribute)
70
+
71
+
72
+ def not_started(wait: Wait) -> WaitOutcome:
73
+ """The outcome of a wait whose trigger started nothing: the answer has no value to bind."""
74
+ return WaitOutcome("not_started", None, None, 0.0, {}, wait.status_operation)
75
+
76
+
77
+ def bind(wait: Wait, trigger: Any, trigger_path: dict[str, str]) -> dict[str, str] | None: # noqa: ANN401
78
+ """
79
+ The status operation's parameters, from the trigger's answer or its own path parameters.
80
+
81
+ ``None`` when the answer, already validated against its model, leaves a bound field out or
82
+ null: the trigger started nothing to wait on.
83
+ """
84
+ bound: dict[str, str] = {}
85
+ for parameter, source in wait.bind:
86
+ if source.startswith("path:"):
87
+ bound[parameter] = trigger_path[source.removeprefix("path:")]
88
+ continue
89
+ try:
90
+ value = pointer(trigger, source)
91
+ except (KeyError, IndexError, ValueError):
92
+ value = None
93
+ if value is None:
94
+ return None
95
+ bound[parameter] = json_text(value)
96
+ return bound
97
+
98
+
99
+ def classify(wait: Wait, status: Any) -> tuple[Outcome | None, str]: # noqa: ANN401
100
+ """The outcome a status answer means, and the value it read; ``None`` for a value the wait does not name."""
101
+ value = json_text(pointer(status, wait.field))
102
+ for outcome, values in (("pending", wait.pending), ("success", wait.success), ("failure", wait.failure)):
103
+ if value in values:
104
+ return outcome, value
105
+ for conditional_value, condition in wait.conditional:
106
+ if conditional_value != value:
107
+ continue
108
+ second = json_text(pointer(status, condition.field))
109
+ for outcome, values in (
110
+ ("pending", condition.pending),
111
+ ("success", condition.success),
112
+ ("failure", condition.failure),
113
+ ):
114
+ if second in values:
115
+ return outcome, f"{value} ({condition.field.lstrip('/')}={second})"
116
+ return None, f"{value} ({condition.field.lstrip('/')}={second})"
117
+ return None, value
118
+
119
+
120
+ def _status_values(status_op: Operation, bound: dict[str, str]) -> CallValues:
121
+ values = CallValues()
122
+ for param in status_op.params:
123
+ if param.name not in bound:
124
+ continue
125
+ target = values.path if param.location == "path" else values.query
126
+ target[param.name] = bound[param.name]
127
+ return values
128
+
129
+
130
+ @dataclass(frozen=True, slots=True)
131
+ class Reading:
132
+ """What one tick of a wait read, and whether it ends the wait."""
133
+
134
+ outcome: Outcome
135
+ state: str
136
+ status: Any
137
+ status_operation: str
138
+ #: Raised to end the wait, with the wait's last state as its ``outcome``.
139
+ stop: DatabayWaitError | None = None
140
+ #: Why a successful wait ended, when it can end more than one way.
141
+ reason: str | None = None
142
+
143
+
144
+ class _TransientFailureError(Exception):
145
+ """A tick's request got an answer a later tick can get past: 5xx, 408, 429 or a network failure."""
146
+
147
+ def __init__(self, failure: DatabayError, retry_after: float | None) -> None:
148
+ super().__init__(failure.message)
149
+ self.failure = failure
150
+ self.retry_after = retry_after
151
+
152
+
153
+ #: Sends a tick's request and answers its JSON. It raises what stops the wait, and a transient failure ends the tick.
154
+ Fetch = Callable[["Operation", "Request"], Any]
155
+
156
+
157
+ def read_status(wait: Wait, raw: Any, command: str) -> Reading: # noqa: ANN401
158
+ """A status answer as ``wait`` classifies it; a failure or a value it does not name stops the wait."""
159
+ try:
160
+ outcome, state = classify(wait, raw)
161
+ except (KeyError, IndexError, ValueError):
162
+ raise DatabayResponseInvalid(
163
+ f"{command}: the answer has no {wait.field}", details={"field": wait.field}
164
+ ) from None
165
+ if outcome is None:
166
+ stop = DatabayWaitError(
167
+ f"{command}: {wait.field.lstrip('/')} is {state!r}, a state this CLI does not know",
168
+ code="unexpected_wait_state",
169
+ details={"state": state},
170
+ )
171
+ return Reading("failure", state, raw, wait.status_operation, stop)
172
+ if outcome == "failure":
173
+ reason = _error_text(wait, raw)
174
+ stop = DatabayWaitError(
175
+ f"{command}: ended {state}" + (f": {reason}" if reason else ""),
176
+ details={"state": state, "error": reason},
177
+ )
178
+ return Reading("failure", state, raw, wait.status_operation, stop)
179
+ return Reading(outcome, state, raw, wait.status_operation)
180
+
181
+
182
+ def poll(
183
+ session: Session,
184
+ step: Callable[[Fetch], Reading],
185
+ *,
186
+ command: str,
187
+ status_operation: str,
188
+ bound: dict[str, str],
189
+ limit: float,
190
+ on_state: Callable[[WaitOutcome], None] | None = None,
191
+ clock: Callable[[], float] = time.monotonic,
192
+ sleep: Callable[[float], None] = time.sleep,
193
+ ) -> WaitOutcome:
194
+ """
195
+ Run ``step`` once per tick until a reading ends the wait or ``limit`` seconds pass.
196
+
197
+ ``step`` sends its requests through the ``Fetch`` it is given, which applies the rules every
198
+ wait shares. A request after a tick's first is not sent once ``limit`` plus the policy's
199
+ ``tick_grace_s`` has passed, so a slow tick cannot pile its requests' timeouts on top of the
200
+ limit. ``command`` names the wait in the timeout message.
201
+ """
202
+ policy = session.transport.settings.wait
203
+ started = clock()
204
+ interval = policy.first_interval_s
205
+ transient = 0
206
+ last = WaitOutcome("pending", None, None, 0.0, bound, status_operation)
207
+ tick_has_sent = False
208
+
209
+ def out_of_time(elapsed: float) -> DatabayWaitError:
210
+ last.outcome = "pending"
211
+ last.elapsed_s = elapsed
212
+ return DatabayWaitError(
213
+ f"{command}: still {last.state or 'unknown'} after {round(elapsed)}s",
214
+ code="wait_pending",
215
+ outcome=last,
216
+ details={"state": last.state, "timeout_s": limit},
217
+ )
218
+
219
+ def fetch(op: Operation, request: Request) -> Any: # noqa: ANN401
220
+ nonlocal tick_has_sent
221
+ if tick_has_sent and clock() - started >= limit + policy.tick_grace_s:
222
+ raise out_of_time(clock() - started)
223
+ tick_has_sent = True
224
+ try:
225
+ answer = session.transport.exchange(request, retries=False)
226
+ except DatabayNetworkError as exc:
227
+ raise _TransientFailureError(exc, None) from None
228
+ if answer.status == 404: # noqa: PLR2004
229
+ last.outcome = "failure"
230
+ last.elapsed_s = clock() - started
231
+ refusal = error_from(answer, request)
232
+ if isinstance(refusal, DatabayFeatureDisabled):
233
+ raise DatabayWaitError(
234
+ f"{op.command}: the feature {refusal.feature} is off (source: {refusal.source})",
235
+ code="wait_feature_disabled",
236
+ hint=refusal.hint,
237
+ outcome=last,
238
+ details={"bound": bound, **refusal.details},
239
+ )
240
+ raise DatabayWaitError(
241
+ f"{op.command}: what the wait polled is gone (HTTP 404)",
242
+ code="wait_target_gone",
243
+ outcome=last,
244
+ details={"bound": bound},
245
+ )
246
+ if is_transient(answer.status):
247
+ retry_after = retry_after_seconds(answer.headers.get("retry-after"))
248
+ raise _TransientFailureError(error_from(answer, request), retry_after)
249
+ if answer.status >= 400: # noqa: PLR2004
250
+ raise error_from(answer, request)
251
+ raw, _ = decode(op, answer)
252
+ return raw
253
+
254
+ while True:
255
+ retry_after: float | None = None
256
+ tick_has_sent = False
257
+ try:
258
+ reading = step(fetch)
259
+ except _TransientFailureError as exc:
260
+ retry_after = exc.retry_after
261
+ transient += 1
262
+ if transient >= policy.max_transient_in_a_row:
263
+ exc.failure.details = {**exc.failure.details, "wait": last.to_dict()}
264
+ raise exc.failure from None
265
+ else:
266
+ transient = 0
267
+ changed = reading.state != last.state
268
+ last = WaitOutcome(
269
+ reading.outcome,
270
+ reading.state,
271
+ reading.status,
272
+ clock() - started,
273
+ bound,
274
+ reading.status_operation,
275
+ reading.reason,
276
+ )
277
+ if changed and on_state is not None:
278
+ on_state(last)
279
+ if reading.stop is not None:
280
+ reading.stop.outcome = last
281
+ raise reading.stop
282
+ if reading.outcome == "success":
283
+ return last
284
+ elapsed = clock() - started
285
+ if elapsed >= limit:
286
+ raise out_of_time(elapsed)
287
+ pause = interval
288
+ if retry_after is not None:
289
+ pause = min(max(interval, retry_after), session.transport.settings.retry.retry_after_cap_s)
290
+ sleep(min(pause, limit - elapsed))
291
+ interval = min(interval * policy.backoff, policy.max_interval_s)
292
+
293
+
294
+ def run(
295
+ session: Session,
296
+ wait: Wait,
297
+ bound: dict[str, str],
298
+ *,
299
+ timeout_s: float | None,
300
+ project: str | None = None,
301
+ on_state: Callable[[WaitOutcome], None] | None = None,
302
+ clock: Callable[[], float] = time.monotonic,
303
+ sleep: Callable[[float], None] = time.sleep,
304
+ ) -> WaitOutcome:
305
+ """Poll until success; raise ``DatabayWaitError`` (or the error that stopped it) otherwise."""
306
+ policy = session.transport.settings.wait
307
+ limit = timeout_s if timeout_s is not None else (wait.timeout_s or policy.default_timeout_s)
308
+ status_op = operation(wait.status_operation)
309
+ request = build_request(status_op, _status_values(status_op, bound), project if status_op.project else None)
310
+ return poll(
311
+ session,
312
+ lambda fetch: read_status(wait, fetch(status_op, request), status_op.command),
313
+ command=status_op.command,
314
+ status_operation=wait.status_operation,
315
+ bound=bound,
316
+ limit=limit,
317
+ on_state=on_state,
318
+ clock=clock,
319
+ sleep=sleep,
320
+ )
321
+
322
+
323
+ def _error_text(wait: Wait, raw: Any) -> str | None: # noqa: ANN401
324
+ if wait.error_field is None:
325
+ return None
326
+ try:
327
+ value = pointer(raw, wait.error_field)
328
+ except (KeyError, IndexError, ValueError):
329
+ return None
330
+ return None if value is None else json_text(value)
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.5
2
+ Name: databay-cli
3
+ Version: 0.1.0
4
+ Summary: Databay SDK and the databay CLI, generated from the Databay client contract
5
+ Project-URL: Homepage, https://databurst.tech
6
+ Project-URL: Repository, https://github.com/data-burst/databay-clients
7
+ Project-URL: Changelog, https://github.com/data-burst/databay-clients/blob/main/python/CHANGELOG.md
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Python: >=3.12
15
+ Requires-Dist: click<9,>=8.1.7
16
+ Requires-Dist: httpx<1,>=0.27.2
17
+ Requires-Dist: keyring<26,>=25.5.0
18
+ Requires-Dist: platformdirs<5,>=4.3.6
19
+ Requires-Dist: pydantic<3,>=2.9.2
20
+ Requires-Dist: rich>=13.9.4
21
+ Description-Content-Type: text/markdown
22
+
23
+ # databay-cli
24
+
25
+ The SDK and the `databay` command for a [Databay](https://databurst.tech) server. The agent skill that
26
+ ships with it lets an agent such as Claude Code drive the server through the command.
27
+
28
+ Needs Python 3.12 or newer, on Linux or macOS. The package is `databay-cli`; the `databay` project on
29
+ PyPI is unrelated.
30
+
31
+ Until the first PyPI release, install it from the repository:
32
+
33
+ ```bash
34
+ uv tool install "git+https://github.com/data-burst/databay-clients#subdirectory=python"
35
+ databay login --server https://<host>
36
+ databay skill install --target claude
37
+ ```
38
+
39
+ Install, sign in and releases: [docs/cli.md](https://github.com/data-burst/databay-clients/blob/main/docs/cli.md).
40
+ Changes: [CHANGELOG.md](https://github.com/data-burst/databay-clients/blob/main/python/CHANGELOG.md).
41
+
42
+ Licensed under Apache-2.0: see the `LICENSE` file in the package.