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/login_flow.py ADDED
@@ -0,0 +1,245 @@
1
+ """
2
+ Signing in: the browser and device flows, and keeping what they return.
3
+
4
+ - Browser: authorization code with PKCE; Keycloak sends the browser back to
5
+ ``http://127.0.0.1:<random port>/callback``, served here for one request.
6
+ - Device: the device authorization grant, also with PKCE (the ``databay-cli`` client requires it on
7
+ every flow). Chosen by ``--device``, and automatically over SSH, on Linux without a display, or
8
+ when Python finds no browser: the loopback callback would not reach the user's real browser.
9
+ - API key: the client-credentials grant with a key's client id and secret.
10
+
11
+ Both person flows ask for ``offline_access`` and fail ``offline_access_not_granted`` without it.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import secrets
17
+ import time
18
+ import webbrowser
19
+ from dataclasses import dataclass
20
+ from http.server import BaseHTTPRequestHandler, HTTPServer
21
+ from typing import TYPE_CHECKING, Any
22
+ from urllib.parse import parse_qs, urlencode, urlparse
23
+
24
+ from databay import oidc
25
+ from databay.credentials import Credential
26
+ from databay.errors import DatabayError, DatabayProfileError
27
+ from databay.settings import SETTINGS
28
+
29
+ if TYPE_CHECKING:
30
+ from collections.abc import Callable
31
+
32
+ import httpx
33
+
34
+ from databay.cli.console import Console
35
+ from databay.compat import ServerInfo
36
+
37
+ #: The interval RFC 8628 (section 3.2) sets when the device answer gives none, and the step ``slow_down`` adds.
38
+ DEVICE_POLL_DEFAULT_S = 5
39
+ DEVICE_SLOW_DOWN_S = 5
40
+ CALLBACK_READ_TIMEOUT_S = 5
41
+
42
+
43
+ def device_reason(environ: dict[str, str], platform: str, has_browser: Callable[[], bool]) -> str | None:
44
+ """Why the device flow is used although ``--device`` was not passed; ``None`` keeps the browser flow."""
45
+ if environ.get("SSH_CONNECTION") or environ.get("SSH_TTY"):
46
+ return "this is an SSH session"
47
+ if platform.startswith("linux") and not (environ.get("DISPLAY") or environ.get("WAYLAND_DISPLAY")):
48
+ return "there is no display"
49
+ if not has_browser():
50
+ return "no local browser"
51
+ return None
52
+
53
+
54
+ def local_browser() -> bool:
55
+ try:
56
+ webbrowser.get()
57
+ except webbrowser.Error:
58
+ return False
59
+ return True
60
+
61
+
62
+ def _refused(exc: oidc.OAuthRefusal) -> DatabayError:
63
+ if exc.error in {"expired_token", "access_denied"}:
64
+ exc.code = exc.error
65
+ return exc
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class SignIn:
70
+ """What a flow got: the provider's endpoints, the grant and its subject."""
71
+
72
+ endpoints: oidc.Endpoints
73
+ issued: oidc.Grant
74
+ sub: str
75
+
76
+
77
+ def _checked(endpoints: oidc.Endpoints, issued: oidc.Grant, *, offline: bool) -> SignIn:
78
+ if offline and (oidc.OFFLINE_SCOPE not in issued.scope or issued.refresh_token is None):
79
+ raise DatabayProfileError(
80
+ "Keycloak signed you in without offline_access, so the CLI would lose the session within a day",
81
+ code="offline_access_not_granted",
82
+ details={"granted_scope": " ".join(issued.scope)},
83
+ )
84
+ return SignIn(endpoints, issued, oidc.claims(issued.access_token)["sub"])
85
+
86
+
87
+ class _Callback(BaseHTTPRequestHandler):
88
+ """Answers the browser's one return to the loopback; ``server.query`` keeps its parameters."""
89
+
90
+ #: Seconds a connection may stay silent: a browser's idle preconnect socket must not hold the
91
+ #: one-request-at-a-time server while the real redirect waits behind it.
92
+ timeout = CALLBACK_READ_TIMEOUT_S
93
+
94
+ def do_GET(self) -> None:
95
+ url = urlparse(self.path)
96
+ if url.path != "/callback":
97
+ self.send_error(404)
98
+ return
99
+ self.server.query = {k: v[0] for k, v in parse_qs(url.query).items()} # type: ignore[attr-defined]
100
+ ok = "code" in self.server.query # type: ignore[attr-defined]
101
+ text = "Signed in to Databay. You can close this tab." if ok else "Sign-in failed; see the terminal."
102
+ body = f"<!doctype html><title>Databay CLI</title><p>{text}</p>".encode()
103
+ self.send_response(200)
104
+ self.send_header("Content-Type", "text/html; charset=utf-8")
105
+ self.send_header("Content-Length", str(len(body)))
106
+ self.end_headers()
107
+ self.wfile.write(body)
108
+
109
+ def log_message(self, format: str, *args: Any) -> None: # noqa: A002, ANN401, ARG002 — the stdlib signature
110
+ return
111
+
112
+
113
+ def browser_flow(
114
+ http: httpx.Client,
115
+ endpoints: oidc.Endpoints,
116
+ client_id: str,
117
+ console: Console,
118
+ ) -> SignIn:
119
+ verifier, challenge = oidc.pkce()
120
+ state = secrets.token_urlsafe(24)
121
+ server = HTTPServer(("127.0.0.1", 0), _Callback)
122
+ server.query = None # type: ignore[attr-defined]
123
+ redirect_uri = f"http://127.0.0.1:{server.server_address[1]}/callback"
124
+ url = (
125
+ endpoints.authorization
126
+ + "?"
127
+ + urlencode(
128
+ {
129
+ "response_type": "code",
130
+ "client_id": client_id,
131
+ "redirect_uri": redirect_uri,
132
+ "scope": oidc.LOGIN_SCOPE,
133
+ "state": state,
134
+ "code_challenge": challenge,
135
+ "code_challenge_method": "S256",
136
+ }
137
+ )
138
+ )
139
+ try:
140
+ console.inform(
141
+ "browser_login",
142
+ {"url": url},
143
+ f"Opening the Databay sign-in page in your browser. If it does not open, visit:\n {url}",
144
+ )
145
+ webbrowser.open(url)
146
+ deadline = time.monotonic() + SETTINGS.browser_login_timeout_s
147
+ while server.query is None: # type: ignore[attr-defined]
148
+ left = deadline - time.monotonic()
149
+ if left <= 0:
150
+ raise DatabayProfileError(
151
+ f"the browser did not return to {redirect_uri} within {SETTINGS.browser_login_timeout_s:g}s",
152
+ code="login_timeout",
153
+ )
154
+ server.timeout = left
155
+ server.handle_request()
156
+ finally:
157
+ server.server_close()
158
+ query: dict[str, str] = server.query # type: ignore[attr-defined]
159
+ if query.get("state") != state:
160
+ raise DatabayProfileError(
161
+ "the browser came back with another sign-in's state; nothing was stored", code="login_failed"
162
+ )
163
+ if "error" in query:
164
+ raise _refused(oidc.OAuthRefusal(f"browser sign-in: {query['error']}", error=query["error"]))
165
+ if "code" not in query:
166
+ raise DatabayProfileError(
167
+ "the browser came back without a code or an error; nothing was stored", code="login_failed"
168
+ )
169
+ issued = oidc.grant(
170
+ http,
171
+ endpoints.token,
172
+ {
173
+ "grant_type": "authorization_code",
174
+ "client_id": client_id,
175
+ "code": query["code"],
176
+ "redirect_uri": redirect_uri,
177
+ "code_verifier": verifier,
178
+ },
179
+ )
180
+ return _checked(endpoints, issued, offline=True)
181
+
182
+
183
+ def device_flow(
184
+ http: httpx.Client,
185
+ endpoints: oidc.Endpoints,
186
+ client_id: str,
187
+ console: Console,
188
+ sleep: Callable[[float], None] = time.sleep,
189
+ ) -> SignIn:
190
+ verifier, challenge = oidc.pkce()
191
+ started = oidc.start_device(http, endpoints.device, client_id, challenge)
192
+ link = started.get("verification_uri_complete") or started["verification_uri"]
193
+ console.inform(
194
+ "device_login",
195
+ {
196
+ "verification_uri": started["verification_uri"],
197
+ "verification_uri_complete": started.get("verification_uri_complete"),
198
+ "user_code": started["user_code"],
199
+ "expires_in": started["expires_in"],
200
+ },
201
+ f"To sign in, open {link}\nand check that it shows the code {started['user_code']}.",
202
+ )
203
+ interval = float(started.get("interval", DEVICE_POLL_DEFAULT_S))
204
+ deadline = time.monotonic() + started["expires_in"]
205
+ data = {
206
+ "grant_type": oidc.DEVICE_GRANT,
207
+ "client_id": client_id,
208
+ "device_code": started["device_code"],
209
+ "code_verifier": verifier,
210
+ }
211
+ while True:
212
+ if time.monotonic() >= deadline:
213
+ raise DatabayProfileError("the sign-in code expired before it was approved", code="expired_token")
214
+ sleep(interval)
215
+ try:
216
+ issued = oidc.grant(http, endpoints.token, data)
217
+ except oidc.OAuthRefusal as exc:
218
+ if exc.error == "authorization_pending":
219
+ continue
220
+ if exc.error == "slow_down":
221
+ interval += DEVICE_SLOW_DOWN_S
222
+ continue
223
+ raise _refused(exc) from None
224
+ return _checked(endpoints, issued, offline=True)
225
+
226
+
227
+ def api_key_flow(http: httpx.Client, endpoints: oidc.Endpoints, client_id: str, secret: str) -> SignIn:
228
+ issued = oidc.grant(
229
+ http, endpoints.token, {"grant_type": "client_credentials", "client_id": client_id, "client_secret": secret}
230
+ )
231
+ return _checked(endpoints, issued, offline=False)
232
+
233
+
234
+ def credential(server: str, info: ServerInfo, signed: SignIn, client_id: str, secret: str | None) -> Credential:
235
+ return Credential(
236
+ kind="login" if secret is None else "api_key",
237
+ server=server,
238
+ sub=signed.sub,
239
+ issuer=info.issuer,
240
+ client_id=client_id,
241
+ access_token=signed.issued.access_token,
242
+ expires_at=signed.issued.expires_at,
243
+ refresh_token=signed.issued.refresh_token if secret is None else None,
244
+ client_secret=secret,
245
+ )
databay/model_base.py ADDED
@@ -0,0 +1,40 @@
1
+ """
2
+ Base classes of the generated models.
3
+
4
+ A response model ignores fields it does not know, so a field a newer server adds never breaks
5
+ this client. A response enum is open: a value it does not know becomes an ``Unknown(<value>)``
6
+ member with ``is_unknown`` set, never one of the known members. Code that acts on an enum value
7
+ (a wait) treats an unknown one as an error; display shows it as it came.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from enum import StrEnum
13
+ from typing import Any
14
+
15
+ from pydantic import BaseModel, ConfigDict
16
+
17
+
18
+ class ResponseModel(BaseModel):
19
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
20
+
21
+
22
+ class RequestModel(BaseModel):
23
+ """A schema only requests use: a field it does not declare is an error, caught before sending."""
24
+
25
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
26
+
27
+
28
+ class OpenStrEnum(StrEnum):
29
+ @classmethod
30
+ def _missing_(cls, value: Any) -> OpenStrEnum | None: # noqa: ANN401 — whatever the caller passed
31
+ if not isinstance(value, str):
32
+ return None
33
+ member = str.__new__(cls, value)
34
+ member._name_ = f"Unknown({value})"
35
+ member._value_ = value
36
+ return member
37
+
38
+ @property
39
+ def is_unknown(self) -> bool:
40
+ return self._name_.startswith("Unknown(")
File without changes
@@ -0,0 +1,46 @@
1
+ """Models of the ``alerting_webhook`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from databay.model_base import OpenStrEnum, RequestModel, ResponseModel
9
+
10
+
11
+ class AlertingWebhookType(OpenStrEnum):
12
+ DISCORD = 'discord'
13
+ TELEGRAM = 'telegram'
14
+
15
+
16
+ class AlertingWebhookCreateScope(OpenStrEnum):
17
+ PROJECT = 'project'
18
+ ORGANIZATION = 'organization'
19
+
20
+
21
+ class AlertingMessage(RequestModel):
22
+ body: str
23
+ title: str
24
+
25
+
26
+ class AlertingWebhookCreate(RequestModel):
27
+ name: str
28
+ scope: AlertingWebhookCreateScope | None = None
29
+ tags: list[str] | None = None
30
+ type: AlertingWebhookType
31
+ url: str
32
+
33
+
34
+ class AlertingWebhookResponse(ResponseModel):
35
+ editable: bool = False
36
+ name: str
37
+ org_wide: bool = True
38
+ project_id: str | None = None
39
+ tags: list[str] | None = None
40
+ type: AlertingWebhookType
41
+ url: str | None = None
42
+ url_masked: str = ''
43
+ uuid: str
44
+
45
+
46
+ AlertingWebhookListResponse = list[AlertingWebhookResponse]
@@ -0,0 +1,11 @@
1
+ """Models of the ``alerting_webhook_tag`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+
9
+
10
+
11
+ AlertingWebhookTagListResponse = list[str]
@@ -0,0 +1,11 @@
1
+ """Models of the ``alerting_webhook_type`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+
9
+
10
+
11
+ AlertingWebhookTypeListResponse = list[str]
@@ -0,0 +1,23 @@
1
+ """Models of the ``alerting_webhook_undelivered`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+
10
+ from databay.model_base import ResponseModel
11
+
12
+
13
+ class UndeliveredAlertResponse(ResponseModel):
14
+ body: str
15
+ failed_at: datetime
16
+ replayed_at: datetime | None = None
17
+ tags: list[str] | None = None
18
+ title: str
19
+ uuid: str
20
+ webhook_uuid: str
21
+
22
+
23
+ AlertingWebhookUndeliveredListResponse = list[UndeliveredAlertResponse]
@@ -0,0 +1,37 @@
1
+ """Models of the ``announcement`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+
10
+ from databay.model_base import OpenStrEnum, ResponseModel
11
+
12
+
13
+ class AnnouncementResponseLocale(OpenStrEnum):
14
+ EN = 'en'
15
+ FA = 'fa'
16
+ FR = 'fr'
17
+ ES = 'es'
18
+ DE = 'de'
19
+
20
+
21
+ class AnnouncementResponseSeverity(OpenStrEnum):
22
+ INFO = 'info'
23
+ WARNING = 'warning'
24
+
25
+
26
+ class AnnouncementResponse(ResponseModel):
27
+ body: str
28
+ is_read: bool
29
+ locale: AnnouncementResponseLocale
30
+ severity: AnnouncementResponseSeverity
31
+ starts_at: datetime
32
+ title: str
33
+ uuid: str
34
+
35
+
36
+ class AnnouncementListResponse(ResponseModel):
37
+ items: list[AnnouncementResponse]
@@ -0,0 +1,81 @@
1
+ """Models of the ``api_key`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+
10
+ from databay.model_base import OpenStrEnum, RequestModel, ResponseModel
11
+
12
+
13
+ class ApiKeyCreateRequestRole(OpenStrEnum):
14
+ READER = 'reader'
15
+ EDITOR = 'editor'
16
+
17
+
18
+ class ApiKeyCreatedRole(OpenStrEnum):
19
+ READER = 'reader'
20
+ EDITOR = 'editor'
21
+
22
+
23
+ class ApiKeyCreatedStatus(OpenStrEnum):
24
+ ACTIVE = 'active'
25
+ REVOKED = 'revoked'
26
+
27
+
28
+ class ApiKeyOutRole(OpenStrEnum):
29
+ READER = 'reader'
30
+ EDITOR = 'editor'
31
+
32
+
33
+ class ApiKeyOutStatus(OpenStrEnum):
34
+ ACTIVE = 'active'
35
+ REVOKED = 'revoked'
36
+
37
+
38
+ class ApiKeyCreateRequest(RequestModel):
39
+ name: str
40
+ project_ids: list[str]
41
+ role: ApiKeyCreateRequestRole
42
+
43
+
44
+ class ApiKeyCreated(ResponseModel):
45
+ client_id: str
46
+ client_secret: str
47
+ created_at: datetime
48
+ created_by: str
49
+ id: str
50
+ name: str
51
+ project_ids: list[str]
52
+ revoked_at: datetime | None
53
+ role: ApiKeyCreatedRole
54
+ rotated_at: datetime | None
55
+ service_account_id: str
56
+ status: ApiKeyCreatedStatus
57
+
58
+
59
+ class ApiKeyOut(ResponseModel):
60
+ client_id: str
61
+ created_at: datetime
62
+ created_by: str
63
+ id: str
64
+ name: str
65
+ project_ids: list[str]
66
+ revoked_at: datetime | None
67
+ role: ApiKeyOutRole
68
+ rotated_at: datetime | None
69
+ service_account_id: str
70
+ status: ApiKeyOutStatus
71
+
72
+
73
+ class ApiKeyList(ResponseModel):
74
+ keys: list[ApiKeyOut]
75
+
76
+
77
+ class ApiKeyRotated(ResponseModel):
78
+ client_id: str
79
+ client_secret: str
80
+ id: str
81
+ rotated_at: datetime
@@ -0,0 +1,29 @@
1
+ """Models of the ``api_key_project`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Literal
9
+
10
+ from databay.model_base import OpenStrEnum, ResponseModel
11
+
12
+
13
+ class ApiKeyBiConnectionOutEndpoint(OpenStrEnum):
14
+ ON = 'on'
15
+ OFF = 'off'
16
+
17
+
18
+ class ApiKeyBiConnectionOut(ResponseModel):
19
+ database: str | None
20
+ endpoint: ApiKeyBiConnectionOutEndpoint
21
+ host: str | None
22
+ port: int | None
23
+ sslmode: Literal['verify-full'] | None
24
+ username: str | None
25
+
26
+
27
+ class ApiKeyProjectOut(ResponseModel):
28
+ key_id: str
29
+ project_id: str
@@ -0,0 +1,11 @@
1
+ """Models of the ``backfill_reason_code`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+
9
+
10
+
11
+ BackfillReasonCodeListResponse = list[str]
@@ -0,0 +1,41 @@
1
+ """Models of the ``backfill_run`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import date
9
+
10
+ from databay.model_base import OpenStrEnum, RequestModel, ResponseModel
11
+
12
+
13
+ class BackfillV2CreateSnapshotType(OpenStrEnum):
14
+ INCREMENTAL = 'incremental'
15
+ BLOCKING = 'blocking'
16
+
17
+
18
+ class BackfillRunCancelResponse(ResponseModel):
19
+ pipe_uuid: str
20
+ state: str
21
+ uuid: str
22
+
23
+
24
+ class BackfillV2Create(RequestModel):
25
+ cursor_field: str | None = None
26
+ pipe_uuid: str
27
+ snapshot_type: BackfillV2CreateSnapshotType = 'incremental'
28
+ start_date: date | None = None
29
+ where_clause: str | None = None
30
+
31
+
32
+ class BackfillV2Response(ResponseModel):
33
+ clamped: bool = False
34
+ cursor_field: str | None = None
35
+ job_type: str = 'sync'
36
+ notices: list[str] | None = None
37
+ pipe_uuid: str
38
+ run_uuid: str | None = None
39
+ start_date: str | None = None
40
+ status: str
41
+ stream_ref: str | None = None
@@ -0,0 +1,68 @@
1
+ """Models of the ``canvas`` noun.
2
+
3
+ Generated by python/tools/gen_client.py from contract/core.json; do not edit.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+ from typing import Any
10
+
11
+ from databay.model_base import OpenStrEnum, RequestModel, ResponseModel
12
+ from databay.models.shared import PipeDestinationUnavailableCode
13
+
14
+
15
+ class ActivationResultStatus(OpenStrEnum):
16
+ CREATED = 'created'
17
+ EXISTING = 'existing'
18
+ FAILED = 'failed'
19
+
20
+
21
+ class ActivationSecret(RequestModel):
22
+ mask_secret: str | None = None
23
+
24
+
25
+ class Activate(RequestModel):
26
+ acknowledge_org_visible: bool = False
27
+ item_ids: list[str] | None = None
28
+ revision: int
29
+ secrets: dict[str, ActivationSecret] | None = None
30
+
31
+
32
+ class ActivationError(ResponseModel):
33
+ code: str
34
+ detail: Any
35
+ status_code: int
36
+
37
+
38
+ class ActivationResult(ResponseModel):
39
+ error: ActivationError | None
40
+ item_id: str
41
+ pipeline_id: str | None
42
+ status: ActivationResultStatus
43
+
44
+
45
+ class ActivationOut(ResponseModel):
46
+ results: list[ActivationResult]
47
+ revision: int
48
+
49
+
50
+ class CanvasSummary(ResponseModel):
51
+ draft_count: int
52
+ pipeline_count: int
53
+ project_id: str
54
+ revision: int
55
+
56
+
57
+ class PipeVolumeOut(ResponseModel):
58
+ pipe_uuid: str
59
+ stored_rows: int | None = None
60
+ unavailable_code: PipeDestinationUnavailableCode | None = None
61
+
62
+
63
+ class ProjectVolumes(ResponseModel):
64
+ measured_at: datetime
65
+ pipes: list[PipeVolumeOut]
66
+
67
+
68
+ CanvasListResponse = list[CanvasSummary]