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.
- cloady/__init__.py +21 -0
- cloady/files.py +234 -0
- cloady/generated/__init__.py +8 -0
- cloady/generated/api/__init__.py +1 -0
- cloady/generated/api/default/__init__.py +1 -0
- cloady/generated/api/default/accept_invitation.py +176 -0
- cloady/generated/api/default/add_ticket_message.py +198 -0
- cloady/generated/api/default/change_billing_plan.py +198 -0
- cloady/generated/api/default/check_password.py +134 -0
- cloady/generated/api/default/check_workspace_slug.py +193 -0
- cloady/generated/api/default/create_app_deploy.py +252 -0
- cloady/generated/api/default/create_domain.py +252 -0
- cloady/generated/api/default/create_s3_session.py +210 -0
- cloady/generated/api/default/create_ssh_session.py +252 -0
- cloady/generated/api/default/create_ticket.py +182 -0
- cloady/generated/api/default/create_token.py +182 -0
- cloady/generated/api/default/create_var.py +252 -0
- cloady/generated/api/default/create_workspace.py +182 -0
- cloady/generated/api/default/create_workspace_invitation.py +198 -0
- cloady/generated/api/default/decline_invitation.py +172 -0
- cloady/generated/api/default/delete_app.py +227 -0
- cloady/generated/api/default/delete_domain.py +241 -0
- cloady/generated/api/default/delete_var.py +241 -0
- cloady/generated/api/default/delete_workspace.py +172 -0
- cloady/generated/api/default/deploy_app.py +198 -0
- cloady/generated/api/default/disconnect_github_integration.py +172 -0
- cloady/generated/api/default/end_s3_session.py +172 -0
- cloady/generated/api/default/exec_command.py +252 -0
- cloady/generated/api/default/get_account_billing.py +134 -0
- cloady/generated/api/default/get_app_services.py +231 -0
- cloady/generated/api/default/get_db_credentials.py +258 -0
- cloady/generated/api/default/get_db_schema.py +261 -0
- cloady/generated/api/default/get_git_diff.py +290 -0
- cloady/generated/api/default/get_git_overview.py +275 -0
- cloady/generated/api/default/get_invitation.py +176 -0
- cloady/generated/api/default/get_me.py +134 -0
- cloady/generated/api/default/get_notification_preferences.py +134 -0
- cloady/generated/api/default/get_pricing.py +149 -0
- cloady/generated/api/default/get_ticket.py +176 -0
- cloady/generated/api/default/get_user_billing_state.py +134 -0
- cloady/generated/api/default/get_volume_keys.py +258 -0
- cloady/generated/api/default/get_workspace.py +176 -0
- cloady/generated/api/default/get_workspace_billing.py +176 -0
- cloady/generated/api/default/import_vars.py +252 -0
- cloady/generated/api/default/inspect_source.py +198 -0
- cloady/generated/api/default/link_github_integration.py +198 -0
- cloady/generated/api/default/list_app_domains.py +231 -0
- cloady/generated/api/default/list_backups.py +231 -0
- cloady/generated/api/default/list_branches.py +243 -0
- cloady/generated/api/default/list_cert_statuses.py +231 -0
- cloady/generated/api/default/list_deploys.py +231 -0
- cloady/generated/api/default/list_github_repos.py +227 -0
- cloady/generated/api/default/list_regions.py +134 -0
- cloady/generated/api/default/list_templates.py +134 -0
- cloady/generated/api/default/list_tickets.py +134 -0
- cloady/generated/api/default/list_tiers.py +134 -0
- cloady/generated/api/default/list_tokens.py +134 -0
- cloady/generated/api/default/list_vars.py +231 -0
- cloady/generated/api/default/list_volumes.py +231 -0
- cloady/generated/api/default/list_workspace_domains.py +176 -0
- cloady/generated/api/default/list_workspace_integrations.py +176 -0
- cloady/generated/api/default/list_workspace_invitations.py +176 -0
- cloady/generated/api/default/list_workspace_members.py +176 -0
- cloady/generated/api/default/list_workspace_volumes.py +217 -0
- cloady/generated/api/default/list_workspaces.py +134 -0
- cloady/generated/api/default/read_service_logs.py +265 -0
- cloady/generated/api/default/redeploy_app.py +231 -0
- cloady/generated/api/default/restore_backup.py +249 -0
- cloady/generated/api/default/reveal_var.py +245 -0
- cloady/generated/api/default/revoke_token.py +172 -0
- cloady/generated/api/default/revoke_workspace_invitation.py +186 -0
- cloady/generated/api/default/rollback_deploy.py +257 -0
- cloady/generated/api/default/run_db_query.py +264 -0
- cloady/generated/api/default/run_git_action.py +374 -0
- cloady/generated/api/default/set_mfa.py +190 -0
- cloady/generated/api/default/start_card_setup.py +155 -0
- cloady/generated/api/default/start_phone_verification.py +178 -0
- cloady/generated/api/default/stream_active_deploy.py +237 -0
- cloady/generated/api/default/stream_app_metrics.py +247 -0
- cloady/generated/api/default/stream_app_status.py +243 -0
- cloady/generated/api/default/stream_deploy_logs.py +259 -0
- cloady/generated/api/default/stream_ticket_messages.py +188 -0
- cloady/generated/api/default/transfer_app.py +252 -0
- cloady/generated/api/default/transfer_workspace_ownership.py +198 -0
- cloady/generated/api/default/update_app.py +252 -0
- cloady/generated/api/default/update_backups.py +252 -0
- cloady/generated/api/default/update_domain.py +266 -0
- cloady/generated/api/default/update_notification_preferences.py +182 -0
- cloady/generated/api/default/update_password.py +182 -0
- cloady/generated/api/default/update_ticket.py +194 -0
- cloady/generated/api/default/update_var.py +266 -0
- cloady/generated/api/default/update_workspace.py +198 -0
- cloady/generated/api/default/verify_phone.py +182 -0
- cloady/generated/client.py +268 -0
- cloady/generated/errors.py +16 -0
- cloady/generated/models/__init__.py +515 -0
- cloady/generated/models/accept_invitation_response_200.py +42 -0
- cloady/generated/models/account_billing.py +93 -0
- cloady/generated/models/add_ticket_message_body.py +61 -0
- cloady/generated/models/add_ticket_message_response_200.py +48 -0
- cloady/generated/models/app.py +200 -0
- cloady/generated/models/app_env_schema.py +58 -0
- cloady/generated/models/app_env_schema_additional_property.py +106 -0
- cloady/generated/models/app_env_schema_additional_property_type.py +10 -0
- cloady/generated/models/app_limits.py +58 -0
- cloady/generated/models/app_template.py +86 -0
- cloady/generated/models/app_template_configs.py +60 -0
- cloady/generated/models/app_template_configs_additional_property.py +51 -0
- cloady/generated/models/app_template_services.py +60 -0
- cloady/generated/models/app_template_services_additional_property.py +465 -0
- cloady/generated/models/app_template_services_additional_property_build_type_1.py +51 -0
- cloady/generated/models/app_template_services_additional_property_configs_item_type_1.py +50 -0
- cloady/generated/models/app_template_services_additional_property_depends_on_type_1.py +68 -0
- cloady/generated/models/app_template_services_additional_property_depends_on_type_1_additional_property.py +42 -0
- cloady/generated/models/app_template_services_additional_property_deploy.py +68 -0
- cloady/generated/models/app_template_services_additional_property_deploy_resources.py +81 -0
- cloady/generated/models/app_template_services_additional_property_deploy_resources_limits.py +70 -0
- cloady/generated/models/app_template_services_additional_property_deploy_resources_reservations.py +70 -0
- cloady/generated/models/app_template_services_additional_property_environment_type_0.py +58 -0
- cloady/generated/models/app_template_services_additional_property_healthcheck.py +108 -0
- cloady/generated/models/app_template_services_additional_property_ports_item_type_2.py +95 -0
- cloady/generated/models/app_template_services_additional_property_ports_item_type_2_protocol.py +9 -0
- cloady/generated/models/app_template_services_additional_property_volumes_item_type_1.py +72 -0
- cloady/generated/models/app_template_volumes.py +82 -0
- cloady/generated/models/app_template_volumes_additional_property_type_1.py +59 -0
- cloady/generated/models/app_template_volumes_additional_property_type_1_driver_opts.py +42 -0
- cloady/generated/models/backup.py +111 -0
- cloady/generated/models/backup_kind.py +10 -0
- cloady/generated/models/backup_state.py +142 -0
- cloady/generated/models/backup_state_window.py +14 -0
- cloady/generated/models/backup_status.py +10 -0
- cloady/generated/models/branch_tracking.py +73 -0
- cloady/generated/models/branch_tracking_mode.py +10 -0
- cloady/generated/models/branch_tracking_output.py +54 -0
- cloady/generated/models/branch_tracking_output_mode.py +10 -0
- cloady/generated/models/branches.py +56 -0
- cloady/generated/models/cert_status.py +62 -0
- cloady/generated/models/cert_status_status.py +11 -0
- cloady/generated/models/change_billing_plan_body.py +61 -0
- cloady/generated/models/change_billing_plan_response_200.py +98 -0
- cloady/generated/models/check_password_response_200.py +42 -0
- cloady/generated/models/component_service.py +116 -0
- cloady/generated/models/component_service_state.py +11 -0
- cloady/generated/models/create_app_deploy_response_201.py +48 -0
- cloady/generated/models/create_deploy_body.py +79 -0
- cloady/generated/models/create_domain_body.py +112 -0
- cloady/generated/models/create_domain_response_201.py +48 -0
- cloady/generated/models/create_invitation_body.py +80 -0
- cloady/generated/models/create_invitation_body_role.py +11 -0
- cloady/generated/models/create_ticket_body.py +80 -0
- cloady/generated/models/create_ticket_response_201.py +48 -0
- cloady/generated/models/create_token_body.py +97 -0
- cloady/generated/models/create_token_body_expires.py +11 -0
- cloady/generated/models/create_token_response_201.py +56 -0
- cloady/generated/models/create_var_body.py +80 -0
- cloady/generated/models/create_var_response_201.py +48 -0
- cloady/generated/models/create_workspace_body.py +118 -0
- cloady/generated/models/create_workspace_invitation_response_201.py +56 -0
- cloady/generated/models/create_workspace_response_201.py +48 -0
- cloady/generated/models/db_credentials.py +80 -0
- cloady/generated/models/db_engine.py +12 -0
- cloady/generated/models/db_query_body.py +80 -0
- cloady/generated/models/db_query_result.py +119 -0
- cloady/generated/models/db_schema.py +88 -0
- cloady/generated/models/db_table.py +89 -0
- cloady/generated/models/db_table_cols_item.py +61 -0
- cloady/generated/models/db_table_kind.py +10 -0
- cloady/generated/models/deploy.py +138 -0
- cloady/generated/models/deploy_app_body.py +211 -0
- cloady/generated/models/deploy_app_body_values.py +45 -0
- cloady/generated/models/deploy_app_response_201.py +48 -0
- cloady/generated/models/deploy_status.py +11 -0
- cloady/generated/models/domain.py +94 -0
- cloady/generated/models/env_id.py +10 -0
- cloady/generated/models/error_response.py +77 -0
- cloady/generated/models/error_response_error_type_1.py +50 -0
- cloady/generated/models/exec_body.py +98 -0
- cloady/generated/models/exec_result.py +72 -0
- cloady/generated/models/get_account_billing_response_200.py +48 -0
- cloady/generated/models/get_app_services_response_200.py +56 -0
- cloady/generated/models/get_invitation_response_200.py +48 -0
- cloady/generated/models/get_me_response_200.py +48 -0
- cloady/generated/models/get_notification_preferences_response_200.py +48 -0
- cloady/generated/models/get_pricing_response_200.py +56 -0
- cloady/generated/models/get_ticket_response_200.py +48 -0
- cloady/generated/models/get_user_billing_state_response_200.py +50 -0
- cloady/generated/models/get_workspace_billing_response_200.py +48 -0
- cloady/generated/models/get_workspace_response_200.py +48 -0
- cloady/generated/models/git_action_body_type_0.py +71 -0
- cloady/generated/models/git_action_body_type_1.py +84 -0
- cloady/generated/models/git_action_body_type_10.py +63 -0
- cloady/generated/models/git_action_body_type_2.py +82 -0
- cloady/generated/models/git_action_body_type_3.py +71 -0
- cloady/generated/models/git_action_body_type_4.py +71 -0
- cloady/generated/models/git_action_body_type_5.py +71 -0
- cloady/generated/models/git_action_body_type_6.py +71 -0
- cloady/generated/models/git_action_body_type_7.py +71 -0
- cloady/generated/models/git_action_body_type_8.py +71 -0
- cloady/generated/models/git_action_body_type_9.py +63 -0
- cloady/generated/models/git_change.py +74 -0
- cloady/generated/models/git_commit.py +66 -0
- cloady/generated/models/git_file.py +66 -0
- cloady/generated/models/git_overview_type_0.py +130 -0
- cloady/generated/models/git_overview_type_1.py +52 -0
- cloady/generated/models/git_overview_type_1_reason.py +12 -0
- cloady/generated/models/git_patch_type_0.py +109 -0
- cloady/generated/models/git_patch_type_1.py +52 -0
- cloady/generated/models/git_patch_type_1_reason.py +12 -0
- cloady/generated/models/github_integration.py +116 -0
- cloady/generated/models/github_repo.py +88 -0
- cloady/generated/models/import_vars_body.py +75 -0
- cloady/generated/models/import_vars_body_entries_item.py +80 -0
- cloady/generated/models/import_vars_result.py +72 -0
- cloady/generated/models/import_vars_result_errors_item.py +50 -0
- cloady/generated/models/inspect_source_body.py +123 -0
- cloady/generated/models/inspection.py +171 -0
- cloady/generated/models/instance_metrics.py +129 -0
- cloady/generated/models/instance_metrics_components.py +62 -0
- cloady/generated/models/instance_metrics_components_additional_property.py +50 -0
- cloady/generated/models/instance_metrics_cpu.py +42 -0
- cloady/generated/models/instance_metrics_disk.py +50 -0
- cloady/generated/models/instance_metrics_memory.py +42 -0
- cloady/generated/models/instance_metrics_network_type_0.py +50 -0
- cloady/generated/models/invitation.py +115 -0
- cloady/generated/models/invitation_actor.py +63 -0
- cloady/generated/models/invitation_detail.py +111 -0
- cloady/generated/models/invitation_detail_role.py +11 -0
- cloady/generated/models/invitation_detail_workspace.py +82 -0
- cloady/generated/models/invoice.py +102 -0
- cloady/generated/models/link_github_body.py +61 -0
- cloady/generated/models/link_github_integration_response_201.py +48 -0
- cloady/generated/models/list_app_domains_response_200.py +56 -0
- cloady/generated/models/list_cert_statuses_response_200.py +56 -0
- cloady/generated/models/list_deploys_response_200.py +56 -0
- cloady/generated/models/list_github_repos_response_200.py +64 -0
- cloady/generated/models/list_regions_response_200.py +56 -0
- cloady/generated/models/list_templates_response_200.py +56 -0
- cloady/generated/models/list_tickets_response_200.py +56 -0
- cloady/generated/models/list_tiers_response_200.py +56 -0
- cloady/generated/models/list_tokens_response_200.py +56 -0
- cloady/generated/models/list_vars_response_200.py +56 -0
- cloady/generated/models/list_volumes_response_200.py +56 -0
- cloady/generated/models/list_workspace_domains_response_200.py +56 -0
- cloady/generated/models/list_workspace_integrations_response_200.py +70 -0
- cloady/generated/models/list_workspace_integrations_response_200_providers.py +52 -0
- cloady/generated/models/list_workspace_integrations_response_200_providers_github.py +42 -0
- cloady/generated/models/list_workspace_invitations_response_200.py +56 -0
- cloady/generated/models/list_workspace_members_response_200.py +66 -0
- cloady/generated/models/list_workspace_members_response_200_viewer_role.py +11 -0
- cloady/generated/models/list_workspace_volumes_response_200.py +56 -0
- cloady/generated/models/list_workspaces_response_200.py +92 -0
- cloady/generated/models/member.py +102 -0
- cloady/generated/models/member_role.py +11 -0
- cloady/generated/models/mfa_body.py +61 -0
- cloady/generated/models/notification_prefs.py +74 -0
- cloady/generated/models/ok.py +42 -0
- cloady/generated/models/payment_method.py +78 -0
- cloady/generated/models/pricing.py +72 -0
- cloady/generated/models/redeploy_app_response_200.py +48 -0
- cloady/generated/models/region.py +140 -0
- cloady/generated/models/reveal_var_response_200.py +42 -0
- cloady/generated/models/rollback_deploy_response_200.py +48 -0
- cloady/generated/models/s3_session.py +92 -0
- cloady/generated/models/s3_session_body.py +69 -0
- cloady/generated/models/s3_session_body_access.py +9 -0
- cloady/generated/models/s3_session_scope.py +9 -0
- cloady/generated/models/scale_body.py +93 -0
- cloady/generated/models/scale_body_output.py +74 -0
- cloady/generated/models/scale_body_output_volumes.py +45 -0
- cloady/generated/models/scale_body_volumes.py +45 -0
- cloady/generated/models/service_endpoint.py +87 -0
- cloady/generated/models/service_endpoint_protocol.py +10 -0
- cloady/generated/models/service_logs.py +60 -0
- cloady/generated/models/service_logs_state.py +9 -0
- cloady/generated/models/service_status.py +14 -0
- cloady/generated/models/set_mfa_response_200.py +50 -0
- cloady/generated/models/slug_check.py +50 -0
- cloady/generated/models/source_output_type_0.py +103 -0
- cloady/generated/models/source_output_type_1.py +72 -0
- cloady/generated/models/source_output_type_2.py +52 -0
- cloady/generated/models/source_type_0.py +122 -0
- cloady/generated/models/source_type_1.py +91 -0
- cloady/generated/models/source_type_2.py +71 -0
- cloady/generated/models/ssh_session.py +74 -0
- cloady/generated/models/ssh_session_body.py +61 -0
- cloady/generated/models/start_card_setup_response_200.py +50 -0
- cloady/generated/models/start_phone_body.py +61 -0
- cloady/generated/models/status_frame.py +131 -0
- cloady/generated/models/status_frame_deploying.py +50 -0
- cloady/generated/models/status_frame_services_item.py +52 -0
- cloady/generated/models/sub_service.py +97 -0
- cloady/generated/models/sub_service_limits.py +50 -0
- cloady/generated/models/sub_service_role.py +14 -0
- cloady/generated/models/template.py +80 -0
- cloady/generated/models/ticket.py +106 -0
- cloady/generated/models/ticket_message.py +90 -0
- cloady/generated/models/ticket_message_role.py +10 -0
- cloady/generated/models/ticket_status.py +9 -0
- cloady/generated/models/ticket_summary.py +117 -0
- cloady/generated/models/ticket_summary_last_message_type_0.py +52 -0
- cloady/generated/models/ticket_summary_last_message_type_0_role.py +10 -0
- cloady/generated/models/ticket_summary_status.py +9 -0
- cloady/generated/models/ticket_thread.py +66 -0
- cloady/generated/models/ticket_thread_status.py +9 -0
- cloady/generated/models/tier.py +106 -0
- cloady/generated/models/token_scope.py +10 -0
- cloady/generated/models/token_view.py +118 -0
- cloady/generated/models/transfer_app_body.py +61 -0
- cloady/generated/models/transfer_app_response_200.py +56 -0
- cloady/generated/models/transfer_workspace_body.py +61 -0
- cloady/generated/models/transfer_workspace_ownership_response_200.py +88 -0
- cloady/generated/models/update_app_body.py +145 -0
- cloady/generated/models/update_app_body_status.py +11 -0
- cloady/generated/models/update_app_response_200.py +48 -0
- cloady/generated/models/update_backups_body.py +78 -0
- cloady/generated/models/update_backups_body_window.py +14 -0
- cloady/generated/models/update_domain_body.py +73 -0
- cloady/generated/models/update_domain_response_200.py +48 -0
- cloady/generated/models/update_notification_preferences_response_200.py +48 -0
- cloady/generated/models/update_notification_prefs_body.py +97 -0
- cloady/generated/models/update_password_body.py +72 -0
- cloady/generated/models/update_password_response_200.py +50 -0
- cloady/generated/models/update_ticket_body.py +78 -0
- cloady/generated/models/update_ticket_body_status.py +9 -0
- cloady/generated/models/update_var_body.py +70 -0
- cloady/generated/models/update_var_response_200.py +48 -0
- cloady/generated/models/update_workspace_body.py +136 -0
- cloady/generated/models/update_workspace_response_200.py +48 -0
- cloady/generated/models/user.py +84 -0
- cloady/generated/models/var.py +82 -0
- cloady/generated/models/verify_phone_body.py +69 -0
- cloady/generated/models/verify_phone_response_200.py +50 -0
- cloady/generated/models/volume_info.py +90 -0
- cloady/generated/models/volume_keys.py +58 -0
- cloady/generated/models/volume_keys_s3.py +82 -0
- cloady/generated/models/volume_keys_sftp.py +66 -0
- cloady/generated/models/workspace.py +150 -0
- cloady/generated/models/workspace_billing.py +129 -0
- cloady/generated/models/workspace_volume.py +124 -0
- cloady/generated/types.py +54 -0
- cloady/py.typed +0 -0
- cloady-0.5.0.dist-info/METADATA +89 -0
- cloady-0.5.0.dist-info/RECORD +344 -0
- 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 @@
|
|
|
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
|