cloady 0.5.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 (344) hide show
  1. cloady/__init__.py +21 -0
  2. cloady/files.py +234 -0
  3. cloady/generated/__init__.py +8 -0
  4. cloady/generated/api/__init__.py +1 -0
  5. cloady/generated/api/default/__init__.py +1 -0
  6. cloady/generated/api/default/accept_invitation.py +176 -0
  7. cloady/generated/api/default/add_ticket_message.py +198 -0
  8. cloady/generated/api/default/change_billing_plan.py +198 -0
  9. cloady/generated/api/default/check_password.py +134 -0
  10. cloady/generated/api/default/check_workspace_slug.py +193 -0
  11. cloady/generated/api/default/create_app_deploy.py +252 -0
  12. cloady/generated/api/default/create_domain.py +252 -0
  13. cloady/generated/api/default/create_s3_session.py +210 -0
  14. cloady/generated/api/default/create_ssh_session.py +252 -0
  15. cloady/generated/api/default/create_ticket.py +182 -0
  16. cloady/generated/api/default/create_token.py +182 -0
  17. cloady/generated/api/default/create_var.py +252 -0
  18. cloady/generated/api/default/create_workspace.py +182 -0
  19. cloady/generated/api/default/create_workspace_invitation.py +198 -0
  20. cloady/generated/api/default/decline_invitation.py +172 -0
  21. cloady/generated/api/default/delete_app.py +227 -0
  22. cloady/generated/api/default/delete_domain.py +241 -0
  23. cloady/generated/api/default/delete_var.py +241 -0
  24. cloady/generated/api/default/delete_workspace.py +172 -0
  25. cloady/generated/api/default/deploy_app.py +198 -0
  26. cloady/generated/api/default/disconnect_github_integration.py +172 -0
  27. cloady/generated/api/default/end_s3_session.py +172 -0
  28. cloady/generated/api/default/exec_command.py +252 -0
  29. cloady/generated/api/default/get_account_billing.py +134 -0
  30. cloady/generated/api/default/get_app_services.py +231 -0
  31. cloady/generated/api/default/get_db_credentials.py +258 -0
  32. cloady/generated/api/default/get_db_schema.py +261 -0
  33. cloady/generated/api/default/get_git_diff.py +290 -0
  34. cloady/generated/api/default/get_git_overview.py +275 -0
  35. cloady/generated/api/default/get_invitation.py +176 -0
  36. cloady/generated/api/default/get_me.py +134 -0
  37. cloady/generated/api/default/get_notification_preferences.py +134 -0
  38. cloady/generated/api/default/get_pricing.py +149 -0
  39. cloady/generated/api/default/get_ticket.py +176 -0
  40. cloady/generated/api/default/get_user_billing_state.py +134 -0
  41. cloady/generated/api/default/get_volume_keys.py +258 -0
  42. cloady/generated/api/default/get_workspace.py +176 -0
  43. cloady/generated/api/default/get_workspace_billing.py +176 -0
  44. cloady/generated/api/default/import_vars.py +252 -0
  45. cloady/generated/api/default/inspect_source.py +198 -0
  46. cloady/generated/api/default/link_github_integration.py +198 -0
  47. cloady/generated/api/default/list_app_domains.py +231 -0
  48. cloady/generated/api/default/list_backups.py +231 -0
  49. cloady/generated/api/default/list_branches.py +243 -0
  50. cloady/generated/api/default/list_cert_statuses.py +231 -0
  51. cloady/generated/api/default/list_deploys.py +231 -0
  52. cloady/generated/api/default/list_github_repos.py +227 -0
  53. cloady/generated/api/default/list_regions.py +134 -0
  54. cloady/generated/api/default/list_templates.py +134 -0
  55. cloady/generated/api/default/list_tickets.py +134 -0
  56. cloady/generated/api/default/list_tiers.py +134 -0
  57. cloady/generated/api/default/list_tokens.py +134 -0
  58. cloady/generated/api/default/list_vars.py +231 -0
  59. cloady/generated/api/default/list_volumes.py +231 -0
  60. cloady/generated/api/default/list_workspace_domains.py +176 -0
  61. cloady/generated/api/default/list_workspace_integrations.py +176 -0
  62. cloady/generated/api/default/list_workspace_invitations.py +176 -0
  63. cloady/generated/api/default/list_workspace_members.py +176 -0
  64. cloady/generated/api/default/list_workspace_volumes.py +217 -0
  65. cloady/generated/api/default/list_workspaces.py +134 -0
  66. cloady/generated/api/default/read_service_logs.py +265 -0
  67. cloady/generated/api/default/redeploy_app.py +231 -0
  68. cloady/generated/api/default/restore_backup.py +249 -0
  69. cloady/generated/api/default/reveal_var.py +245 -0
  70. cloady/generated/api/default/revoke_token.py +172 -0
  71. cloady/generated/api/default/revoke_workspace_invitation.py +186 -0
  72. cloady/generated/api/default/rollback_deploy.py +257 -0
  73. cloady/generated/api/default/run_db_query.py +264 -0
  74. cloady/generated/api/default/run_git_action.py +374 -0
  75. cloady/generated/api/default/set_mfa.py +190 -0
  76. cloady/generated/api/default/start_card_setup.py +155 -0
  77. cloady/generated/api/default/start_phone_verification.py +178 -0
  78. cloady/generated/api/default/stream_active_deploy.py +237 -0
  79. cloady/generated/api/default/stream_app_metrics.py +247 -0
  80. cloady/generated/api/default/stream_app_status.py +243 -0
  81. cloady/generated/api/default/stream_deploy_logs.py +259 -0
  82. cloady/generated/api/default/stream_ticket_messages.py +188 -0
  83. cloady/generated/api/default/transfer_app.py +252 -0
  84. cloady/generated/api/default/transfer_workspace_ownership.py +198 -0
  85. cloady/generated/api/default/update_app.py +252 -0
  86. cloady/generated/api/default/update_backups.py +252 -0
  87. cloady/generated/api/default/update_domain.py +266 -0
  88. cloady/generated/api/default/update_notification_preferences.py +182 -0
  89. cloady/generated/api/default/update_password.py +182 -0
  90. cloady/generated/api/default/update_ticket.py +194 -0
  91. cloady/generated/api/default/update_var.py +266 -0
  92. cloady/generated/api/default/update_workspace.py +198 -0
  93. cloady/generated/api/default/verify_phone.py +182 -0
  94. cloady/generated/client.py +268 -0
  95. cloady/generated/errors.py +16 -0
  96. cloady/generated/models/__init__.py +515 -0
  97. cloady/generated/models/accept_invitation_response_200.py +42 -0
  98. cloady/generated/models/account_billing.py +93 -0
  99. cloady/generated/models/add_ticket_message_body.py +61 -0
  100. cloady/generated/models/add_ticket_message_response_200.py +48 -0
  101. cloady/generated/models/app.py +200 -0
  102. cloady/generated/models/app_env_schema.py +58 -0
  103. cloady/generated/models/app_env_schema_additional_property.py +106 -0
  104. cloady/generated/models/app_env_schema_additional_property_type.py +10 -0
  105. cloady/generated/models/app_limits.py +58 -0
  106. cloady/generated/models/app_template.py +86 -0
  107. cloady/generated/models/app_template_configs.py +60 -0
  108. cloady/generated/models/app_template_configs_additional_property.py +51 -0
  109. cloady/generated/models/app_template_services.py +60 -0
  110. cloady/generated/models/app_template_services_additional_property.py +465 -0
  111. cloady/generated/models/app_template_services_additional_property_build_type_1.py +51 -0
  112. cloady/generated/models/app_template_services_additional_property_configs_item_type_1.py +50 -0
  113. cloady/generated/models/app_template_services_additional_property_depends_on_type_1.py +68 -0
  114. cloady/generated/models/app_template_services_additional_property_depends_on_type_1_additional_property.py +42 -0
  115. cloady/generated/models/app_template_services_additional_property_deploy.py +68 -0
  116. cloady/generated/models/app_template_services_additional_property_deploy_resources.py +81 -0
  117. cloady/generated/models/app_template_services_additional_property_deploy_resources_limits.py +70 -0
  118. cloady/generated/models/app_template_services_additional_property_deploy_resources_reservations.py +70 -0
  119. cloady/generated/models/app_template_services_additional_property_environment_type_0.py +58 -0
  120. cloady/generated/models/app_template_services_additional_property_healthcheck.py +108 -0
  121. cloady/generated/models/app_template_services_additional_property_ports_item_type_2.py +95 -0
  122. cloady/generated/models/app_template_services_additional_property_ports_item_type_2_protocol.py +9 -0
  123. cloady/generated/models/app_template_services_additional_property_volumes_item_type_1.py +72 -0
  124. cloady/generated/models/app_template_volumes.py +82 -0
  125. cloady/generated/models/app_template_volumes_additional_property_type_1.py +59 -0
  126. cloady/generated/models/app_template_volumes_additional_property_type_1_driver_opts.py +42 -0
  127. cloady/generated/models/backup.py +111 -0
  128. cloady/generated/models/backup_kind.py +10 -0
  129. cloady/generated/models/backup_state.py +142 -0
  130. cloady/generated/models/backup_state_window.py +14 -0
  131. cloady/generated/models/backup_status.py +10 -0
  132. cloady/generated/models/branch_tracking.py +73 -0
  133. cloady/generated/models/branch_tracking_mode.py +10 -0
  134. cloady/generated/models/branch_tracking_output.py +54 -0
  135. cloady/generated/models/branch_tracking_output_mode.py +10 -0
  136. cloady/generated/models/branches.py +56 -0
  137. cloady/generated/models/cert_status.py +62 -0
  138. cloady/generated/models/cert_status_status.py +11 -0
  139. cloady/generated/models/change_billing_plan_body.py +61 -0
  140. cloady/generated/models/change_billing_plan_response_200.py +98 -0
  141. cloady/generated/models/check_password_response_200.py +42 -0
  142. cloady/generated/models/component_service.py +116 -0
  143. cloady/generated/models/component_service_state.py +11 -0
  144. cloady/generated/models/create_app_deploy_response_201.py +48 -0
  145. cloady/generated/models/create_deploy_body.py +79 -0
  146. cloady/generated/models/create_domain_body.py +112 -0
  147. cloady/generated/models/create_domain_response_201.py +48 -0
  148. cloady/generated/models/create_invitation_body.py +80 -0
  149. cloady/generated/models/create_invitation_body_role.py +11 -0
  150. cloady/generated/models/create_ticket_body.py +80 -0
  151. cloady/generated/models/create_ticket_response_201.py +48 -0
  152. cloady/generated/models/create_token_body.py +97 -0
  153. cloady/generated/models/create_token_body_expires.py +11 -0
  154. cloady/generated/models/create_token_response_201.py +56 -0
  155. cloady/generated/models/create_var_body.py +80 -0
  156. cloady/generated/models/create_var_response_201.py +48 -0
  157. cloady/generated/models/create_workspace_body.py +118 -0
  158. cloady/generated/models/create_workspace_invitation_response_201.py +56 -0
  159. cloady/generated/models/create_workspace_response_201.py +48 -0
  160. cloady/generated/models/db_credentials.py +80 -0
  161. cloady/generated/models/db_engine.py +12 -0
  162. cloady/generated/models/db_query_body.py +80 -0
  163. cloady/generated/models/db_query_result.py +119 -0
  164. cloady/generated/models/db_schema.py +88 -0
  165. cloady/generated/models/db_table.py +89 -0
  166. cloady/generated/models/db_table_cols_item.py +61 -0
  167. cloady/generated/models/db_table_kind.py +10 -0
  168. cloady/generated/models/deploy.py +138 -0
  169. cloady/generated/models/deploy_app_body.py +211 -0
  170. cloady/generated/models/deploy_app_body_values.py +45 -0
  171. cloady/generated/models/deploy_app_response_201.py +48 -0
  172. cloady/generated/models/deploy_status.py +11 -0
  173. cloady/generated/models/domain.py +94 -0
  174. cloady/generated/models/env_id.py +10 -0
  175. cloady/generated/models/error_response.py +77 -0
  176. cloady/generated/models/error_response_error_type_1.py +50 -0
  177. cloady/generated/models/exec_body.py +98 -0
  178. cloady/generated/models/exec_result.py +72 -0
  179. cloady/generated/models/get_account_billing_response_200.py +48 -0
  180. cloady/generated/models/get_app_services_response_200.py +56 -0
  181. cloady/generated/models/get_invitation_response_200.py +48 -0
  182. cloady/generated/models/get_me_response_200.py +48 -0
  183. cloady/generated/models/get_notification_preferences_response_200.py +48 -0
  184. cloady/generated/models/get_pricing_response_200.py +56 -0
  185. cloady/generated/models/get_ticket_response_200.py +48 -0
  186. cloady/generated/models/get_user_billing_state_response_200.py +50 -0
  187. cloady/generated/models/get_workspace_billing_response_200.py +48 -0
  188. cloady/generated/models/get_workspace_response_200.py +48 -0
  189. cloady/generated/models/git_action_body_type_0.py +71 -0
  190. cloady/generated/models/git_action_body_type_1.py +84 -0
  191. cloady/generated/models/git_action_body_type_10.py +63 -0
  192. cloady/generated/models/git_action_body_type_2.py +82 -0
  193. cloady/generated/models/git_action_body_type_3.py +71 -0
  194. cloady/generated/models/git_action_body_type_4.py +71 -0
  195. cloady/generated/models/git_action_body_type_5.py +71 -0
  196. cloady/generated/models/git_action_body_type_6.py +71 -0
  197. cloady/generated/models/git_action_body_type_7.py +71 -0
  198. cloady/generated/models/git_action_body_type_8.py +71 -0
  199. cloady/generated/models/git_action_body_type_9.py +63 -0
  200. cloady/generated/models/git_change.py +74 -0
  201. cloady/generated/models/git_commit.py +66 -0
  202. cloady/generated/models/git_file.py +66 -0
  203. cloady/generated/models/git_overview_type_0.py +130 -0
  204. cloady/generated/models/git_overview_type_1.py +52 -0
  205. cloady/generated/models/git_overview_type_1_reason.py +12 -0
  206. cloady/generated/models/git_patch_type_0.py +109 -0
  207. cloady/generated/models/git_patch_type_1.py +52 -0
  208. cloady/generated/models/git_patch_type_1_reason.py +12 -0
  209. cloady/generated/models/github_integration.py +116 -0
  210. cloady/generated/models/github_repo.py +88 -0
  211. cloady/generated/models/import_vars_body.py +75 -0
  212. cloady/generated/models/import_vars_body_entries_item.py +80 -0
  213. cloady/generated/models/import_vars_result.py +72 -0
  214. cloady/generated/models/import_vars_result_errors_item.py +50 -0
  215. cloady/generated/models/inspect_source_body.py +123 -0
  216. cloady/generated/models/inspection.py +171 -0
  217. cloady/generated/models/instance_metrics.py +129 -0
  218. cloady/generated/models/instance_metrics_components.py +62 -0
  219. cloady/generated/models/instance_metrics_components_additional_property.py +50 -0
  220. cloady/generated/models/instance_metrics_cpu.py +42 -0
  221. cloady/generated/models/instance_metrics_disk.py +50 -0
  222. cloady/generated/models/instance_metrics_memory.py +42 -0
  223. cloady/generated/models/instance_metrics_network_type_0.py +50 -0
  224. cloady/generated/models/invitation.py +115 -0
  225. cloady/generated/models/invitation_actor.py +63 -0
  226. cloady/generated/models/invitation_detail.py +111 -0
  227. cloady/generated/models/invitation_detail_role.py +11 -0
  228. cloady/generated/models/invitation_detail_workspace.py +82 -0
  229. cloady/generated/models/invoice.py +102 -0
  230. cloady/generated/models/link_github_body.py +61 -0
  231. cloady/generated/models/link_github_integration_response_201.py +48 -0
  232. cloady/generated/models/list_app_domains_response_200.py +56 -0
  233. cloady/generated/models/list_cert_statuses_response_200.py +56 -0
  234. cloady/generated/models/list_deploys_response_200.py +56 -0
  235. cloady/generated/models/list_github_repos_response_200.py +64 -0
  236. cloady/generated/models/list_regions_response_200.py +56 -0
  237. cloady/generated/models/list_templates_response_200.py +56 -0
  238. cloady/generated/models/list_tickets_response_200.py +56 -0
  239. cloady/generated/models/list_tiers_response_200.py +56 -0
  240. cloady/generated/models/list_tokens_response_200.py +56 -0
  241. cloady/generated/models/list_vars_response_200.py +56 -0
  242. cloady/generated/models/list_volumes_response_200.py +56 -0
  243. cloady/generated/models/list_workspace_domains_response_200.py +56 -0
  244. cloady/generated/models/list_workspace_integrations_response_200.py +70 -0
  245. cloady/generated/models/list_workspace_integrations_response_200_providers.py +52 -0
  246. cloady/generated/models/list_workspace_integrations_response_200_providers_github.py +42 -0
  247. cloady/generated/models/list_workspace_invitations_response_200.py +56 -0
  248. cloady/generated/models/list_workspace_members_response_200.py +66 -0
  249. cloady/generated/models/list_workspace_members_response_200_viewer_role.py +11 -0
  250. cloady/generated/models/list_workspace_volumes_response_200.py +56 -0
  251. cloady/generated/models/list_workspaces_response_200.py +92 -0
  252. cloady/generated/models/member.py +102 -0
  253. cloady/generated/models/member_role.py +11 -0
  254. cloady/generated/models/mfa_body.py +61 -0
  255. cloady/generated/models/notification_prefs.py +74 -0
  256. cloady/generated/models/ok.py +42 -0
  257. cloady/generated/models/payment_method.py +78 -0
  258. cloady/generated/models/pricing.py +72 -0
  259. cloady/generated/models/redeploy_app_response_200.py +48 -0
  260. cloady/generated/models/region.py +140 -0
  261. cloady/generated/models/reveal_var_response_200.py +42 -0
  262. cloady/generated/models/rollback_deploy_response_200.py +48 -0
  263. cloady/generated/models/s3_session.py +92 -0
  264. cloady/generated/models/s3_session_body.py +69 -0
  265. cloady/generated/models/s3_session_body_access.py +9 -0
  266. cloady/generated/models/s3_session_scope.py +9 -0
  267. cloady/generated/models/scale_body.py +93 -0
  268. cloady/generated/models/scale_body_output.py +74 -0
  269. cloady/generated/models/scale_body_output_volumes.py +45 -0
  270. cloady/generated/models/scale_body_volumes.py +45 -0
  271. cloady/generated/models/service_endpoint.py +87 -0
  272. cloady/generated/models/service_endpoint_protocol.py +10 -0
  273. cloady/generated/models/service_logs.py +60 -0
  274. cloady/generated/models/service_logs_state.py +9 -0
  275. cloady/generated/models/service_status.py +14 -0
  276. cloady/generated/models/set_mfa_response_200.py +50 -0
  277. cloady/generated/models/slug_check.py +50 -0
  278. cloady/generated/models/source_output_type_0.py +103 -0
  279. cloady/generated/models/source_output_type_1.py +72 -0
  280. cloady/generated/models/source_output_type_2.py +52 -0
  281. cloady/generated/models/source_type_0.py +122 -0
  282. cloady/generated/models/source_type_1.py +91 -0
  283. cloady/generated/models/source_type_2.py +71 -0
  284. cloady/generated/models/ssh_session.py +74 -0
  285. cloady/generated/models/ssh_session_body.py +61 -0
  286. cloady/generated/models/start_card_setup_response_200.py +50 -0
  287. cloady/generated/models/start_phone_body.py +61 -0
  288. cloady/generated/models/status_frame.py +131 -0
  289. cloady/generated/models/status_frame_deploying.py +50 -0
  290. cloady/generated/models/status_frame_services_item.py +52 -0
  291. cloady/generated/models/sub_service.py +97 -0
  292. cloady/generated/models/sub_service_limits.py +50 -0
  293. cloady/generated/models/sub_service_role.py +14 -0
  294. cloady/generated/models/template.py +80 -0
  295. cloady/generated/models/ticket.py +106 -0
  296. cloady/generated/models/ticket_message.py +90 -0
  297. cloady/generated/models/ticket_message_role.py +10 -0
  298. cloady/generated/models/ticket_status.py +9 -0
  299. cloady/generated/models/ticket_summary.py +117 -0
  300. cloady/generated/models/ticket_summary_last_message_type_0.py +52 -0
  301. cloady/generated/models/ticket_summary_last_message_type_0_role.py +10 -0
  302. cloady/generated/models/ticket_summary_status.py +9 -0
  303. cloady/generated/models/ticket_thread.py +66 -0
  304. cloady/generated/models/ticket_thread_status.py +9 -0
  305. cloady/generated/models/tier.py +106 -0
  306. cloady/generated/models/token_scope.py +10 -0
  307. cloady/generated/models/token_view.py +118 -0
  308. cloady/generated/models/transfer_app_body.py +61 -0
  309. cloady/generated/models/transfer_app_response_200.py +56 -0
  310. cloady/generated/models/transfer_workspace_body.py +61 -0
  311. cloady/generated/models/transfer_workspace_ownership_response_200.py +88 -0
  312. cloady/generated/models/update_app_body.py +145 -0
  313. cloady/generated/models/update_app_body_status.py +11 -0
  314. cloady/generated/models/update_app_response_200.py +48 -0
  315. cloady/generated/models/update_backups_body.py +78 -0
  316. cloady/generated/models/update_backups_body_window.py +14 -0
  317. cloady/generated/models/update_domain_body.py +73 -0
  318. cloady/generated/models/update_domain_response_200.py +48 -0
  319. cloady/generated/models/update_notification_preferences_response_200.py +48 -0
  320. cloady/generated/models/update_notification_prefs_body.py +97 -0
  321. cloady/generated/models/update_password_body.py +72 -0
  322. cloady/generated/models/update_password_response_200.py +50 -0
  323. cloady/generated/models/update_ticket_body.py +78 -0
  324. cloady/generated/models/update_ticket_body_status.py +9 -0
  325. cloady/generated/models/update_var_body.py +70 -0
  326. cloady/generated/models/update_var_response_200.py +48 -0
  327. cloady/generated/models/update_workspace_body.py +136 -0
  328. cloady/generated/models/update_workspace_response_200.py +48 -0
  329. cloady/generated/models/user.py +84 -0
  330. cloady/generated/models/var.py +82 -0
  331. cloady/generated/models/verify_phone_body.py +69 -0
  332. cloady/generated/models/verify_phone_response_200.py +50 -0
  333. cloady/generated/models/volume_info.py +90 -0
  334. cloady/generated/models/volume_keys.py +58 -0
  335. cloady/generated/models/volume_keys_s3.py +82 -0
  336. cloady/generated/models/volume_keys_sftp.py +66 -0
  337. cloady/generated/models/workspace.py +150 -0
  338. cloady/generated/models/workspace_billing.py +129 -0
  339. cloady/generated/models/workspace_volume.py +124 -0
  340. cloady/generated/types.py +54 -0
  341. cloady/py.typed +0 -0
  342. cloady-0.5.0.dist-info/METADATA +89 -0
  343. cloady-0.5.0.dist-info/RECORD +344 -0
  344. cloady-0.5.0.dist-info/WHEEL +4 -0
cloady/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from .generated.client import AuthenticatedClient, Client
4
+
5
+ if TYPE_CHECKING:
6
+ from .files import CloadyFiles
7
+
8
+
9
+ class Cloady(AuthenticatedClient):
10
+ @property
11
+ def files(self) -> "CloadyFiles":
12
+ cached = self.__dict__.get("_files")
13
+ if cached is None:
14
+ from .files import CloadyFiles
15
+
16
+ cached = CloadyFiles(self._base_url, self.token)
17
+ self.__dict__["_files"] = cached
18
+ return cached
19
+
20
+
21
+ __all__ = ("AuthenticatedClient", "Client", "Cloady")
cloady/files.py ADDED
@@ -0,0 +1,234 @@
1
+ """S3-backed volume file operations — the Python mirror of the JS SDK's
2
+ ``cloady.files.*``.
3
+
4
+ The Cloady ``/s3`` endpoint is an S3-compatible face over each app volume, so
5
+ this module is a thin wrapper over :mod:`boto3` (which handles SigV4 signing,
6
+ XML, multipart, and pagination). It authenticates with short-lived credentials
7
+ exchanged from ``POST /api/workspaces/{slug}/s3/session`` using your API token,
8
+ refreshing them automatically before expiry.
9
+
10
+ from cloady import Cloady
11
+ from cloady.files import VolumeRef
12
+
13
+ cloady = Cloady(base_url="https://cloady.com", token="cldy_…")
14
+ ref = VolumeRef(slug="neptolab", region="hil1", env="prod",
15
+ svc_id="dailyfrog", volume="dailyfrog-data")
16
+
17
+ cloady.files.list(ref, "") # -> [FileEntry(...), ...]
18
+ cloady.files.upload(ref, "logs", "out.txt", b"hi")
19
+ cloady.files.download(ref, "logs/out.txt", "./out.txt")
20
+ cloady.files.rename(ref, "", "old", "new", is_dir=True) # atomic server-side mv
21
+ cloady.files.remove(ref, "", [("cache", True)]) # recursive rm -rf
22
+
23
+ Requires the ``boto3`` extra: ``pip install 'cloady[s3]'`` (or add boto3).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from collections.abc import Iterable
29
+ from dataclasses import dataclass
30
+ from datetime import UTC, datetime
31
+
32
+ import httpx
33
+
34
+ try: # boto3 is an optional extra; only needed for file operations.
35
+ import boto3
36
+ from botocore.config import Config
37
+ from botocore.credentials import RefreshableCredentials
38
+ from botocore.session import get_session
39
+ except ImportError as exc: # pragma: no cover
40
+ raise ImportError("cloady.files requires boto3 — install it with: pip install 'cloady[s3]'") from exc
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class VolumeRef:
45
+ """Addresses one volume of one app instance (an S3 'bucket')."""
46
+
47
+ slug: str
48
+ region: str
49
+ env: str
50
+ svc_id: str
51
+ volume: str
52
+
53
+ def bucket(self) -> str:
54
+ return ".".join([self.slug, self.region, self.env, self.svc_id, self.volume])
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class FileEntry:
59
+ name: str
60
+ type: str # "dir" | "file"
61
+ size: int
62
+ modified_ms: int
63
+
64
+
65
+ def _join(path: str, name: str) -> str:
66
+ return f"{path}/{name}" if path else name
67
+
68
+
69
+ def _parse_expiry(iso: str) -> datetime:
70
+ # Server sends e.g. "2026-07-14T20:06:55.987Z"; make it tz-aware.
71
+ try:
72
+ return datetime.fromisoformat(iso.replace("Z", "+00:00"))
73
+ except ValueError: # pragma: no cover — fall back to a short lifetime
74
+ return datetime.now(UTC)
75
+
76
+
77
+ class CloadyFiles:
78
+ """File operations over a Cloady volume via its S3 endpoint.
79
+
80
+ One instance per API token; boto3 clients (with auto-refreshing session
81
+ credentials) are cached per (workspace slug, access). Read operations
82
+ exchange a read-scoped session, write operations a write-scoped one —
83
+ same as the JS SDK.
84
+ """
85
+
86
+ def __init__(self, base_url: str, token: str):
87
+ self._base_url = base_url.rstrip("/")
88
+ self._token = token
89
+ self._clients: dict[tuple[str, str], boto3.client] = {}
90
+ self._http = httpx.Client(timeout=30.0)
91
+
92
+ # -- credential exchange ------------------------------------------------
93
+
94
+ def _mint(self, slug: str, access: str) -> dict:
95
+ res = self._http.post(
96
+ f"{self._base_url}/api/workspaces/{slug}/s3/session",
97
+ headers={"authorization": f"Bearer {self._token}"},
98
+ json={"access": access},
99
+ )
100
+ if res.status_code >= 400:
101
+ detail = ""
102
+ try:
103
+ detail = res.json().get("error", {}).get("message", "")
104
+ except Exception: # noqa: BLE001
105
+ pass
106
+ raise RuntimeError(f"Could not obtain file access ({res.status_code}): {detail}")
107
+ return res.json()
108
+
109
+ def _client(self, slug: str, access: str = "read"):
110
+ cached = self._clients.get((slug, access))
111
+ if cached is not None:
112
+ return cached
113
+
114
+ first = self._mint(slug, access)
115
+ endpoint = first["endpoint"]
116
+
117
+ def refresh() -> dict:
118
+ data = self._mint(slug, access)
119
+ return {
120
+ "access_key": data["accessKeyId"],
121
+ "secret_key": data["secretAccessKey"],
122
+ "token": None,
123
+ "expiry_time": _parse_expiry(data["expiresAt"]).isoformat(),
124
+ }
125
+
126
+ creds = RefreshableCredentials.create_from_metadata(
127
+ metadata={
128
+ "access_key": first["accessKeyId"],
129
+ "secret_key": first["secretAccessKey"],
130
+ "token": None,
131
+ "expiry_time": _parse_expiry(first["expiresAt"]).isoformat(),
132
+ },
133
+ refresh_using=refresh,
134
+ method="cloady-session",
135
+ )
136
+ creds._advisory_refresh_timeout = 300 # noqa: SLF001 — botocore defaults (900s) exceed the 15-min session TTL, forcing a re-mint on every request
137
+ creds._mandatory_refresh_timeout = 60 # noqa: SLF001
138
+ botocore_session = get_session()
139
+ botocore_session._credentials = creds # noqa: SLF001 — documented botocore pattern
140
+ client = boto3.Session(botocore_session=botocore_session).client(
141
+ "s3",
142
+ endpoint_url=endpoint,
143
+ region_name="auto",
144
+ config=Config(s3={"addressing_style": "path"}, signature_version="s3v4"),
145
+ )
146
+ self._clients[(slug, access)] = client
147
+ return client
148
+
149
+ def clear_sessions(self) -> None:
150
+ """Drop cached credentials/clients (e.g. after rotating the token)."""
151
+ self._clients.clear()
152
+
153
+ # -- operations ---------------------------------------------------------
154
+
155
+ def list(self, ref: VolumeRef, path: str) -> list[FileEntry]:
156
+ """One-level listing: folders (as dirs) and files."""
157
+ prefix = (path.rstrip("/") + "/") if path else ""
158
+ client = self._client(ref.slug)
159
+ entries: list[FileEntry] = []
160
+ for page in client.get_paginator("list_objects_v2").paginate(Bucket=ref.bucket(), Prefix=prefix, Delimiter="/"):
161
+ for cp in page.get("CommonPrefixes", []):
162
+ name = cp["Prefix"].rstrip("/").split("/")[-1]
163
+ if name:
164
+ entries.append(FileEntry(name=name, type="dir", size=0, modified_ms=0))
165
+ for obj in page.get("Contents", []):
166
+ key = obj["Key"]
167
+ if not key or key.endswith("/"):
168
+ continue
169
+ name = key.split("/")[-1]
170
+ lm = obj.get("LastModified")
171
+ entries.append(
172
+ FileEntry(
173
+ name=name,
174
+ type="file",
175
+ size=int(obj.get("Size", 0)),
176
+ modified_ms=int(lm.timestamp() * 1000) if lm else 0,
177
+ )
178
+ )
179
+ return entries
180
+
181
+ def read(self, ref: VolumeRef, key: str) -> bytes:
182
+ """Read a whole object's bytes (ranged internally if it exceeds the cap)."""
183
+ client = self._client(ref.slug)
184
+ head = client.head_object(Bucket=ref.bucket(), Key=key)
185
+ size = int(head.get("ContentLength", 0))
186
+ cap = 50 * 1024 * 1024
187
+ if size <= cap:
188
+ return client.get_object(Bucket=ref.bucket(), Key=key)["Body"].read()
189
+ chunks: list[bytes] = []
190
+ for off in range(0, size, cap):
191
+ rng = f"bytes={off}-{min(off + cap, size) - 1}"
192
+ chunks.append(client.get_object(Bucket=ref.bucket(), Key=key, Range=rng)["Body"].read())
193
+ return b"".join(chunks)
194
+
195
+ def read_text(self, ref: VolumeRef, key: str, encoding: str = "utf-8") -> str:
196
+ return self.read(ref, key).decode(encoding)
197
+
198
+ def upload(self, ref: VolumeRef, path: str, name: str, data: bytes) -> None:
199
+ """Upload a file (multipart for large blobs, handled by boto3)."""
200
+ import io
201
+
202
+ self._client(ref.slug, "write").upload_fileobj(io.BytesIO(data), ref.bucket(), _join(path, name))
203
+
204
+ def download(self, ref: VolumeRef, key: str, dest_path: str) -> None:
205
+ with open(dest_path, "wb") as fh:
206
+ fh.write(self.read(ref, key))
207
+
208
+ def mkdir(self, ref: VolumeRef, path: str, name: str) -> None:
209
+ self._client(ref.slug, "write").put_object(Bucket=ref.bucket(), Key=_join(path, name) + "/", Body=b"")
210
+
211
+ def remove(self, ref: VolumeRef, path: str, items: Iterable[tuple[str, bool]]) -> None:
212
+ """Delete files/folders. ``items`` is (name, is_dir); folders are
213
+ removed recursively server-side via a trailing-slash key."""
214
+ client = self._client(ref.slug, "write")
215
+ for name, is_dir in items:
216
+ key = _join(path, name) + ("/" if is_dir else "")
217
+ client.delete_object(Bucket=ref.bucket(), Key=key)
218
+
219
+ def rename(self, ref: VolumeRef, path: str, frm: str, to: str, is_dir: bool) -> None:
220
+ """Atomic rename/move (files and folders) via the ``x-cloady-move``
221
+ server extension — one call, no client-side copy+delete."""
222
+ client = self._client(ref.slug, "write")
223
+ bucket = ref.bucket()
224
+ suffix = "/" if is_dir else ""
225
+ src = f"{bucket}/{_join(path, frm)}{suffix}"
226
+
227
+ def _add_move_header(request, **_kwargs):
228
+ request.headers["x-cloady-move"] = "true"
229
+
230
+ client.meta.events.register("before-send.s3.CopyObject", _add_move_header)
231
+ try:
232
+ client.copy_object(Bucket=bucket, Key=_join(path, to) + suffix, CopySource=src)
233
+ finally:
234
+ client.meta.events.unregister("before-send.s3.CopyObject", _add_move_header)
@@ -0,0 +1,8 @@
1
+ """A client library for accessing Cloady API"""
2
+
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """Contains methods for accessing the API"""
@@ -0,0 +1 @@
1
+ """Contains endpoint functions for accessing the API"""
@@ -0,0 +1,176 @@
1
+ from http import HTTPStatus
2
+ from typing import Any
3
+ from urllib.parse import quote
4
+
5
+ import httpx
6
+
7
+ from ... import errors
8
+ from ...client import AuthenticatedClient, Client
9
+ from ...models.accept_invitation_response_200 import AcceptInvitationResponse200
10
+ from ...models.error_response import ErrorResponse
11
+ from ...types import Response
12
+
13
+
14
+ def _get_kwargs(
15
+ token: str,
16
+ ) -> dict[str, Any]:
17
+
18
+ _kwargs: dict[str, Any] = {
19
+ "method": "post",
20
+ "url": "/api/invitations/{token}/accept".format(
21
+ token=quote(str(token), safe=""),
22
+ ),
23
+ }
24
+
25
+ return _kwargs
26
+
27
+
28
+ def _parse_response(
29
+ *, client: AuthenticatedClient | Client, response: httpx.Response
30
+ ) -> AcceptInvitationResponse200 | ErrorResponse | None:
31
+ if response.status_code == 200:
32
+ response_200 = AcceptInvitationResponse200.from_dict(response.json())
33
+
34
+ return response_200
35
+
36
+ if response.status_code == 400:
37
+ response_400 = ErrorResponse.from_dict(response.json())
38
+
39
+ return response_400
40
+
41
+ if response.status_code == 401:
42
+ response_401 = ErrorResponse.from_dict(response.json())
43
+
44
+ return response_401
45
+
46
+ if response.status_code == 403:
47
+ response_403 = ErrorResponse.from_dict(response.json())
48
+
49
+ return response_403
50
+
51
+ if response.status_code == 404:
52
+ response_404 = ErrorResponse.from_dict(response.json())
53
+
54
+ return response_404
55
+
56
+ if client.raise_on_unexpected_status:
57
+ raise errors.UnexpectedStatus(response.status_code, response.content)
58
+ else:
59
+ return None
60
+
61
+
62
+ def _build_response(
63
+ *, client: AuthenticatedClient | Client, response: httpx.Response
64
+ ) -> Response[AcceptInvitationResponse200 | ErrorResponse]:
65
+ return Response(
66
+ status_code=HTTPStatus(response.status_code),
67
+ content=response.content,
68
+ headers=response.headers,
69
+ parsed=_parse_response(client=client, response=response),
70
+ )
71
+
72
+
73
+ def sync_detailed(
74
+ token: str,
75
+ *,
76
+ client: AuthenticatedClient | Client,
77
+ ) -> Response[AcceptInvitationResponse200 | ErrorResponse]:
78
+ """Accept a workspace invitation
79
+
80
+ Args:
81
+ token (str):
82
+
83
+ Raises:
84
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
85
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
86
+
87
+ Returns:
88
+ Response[AcceptInvitationResponse200 | ErrorResponse]
89
+ """
90
+
91
+ kwargs = _get_kwargs(
92
+ token=token,
93
+ )
94
+
95
+ response = client.get_httpx_client().request(
96
+ **kwargs,
97
+ )
98
+
99
+ return _build_response(client=client, response=response)
100
+
101
+
102
+ def sync(
103
+ token: str,
104
+ *,
105
+ client: AuthenticatedClient | Client,
106
+ ) -> AcceptInvitationResponse200 | ErrorResponse | None:
107
+ """Accept a workspace invitation
108
+
109
+ Args:
110
+ token (str):
111
+
112
+ Raises:
113
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
114
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
115
+
116
+ Returns:
117
+ AcceptInvitationResponse200 | ErrorResponse
118
+ """
119
+
120
+ return sync_detailed(
121
+ token=token,
122
+ client=client,
123
+ ).parsed
124
+
125
+
126
+ async def asyncio_detailed(
127
+ token: str,
128
+ *,
129
+ client: AuthenticatedClient | Client,
130
+ ) -> Response[AcceptInvitationResponse200 | ErrorResponse]:
131
+ """Accept a workspace invitation
132
+
133
+ Args:
134
+ token (str):
135
+
136
+ Raises:
137
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
138
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
139
+
140
+ Returns:
141
+ Response[AcceptInvitationResponse200 | ErrorResponse]
142
+ """
143
+
144
+ kwargs = _get_kwargs(
145
+ token=token,
146
+ )
147
+
148
+ response = await client.get_async_httpx_client().request(**kwargs)
149
+
150
+ return _build_response(client=client, response=response)
151
+
152
+
153
+ async def asyncio(
154
+ token: str,
155
+ *,
156
+ client: AuthenticatedClient | Client,
157
+ ) -> AcceptInvitationResponse200 | ErrorResponse | None:
158
+ """Accept a workspace invitation
159
+
160
+ Args:
161
+ token (str):
162
+
163
+ Raises:
164
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
165
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
166
+
167
+ Returns:
168
+ AcceptInvitationResponse200 | ErrorResponse
169
+ """
170
+
171
+ return (
172
+ await asyncio_detailed(
173
+ token=token,
174
+ client=client,
175
+ )
176
+ ).parsed
@@ -0,0 +1,198 @@
1
+ from http import HTTPStatus
2
+ from typing import Any
3
+ from urllib.parse import quote
4
+
5
+ import httpx
6
+
7
+ from ... import errors
8
+ from ...client import AuthenticatedClient, Client
9
+ from ...models.add_ticket_message_body import AddTicketMessageBody
10
+ from ...models.add_ticket_message_response_200 import AddTicketMessageResponse200
11
+ from ...models.error_response import ErrorResponse
12
+ from ...types import UNSET, Response, Unset
13
+
14
+
15
+ def _get_kwargs(
16
+ id: str,
17
+ *,
18
+ body: AddTicketMessageBody | Unset = UNSET,
19
+ ) -> dict[str, Any]:
20
+ headers: dict[str, Any] = {}
21
+
22
+ _kwargs: dict[str, Any] = {
23
+ "method": "post",
24
+ "url": "/api/me/tickets/{id}/messages".format(
25
+ id=quote(str(id), safe=""),
26
+ ),
27
+ }
28
+
29
+ if not isinstance(body, Unset):
30
+ _kwargs["json"] = body.to_dict()
31
+
32
+ headers["Content-Type"] = "application/json"
33
+
34
+ _kwargs["headers"] = headers
35
+ return _kwargs
36
+
37
+
38
+ def _parse_response(
39
+ *, client: AuthenticatedClient | Client, response: httpx.Response
40
+ ) -> AddTicketMessageResponse200 | ErrorResponse | None:
41
+ if response.status_code == 200:
42
+ response_200 = AddTicketMessageResponse200.from_dict(response.json())
43
+
44
+ return response_200
45
+
46
+ if response.status_code == 400:
47
+ response_400 = ErrorResponse.from_dict(response.json())
48
+
49
+ return response_400
50
+
51
+ if response.status_code == 401:
52
+ response_401 = ErrorResponse.from_dict(response.json())
53
+
54
+ return response_401
55
+
56
+ if response.status_code == 403:
57
+ response_403 = ErrorResponse.from_dict(response.json())
58
+
59
+ return response_403
60
+
61
+ if response.status_code == 404:
62
+ response_404 = ErrorResponse.from_dict(response.json())
63
+
64
+ return response_404
65
+
66
+ if client.raise_on_unexpected_status:
67
+ raise errors.UnexpectedStatus(response.status_code, response.content)
68
+ else:
69
+ return None
70
+
71
+
72
+ def _build_response(
73
+ *, client: AuthenticatedClient | Client, response: httpx.Response
74
+ ) -> Response[AddTicketMessageResponse200 | ErrorResponse]:
75
+ return Response(
76
+ status_code=HTTPStatus(response.status_code),
77
+ content=response.content,
78
+ headers=response.headers,
79
+ parsed=_parse_response(client=client, response=response),
80
+ )
81
+
82
+
83
+ def sync_detailed(
84
+ id: str,
85
+ *,
86
+ client: AuthenticatedClient | Client,
87
+ body: AddTicketMessageBody | Unset = UNSET,
88
+ ) -> Response[AddTicketMessageResponse200 | ErrorResponse]:
89
+ """Add a message to a support ticket
90
+
91
+ Args:
92
+ id (str):
93
+ body (AddTicketMessageBody | Unset):
94
+
95
+ Raises:
96
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
97
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
98
+
99
+ Returns:
100
+ Response[AddTicketMessageResponse200 | ErrorResponse]
101
+ """
102
+
103
+ kwargs = _get_kwargs(
104
+ id=id,
105
+ body=body,
106
+ )
107
+
108
+ response = client.get_httpx_client().request(
109
+ **kwargs,
110
+ )
111
+
112
+ return _build_response(client=client, response=response)
113
+
114
+
115
+ def sync(
116
+ id: str,
117
+ *,
118
+ client: AuthenticatedClient | Client,
119
+ body: AddTicketMessageBody | Unset = UNSET,
120
+ ) -> AddTicketMessageResponse200 | ErrorResponse | None:
121
+ """Add a message to a support ticket
122
+
123
+ Args:
124
+ id (str):
125
+ body (AddTicketMessageBody | Unset):
126
+
127
+ Raises:
128
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
129
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
130
+
131
+ Returns:
132
+ AddTicketMessageResponse200 | ErrorResponse
133
+ """
134
+
135
+ return sync_detailed(
136
+ id=id,
137
+ client=client,
138
+ body=body,
139
+ ).parsed
140
+
141
+
142
+ async def asyncio_detailed(
143
+ id: str,
144
+ *,
145
+ client: AuthenticatedClient | Client,
146
+ body: AddTicketMessageBody | Unset = UNSET,
147
+ ) -> Response[AddTicketMessageResponse200 | ErrorResponse]:
148
+ """Add a message to a support ticket
149
+
150
+ Args:
151
+ id (str):
152
+ body (AddTicketMessageBody | Unset):
153
+
154
+ Raises:
155
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
156
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
157
+
158
+ Returns:
159
+ Response[AddTicketMessageResponse200 | ErrorResponse]
160
+ """
161
+
162
+ kwargs = _get_kwargs(
163
+ id=id,
164
+ body=body,
165
+ )
166
+
167
+ response = await client.get_async_httpx_client().request(**kwargs)
168
+
169
+ return _build_response(client=client, response=response)
170
+
171
+
172
+ async def asyncio(
173
+ id: str,
174
+ *,
175
+ client: AuthenticatedClient | Client,
176
+ body: AddTicketMessageBody | Unset = UNSET,
177
+ ) -> AddTicketMessageResponse200 | ErrorResponse | None:
178
+ """Add a message to a support ticket
179
+
180
+ Args:
181
+ id (str):
182
+ body (AddTicketMessageBody | Unset):
183
+
184
+ Raises:
185
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
186
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
187
+
188
+ Returns:
189
+ AddTicketMessageResponse200 | ErrorResponse
190
+ """
191
+
192
+ return (
193
+ await asyncio_detailed(
194
+ id=id,
195
+ client=client,
196
+ body=body,
197
+ )
198
+ ).parsed