render 1.0.1__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.
- render/__init__.py +98 -0
- render/client/__init__.py +119 -0
- render/client/client.py +110 -0
- render/client/errors.py +39 -0
- render/client/sse.py +90 -0
- render/client/sse_sync.py +20 -0
- render/client/tests/__init__.py +0 -0
- render/client/tests/test_client.py +379 -0
- render/client/tests/test_method_parity.py +118 -0
- render/client/tests/test_render.py +52 -0
- render/client/tests/test_sse.py +67 -0
- render/client/tests/test_util.py +303 -0
- render/client/tests/test_util_sync.py +73 -0
- render/client/tests/test_workflows_sync.py +313 -0
- render/client/types.py +52 -0
- render/client/util.py +285 -0
- render/client/util_sync.py +83 -0
- render/client/workflows.py +363 -0
- render/client/workflows_sync.py +298 -0
- render/experimental/__init__.py +37 -0
- render/experimental/experimental.py +93 -0
- render/experimental/experimental_sync.py +78 -0
- render/experimental/key_value/__init__.py +33 -0
- render/experimental/key_value/api.py +243 -0
- render/experimental/key_value/compare.py +83 -0
- render/experimental/key_value/provider.py +285 -0
- render/experimental/key_value/tests/__init__.py +0 -0
- render/experimental/key_value/tests/test_api.py +371 -0
- render/experimental/key_value/tests/test_compare.py +136 -0
- render/experimental/key_value/tests/test_provider.py +1016 -0
- render/experimental/key_value/types.py +130 -0
- render/experimental/key_value/utils.py +11 -0
- render/experimental/object/__init__.py +38 -0
- render/experimental/object/api.py +250 -0
- render/experimental/object/api_sync.py +250 -0
- render/experimental/object/client.py +545 -0
- render/experimental/object/client_sync.py +547 -0
- render/experimental/object/e2e_helpers.py +78 -0
- render/experimental/object/tests/__init__.py +0 -0
- render/experimental/object/tests/test_client.py +228 -0
- render/experimental/object/tests/test_e2e.py +125 -0
- render/experimental/object/types.py +87 -0
- render/experimental/sandbox/__init__.py +31 -0
- render/experimental/sandbox/_tar.py +278 -0
- render/experimental/sandbox/api.py +518 -0
- render/experimental/sandbox/api_sync.py +514 -0
- render/experimental/sandbox/client.py +168 -0
- render/experimental/sandbox/client_sync.py +170 -0
- render/experimental/sandbox/errors.py +37 -0
- render/experimental/sandbox/files.py +378 -0
- render/experimental/sandbox/tests/__init__.py +0 -0
- render/experimental/sandbox/tests/test_client.py +802 -0
- render/experimental/sandbox/tests/test_download.py +728 -0
- render/experimental/sandbox/tests/test_e2e.py +252 -0
- render/experimental/sandbox/tests/test_tar.py +153 -0
- render/experimental/sandbox/tests/test_types.py +57 -0
- render/experimental/sandbox/types.py +46 -0
- render/public_api/__init__.py +8 -0
- render/public_api/api/__init__.py +1 -0
- render/public_api/api/artifact_sources/__init__.py +1 -0
- render/public_api/api/artifact_sources/add_or_update_artifact_source_secret_file.py +252 -0
- render/public_api/api/artifact_sources/create_artifact_source.py +243 -0
- render/public_api/api/artifact_sources/delete_artifact_source.py +188 -0
- render/public_api/api/artifact_sources/delete_artifact_source_env_var.py +223 -0
- render/public_api/api/artifact_sources/delete_artifact_source_secret_file.py +223 -0
- render/public_api/api/artifact_sources/get_artifact_source.py +185 -0
- render/public_api/api/artifact_sources/get_env_vars_for_artifact_source.py +241 -0
- render/public_api/api/artifact_sources/link_artifact_source_to_env_group.py +215 -0
- render/public_api/api/artifact_sources/list_artifact_sources.py +338 -0
- render/public_api/api/artifact_sources/list_artifacts_in_artifact_source.py +241 -0
- render/public_api/api/artifact_sources/list_secret_files_for_artifact_source.py +241 -0
- render/public_api/api/artifact_sources/retrieve_artifact_source_env_var.py +225 -0
- render/public_api/api/artifact_sources/retrieve_artifact_source_secret_file.py +225 -0
- render/public_api/api/artifact_sources/unlink_artifact_source_from_env_group.py +209 -0
- render/public_api/api/artifact_sources/update_artifact_source.py +244 -0
- render/public_api/api/artifact_sources/update_artifact_source_env_var.py +257 -0
- render/public_api/api/artifact_sources/update_env_vars_for_artifact_source.py +245 -0
- render/public_api/api/artifact_sources/update_secret_files_for_artifact_source.py +255 -0
- render/public_api/api/audit_logs/__init__.py +1 -0
- render/public_api/api/audit_logs/list_organization_audit_logs.py +303 -0
- render/public_api/api/audit_logs/list_owner_audit_logs.py +303 -0
- render/public_api/api/blueprints/__init__.py +1 -0
- render/public_api/api/blueprints/disconnect_blueprint.py +210 -0
- render/public_api/api/blueprints/list_blueprint_syncs.py +241 -0
- render/public_api/api/blueprints/list_blueprints.py +251 -0
- render/public_api/api/blueprints/retrieve_blueprint.py +200 -0
- render/public_api/api/blueprints/update_blueprint.py +222 -0
- render/public_api/api/blueprints/validate_blueprint.py +217 -0
- render/public_api/api/custom_domains/__init__.py +1 -0
- render/public_api/api/custom_domains/create_custom_domain.py +242 -0
- render/public_api/api/custom_domains/delete_custom_domain.py +216 -0
- render/public_api/api/custom_domains/list_custom_domains.py +346 -0
- render/public_api/api/custom_domains/refresh_custom_domain.py +216 -0
- render/public_api/api/custom_domains/retrieve_custom_domain.py +218 -0
- render/public_api/api/dedicated_i_ps/__init__.py +1 -0
- render/public_api/api/dedicated_i_ps/create_dedicated_ip.py +234 -0
- render/public_api/api/dedicated_i_ps/delete_dedicated_ip.py +198 -0
- render/public_api/api/dedicated_i_ps/list_dedicated_ips.py +223 -0
- render/public_api/api/dedicated_i_ps/retrieve_dedicated_ip.py +200 -0
- render/public_api/api/dedicated_i_ps/update_dedicated_ip.py +244 -0
- render/public_api/api/deploys/__init__.py +1 -0
- render/public_api/api/deploys/cancel_deploy.py +216 -0
- render/public_api/api/deploys/create_deploy.py +231 -0
- render/public_api/api/deploys/list_deploys.py +377 -0
- render/public_api/api/deploys/retrieve_deploy.py +213 -0
- render/public_api/api/deploys/rollback_deploy.py +241 -0
- render/public_api/api/disks/__init__.py +1 -0
- render/public_api/api/disks/add_disk.py +222 -0
- render/public_api/api/disks/delete_disk.py +210 -0
- render/public_api/api/disks/list_disks.py +381 -0
- render/public_api/api/disks/list_snapshots.py +214 -0
- render/public_api/api/disks/restore_snapshot.py +255 -0
- render/public_api/api/disks/retrieve_disk.py +200 -0
- render/public_api/api/disks/update_disk.py +243 -0
- render/public_api/api/environment_groups/__init__.py +1 -0
- render/public_api/api/environment_groups/create_env_group.py +199 -0
- render/public_api/api/environment_groups/delete_env_group.py +192 -0
- render/public_api/api/environment_groups/delete_env_group_env_var.py +201 -0
- render/public_api/api/environment_groups/delete_env_group_secret_file.py +201 -0
- render/public_api/api/environment_groups/link_service_to_env_group.py +211 -0
- render/public_api/api/environment_groups/list_env_groups.py +352 -0
- render/public_api/api/environment_groups/retrieve_env_group.py +190 -0
- render/public_api/api/environment_groups/retrieve_env_group_env_var.py +203 -0
- render/public_api/api/environment_groups/retrieve_env_group_secret_file.py +203 -0
- render/public_api/api/environment_groups/unlink_service_from_env_group.py +209 -0
- render/public_api/api/environment_groups/update_env_group.py +212 -0
- render/public_api/api/environment_groups/update_env_group_env_var.py +230 -0
- render/public_api/api/environment_groups/update_env_group_secret_file.py +225 -0
- render/public_api/api/events/__init__.py +1 -0
- render/public_api/api/events/retrieve_event.py +205 -0
- render/public_api/api/key_value/__init__.py +1 -0
- render/public_api/api/key_value/create_key_value.py +199 -0
- render/public_api/api/key_value/delete_key_value.py +188 -0
- render/public_api/api/key_value/list_key_value.py +380 -0
- render/public_api/api/key_value/resume_key_value.py +203 -0
- render/public_api/api/key_value/retrieve_key_value.py +190 -0
- render/public_api/api/key_value/retrieve_key_value_connection_info.py +194 -0
- render/public_api/api/key_value/suspend_key_value.py +203 -0
- render/public_api/api/key_value/update_key_value.py +221 -0
- render/public_api/api/logs/__init__.py +1 -0
- render/public_api/api/logs/delete_owner_log_stream.py +188 -0
- render/public_api/api/logs/delete_resource_log_stream.py +192 -0
- render/public_api/api/logs/get_owner_log_stream.py +205 -0
- render/public_api/api/logs/get_resource_log_stream.py +209 -0
- render/public_api/api/logs/list_logs.py +535 -0
- render/public_api/api/logs/list_logs_values.py +527 -0
- render/public_api/api/logs/list_resource_log_streams.py +317 -0
- render/public_api/api/logs/subscribe_logs.py +527 -0
- render/public_api/api/logs/update_owner_log_stream.py +231 -0
- render/public_api/api/logs/update_resource_log_stream.py +231 -0
- render/public_api/api/maintenance/__init__.py +1 -0
- render/public_api/api/maintenance/list_maintenance.py +249 -0
- render/public_api/api/maintenance/retrieve_maintenance.py +190 -0
- render/public_api/api/maintenance/trigger_maintenance.py +212 -0
- render/public_api/api/maintenance/update_maintenance.py +222 -0
- render/public_api/api/metrics/__init__.py +1 -0
- render/public_api/api/metrics/delete_owner_metrics_stream.py +172 -0
- render/public_api/api/metrics/get_active_connections.py +237 -0
- render/public_api/api/metrics/get_bandwidth.py +237 -0
- render/public_api/api/metrics/get_bandwidth_sources.py +250 -0
- render/public_api/api/metrics/get_cpu.py +287 -0
- render/public_api/api/metrics/get_cpu_limit.py +267 -0
- render/public_api/api/metrics/get_cpu_target.py +267 -0
- render/public_api/api/metrics/get_disk_capacity.py +252 -0
- render/public_api/api/metrics/get_disk_usage.py +252 -0
- render/public_api/api/metrics/get_http_latency.py +297 -0
- render/public_api/api/metrics/get_http_requests.py +302 -0
- render/public_api/api/metrics/get_instance_count.py +252 -0
- render/public_api/api/metrics/get_memory.py +267 -0
- render/public_api/api/metrics/get_memory_limit.py +267 -0
- render/public_api/api/metrics/get_memory_target.py +267 -0
- render/public_api/api/metrics/get_owner_metrics_stream.py +174 -0
- render/public_api/api/metrics/get_replication_lag.py +237 -0
- render/public_api/api/metrics/get_task_runs_completed.py +281 -0
- render/public_api/api/metrics/get_task_runs_queued.py +237 -0
- render/public_api/api/metrics/list_application_filter_values.py +254 -0
- render/public_api/api/metrics/list_http_filter_values.py +282 -0
- render/public_api/api/metrics/list_path_filter_values.py +289 -0
- render/public_api/api/metrics/upsert_owner_metrics_stream.py +192 -0
- render/public_api/api/notification_settings/__init__.py +1 -0
- render/public_api/api/notification_settings/list_notification_overrides.py +255 -0
- render/public_api/api/notification_settings/patch_owner_notification_settings.py +207 -0
- render/public_api/api/notification_settings/patch_service_notification_overrides.py +207 -0
- render/public_api/api/notification_settings/retrieve_owner_notification_settings.py +193 -0
- render/public_api/api/notification_settings/retrieve_service_notification_overrides.py +193 -0
- render/public_api/api/object_storage/__init__.py +1 -0
- render/public_api/api/object_storage/delete_object.py +215 -0
- render/public_api/api/object_storage/get_object.py +221 -0
- render/public_api/api/object_storage/list_objects.py +240 -0
- render/public_api/api/object_storage/put_object.py +248 -0
- render/public_api/api/one_off_jobs/__init__.py +1 -0
- render/public_api/api/one_off_jobs/cancel_job.py +203 -0
- render/public_api/api/one_off_jobs/list_job.py +367 -0
- render/public_api/api/one_off_jobs/post_job.py +216 -0
- render/public_api/api/one_off_jobs/retrieve_job.py +203 -0
- render/public_api/api/postgres/__init__.py +1 -0
- render/public_api/api/postgres/create_postgres.py +199 -0
- render/public_api/api/postgres/create_postgres_export.py +207 -0
- render/public_api/api/postgres/create_postgres_user.py +229 -0
- render/public_api/api/postgres/delete_postgres.py +192 -0
- render/public_api/api/postgres/delete_postgres_user.py +201 -0
- render/public_api/api/postgres/failover_postgres.py +207 -0
- render/public_api/api/postgres/list_available_postgres_parameter_overrides.py +190 -0
- render/public_api/api/postgres/list_postgres.py +418 -0
- render/public_api/api/postgres/list_postgres_export.py +199 -0
- render/public_api/api/postgres/list_postgres_parameter_overrides.py +190 -0
- render/public_api/api/postgres/list_postgres_processes.py +190 -0
- render/public_api/api/postgres/list_postgres_sizes.py +190 -0
- render/public_api/api/postgres/list_postgres_table_scans.py +190 -0
- render/public_api/api/postgres/list_postgres_top_queries.py +194 -0
- render/public_api/api/postgres/list_postgres_users.py +195 -0
- render/public_api/api/postgres/promote_postgres.py +203 -0
- render/public_api/api/postgres/recover_postgres.py +231 -0
- render/public_api/api/postgres/restart_postgres.py +203 -0
- render/public_api/api/postgres/resume_postgres.py +203 -0
- render/public_api/api/postgres/retrieve_postgres.py +190 -0
- render/public_api/api/postgres/retrieve_postgres_connection_info.py +194 -0
- render/public_api/api/postgres/retrieve_postgres_recovery_info.py +194 -0
- render/public_api/api/postgres/setup_postgres_replication.py +231 -0
- render/public_api/api/postgres/suspend_postgres.py +203 -0
- render/public_api/api/postgres/update_postgres.py +217 -0
- render/public_api/api/postgres/update_postgres_parameter_overrides.py +217 -0
- render/public_api/api/projects_environments/__init__.py +1 -0
- render/public_api/api/projects_environments/add_resources_to_environment.py +212 -0
- render/public_api/api/projects_environments/create_environment.py +199 -0
- render/public_api/api/projects_environments/create_project.py +199 -0
- render/public_api/api/projects_environments/delete_environment.py +216 -0
- render/public_api/api/projects_environments/delete_project.py +216 -0
- render/public_api/api/projects_environments/list_environments.py +369 -0
- render/public_api/api/projects_environments/list_projects.py +329 -0
- render/public_api/api/projects_environments/remove_resources_from_environment.py +211 -0
- render/public_api/api/projects_environments/retrieve_environment.py +190 -0
- render/public_api/api/projects_environments/retrieve_project.py +190 -0
- render/public_api/api/projects_environments/update_environment.py +212 -0
- render/public_api/api/projects_environments/update_project.py +224 -0
- render/public_api/api/redis_deprecated/__init__.py +1 -0
- render/public_api/api/redis_deprecated/create_redis.py +199 -0
- render/public_api/api/redis_deprecated/delete_redis.py +188 -0
- render/public_api/api/redis_deprecated/list_redis.py +384 -0
- render/public_api/api/redis_deprecated/resume_redis.py +203 -0
- render/public_api/api/redis_deprecated/retrieve_redis.py +190 -0
- render/public_api/api/redis_deprecated/retrieve_redis_connection_info.py +194 -0
- render/public_api/api/redis_deprecated/suspend_redis.py +203 -0
- render/public_api/api/redis_deprecated/update_redis.py +217 -0
- render/public_api/api/registry_credentials/__init__.py +1 -0
- render/public_api/api/registry_credentials/create_registry_credential.py +209 -0
- render/public_api/api/registry_credentials/delete_registry_credential.py +198 -0
- render/public_api/api/registry_credentials/list_registry_credentials.py +370 -0
- render/public_api/api/registry_credentials/retrieve_registry_credential.py +200 -0
- render/public_api/api/registry_credentials/update_registry_credential.py +241 -0
- render/public_api/api/sandboxes/__init__.py +1 -0
- render/public_api/api/sandboxes/connect_sandbox_files.py +281 -0
- render/public_api/api/sandboxes/connect_sandbox_run.py +271 -0
- render/public_api/api/sandboxes/create_sandbox.py +199 -0
- render/public_api/api/sandboxes/list_sandbox_files.py +244 -0
- render/public_api/api/sandboxes/list_sandbox_groups.py +216 -0
- render/public_api/api/sandboxes/list_sandboxes.py +269 -0
- render/public_api/api/sandboxes/retrieve_sandbox.py +211 -0
- render/public_api/api/sandboxes/retrieve_sandbox_execution.py +224 -0
- render/public_api/api/sandboxes/stream_sandbox_logs.py +274 -0
- render/public_api/api/sandboxes/terminate_sandbox.py +217 -0
- render/public_api/api/sandboxes/update_sandbox_exec.py +258 -0
- render/public_api/api/services/__init__.py +1 -0
- render/public_api/api/services/add_or_update_secret_file.py +252 -0
- render/public_api/api/services/autoscale_service.py +230 -0
- render/public_api/api/services/create_ephemeral_shell.py +217 -0
- render/public_api/api/services/create_service.py +204 -0
- render/public_api/api/services/delete_autoscaling_config.py +198 -0
- render/public_api/api/services/delete_env_var.py +223 -0
- render/public_api/api/services/delete_secret_file.py +223 -0
- render/public_api/api/services/delete_service.py +198 -0
- render/public_api/api/services/get_env_vars_for_service.py +241 -0
- render/public_api/api/services/list_events.py +288 -0
- render/public_api/api/services/list_instances.py +190 -0
- render/public_api/api/services/list_secret_files_for_service.py +241 -0
- render/public_api/api/services/list_services.py +439 -0
- render/public_api/api/services/preview_service.py +227 -0
- render/public_api/api/services/purge_cache.py +203 -0
- render/public_api/api/services/restart_service.py +211 -0
- render/public_api/api/services/resume_service.py +203 -0
- render/public_api/api/services/retrieve_env_var.py +225 -0
- render/public_api/api/services/retrieve_secret_file.py +225 -0
- render/public_api/api/services/retrieve_service.py +190 -0
- render/public_api/api/services/retrieve_service_outbound_ips.py +207 -0
- render/public_api/api/services/scale_service.py +237 -0
- render/public_api/api/services/suspend_service.py +203 -0
- render/public_api/api/services/update_env_var.py +257 -0
- render/public_api/api/services/update_env_vars_for_service.py +241 -0
- render/public_api/api/services/update_secret_files_for_service.py +255 -0
- render/public_api/api/services/update_service.py +227 -0
- render/public_api/api/services_cron_jobs/__init__.py +1 -0
- render/public_api/api/services_cron_jobs/cancel_cron_job_run.py +183 -0
- render/public_api/api/services_cron_jobs/run_cron_job.py +185 -0
- render/public_api/api/services_headers/__init__.py +1 -0
- render/public_api/api/services_headers/add_headers.py +222 -0
- render/public_api/api/services_headers/delete_header.py +211 -0
- render/public_api/api/services_headers/list_headers.py +302 -0
- render/public_api/api/services_headers/update_headers.py +243 -0
- render/public_api/api/services_routes/__init__.py +1 -0
- render/public_api/api/services_routes/add_route.py +227 -0
- render/public_api/api/services_routes/delete_route.py +211 -0
- render/public_api/api/services_routes/list_routes.py +306 -0
- render/public_api/api/services_routes/patch_route.py +259 -0
- render/public_api/api/services_routes/put_routes.py +259 -0
- render/public_api/api/users/__init__.py +1 -0
- render/public_api/api/users/get_user.py +161 -0
- render/public_api/api/webhooks/__init__.py +1 -0
- render/public_api/api/webhooks/create_webhook.py +199 -0
- render/public_api/api/webhooks/delete_webhook.py +183 -0
- render/public_api/api/webhooks/list_webhook_events.py +268 -0
- render/public_api/api/webhooks/list_webhooks.py +237 -0
- render/public_api/api/webhooks/retrieve_webhook.py +185 -0
- render/public_api/api/webhooks/update_webhook.py +212 -0
- render/public_api/api/workflow_tasks_beta/__init__.py +1 -0
- render/public_api/api/workflow_tasks_beta/cancel_task_run.py +188 -0
- render/public_api/api/workflow_tasks_beta/create_task.py +204 -0
- render/public_api/api/workflow_tasks_beta/get_task.py +190 -0
- render/public_api/api/workflow_tasks_beta/get_task_run.py +190 -0
- render/public_api/api/workflow_tasks_beta/list_task_runs.py +317 -0
- render/public_api/api/workflow_tasks_beta/list_tasks.py +298 -0
- render/public_api/api/workflow_tasks_beta/stream_task_runs_events.py +230 -0
- render/public_api/api/workflows_beta/__init__.py +1 -0
- render/public_api/api/workflows_beta/create_workflow.py +199 -0
- render/public_api/api/workflows_beta/create_workflow_version.py +197 -0
- render/public_api/api/workflows_beta/delete_workflow.py +188 -0
- render/public_api/api/workflows_beta/get_workflow.py +190 -0
- render/public_api/api/workflows_beta/get_workflow_version.py +190 -0
- render/public_api/api/workflows_beta/list_workflow_versions.py +275 -0
- render/public_api/api/workflows_beta/list_workflows.py +298 -0
- render/public_api/api/workflows_beta/update_workflow.py +212 -0
- render/public_api/api/workspaces/__init__.py +1 -0
- render/public_api/api/workspaces/list_owners.py +255 -0
- render/public_api/api/workspaces/remove_workspace_member.py +206 -0
- render/public_api/api/workspaces/retrieve_owner.py +207 -0
- render/public_api/api/workspaces/retrieve_owner_members.py +200 -0
- render/public_api/api/workspaces/update_workspace_member.py +235 -0
- render/public_api/client.py +268 -0
- render/public_api/errors.py +16 -0
- render/public_api/models/__init__.py +861 -0
- render/public_api/models/add_headers_response_201.py +72 -0
- render/public_api/models/add_or_update_artifact_source_secret_file_body.py +59 -0
- render/public_api/models/add_or_update_secret_file_body.py +59 -0
- render/public_api/models/artifact.py +145 -0
- render/public_api/models/artifact_fetch_failed.py +67 -0
- render/public_api/models/artifact_source.py +162 -0
- render/public_api/models/artifact_source_changed.py +70 -0
- render/public_api/models/artifact_source_git.py +167 -0
- render/public_api/models/artifact_source_git_region.py +12 -0
- render/public_api/models/artifact_source_git_runtime.py +14 -0
- render/public_api/models/artifact_source_image.py +78 -0
- render/public_api/models/artifact_source_patch_git.py +175 -0
- render/public_api/models/artifact_source_patch_git_region.py +12 -0
- render/public_api/models/artifact_source_patch_git_runtime.py +14 -0
- render/public_api/models/artifact_source_patch_image.py +71 -0
- render/public_api/models/artifact_source_patch_input.py +101 -0
- render/public_api/models/artifact_source_post_input.py +213 -0
- render/public_api/models/artifact_source_service_link.py +67 -0
- render/public_api/models/artifact_source_with_cursor.py +73 -0
- render/public_api/models/audit_log.py +113 -0
- render/public_api/models/audit_log_actor.py +80 -0
- render/public_api/models/audit_log_actor_type.py +10 -0
- render/public_api/models/audit_log_event.py +87 -0
- render/public_api/models/audit_log_metadata.py +49 -0
- render/public_api/models/audit_log_status.py +9 -0
- render/public_api/models/audit_log_with_cursor.py +73 -0
- render/public_api/models/auto_deploy.py +9 -0
- render/public_api/models/auto_deploy_disabled.py +79 -0
- render/public_api/models/auto_deploy_enabled.py +68 -0
- render/public_api/models/auto_deploy_trigger.py +10 -0
- render/public_api/models/autoscaling_config.py +89 -0
- render/public_api/models/autoscaling_config_changed.py +83 -0
- render/public_api/models/autoscaling_criteria.py +73 -0
- render/public_api/models/autoscaling_criteria_percentage.py +68 -0
- render/public_api/models/autoscaling_ended.py +67 -0
- render/public_api/models/autoscaling_started.py +105 -0
- render/public_api/models/background_worker_details.py +256 -0
- render/public_api/models/background_worker_details_patch.py +184 -0
- render/public_api/models/background_worker_details_post.py +266 -0
- render/public_api/models/blueprint.py +128 -0
- render/public_api/models/blueprint_detail.py +150 -0
- render/public_api/models/blueprint_patch.py +77 -0
- render/public_api/models/blueprint_with_cursor.py +74 -0
- render/public_api/models/branch_deleted.py +67 -0
- render/public_api/models/build.py +148 -0
- render/public_api/models/build_config.py +96 -0
- render/public_api/models/build_deploy_end_reason.py +122 -0
- render/public_api/models/build_deploy_end_reason_id.py +59 -0
- render/public_api/models/build_deploy_trigger.py +150 -0
- render/public_api/models/build_ended.py +91 -0
- render/public_api/models/build_filter.py +67 -0
- render/public_api/models/build_plan.py +9 -0
- render/public_api/models/build_runtime.py +14 -0
- render/public_api/models/build_started.py +73 -0
- render/public_api/models/build_status.py +13 -0
- render/public_api/models/cache.py +61 -0
- render/public_api/models/cache_profile.py +10 -0
- render/public_api/models/cidr_block_and_description.py +67 -0
- render/public_api/models/commit_ignored.py +67 -0
- render/public_api/models/commit_ref.py +59 -0
- render/public_api/models/connect_sandbox_files_operation.py +9 -0
- render/public_api/models/connect_sandbox_run_operation.py +9 -0
- render/public_api/models/create_custom_domain_body.py +59 -0
- render/public_api/models/create_deploy_body.py +130 -0
- render/public_api/models/create_deploy_body_clear_cache.py +9 -0
- render/public_api/models/create_ephemeral_shell_body.py +69 -0
- render/public_api/models/create_ephemeral_shell_response_201.py +59 -0
- render/public_api/models/create_registry_credential_body.py +93 -0
- render/public_api/models/create_version.py +70 -0
- render/public_api/models/credential_create_input.py +59 -0
- render/public_api/models/cron_job_details.py +163 -0
- render/public_api/models/cron_job_details_patch.py +138 -0
- render/public_api/models/cron_job_details_post.py +172 -0
- render/public_api/models/cron_job_run.py +123 -0
- render/public_api/models/cron_job_run_ended.py +110 -0
- render/public_api/models/cron_job_run_started.py +59 -0
- render/public_api/models/cron_job_run_status.py +11 -0
- render/public_api/models/custom_domain.py +135 -0
- render/public_api/models/custom_domain_domain_type.py +9 -0
- render/public_api/models/custom_domain_server.py +68 -0
- render/public_api/models/custom_domain_verification_status.py +9 -0
- render/public_api/models/custom_domain_with_cursor.py +73 -0
- render/public_api/models/database_role.py +9 -0
- render/public_api/models/database_status.py +18 -0
- render/public_api/models/dedicated_ip.py +146 -0
- render/public_api/models/dedicated_ip_status.py +14 -0
- render/public_api/models/dedicated_ippatch.py +81 -0
- render/public_api/models/dedicated_ippost.py +100 -0
- render/public_api/models/deploy.py +211 -0
- render/public_api/models/deploy_commit.py +86 -0
- render/public_api/models/deploy_ended.py +102 -0
- render/public_api/models/deploy_image.py +78 -0
- render/public_api/models/deploy_mode.py +9 -0
- render/public_api/models/deploy_started.py +85 -0
- render/public_api/models/deploy_status.py +18 -0
- render/public_api/models/deploy_trigger.py +17 -0
- render/public_api/models/deploy_with_cursor.py +81 -0
- render/public_api/models/disk.py +83 -0
- render/public_api/models/disk_created.py +67 -0
- render/public_api/models/disk_deleted.py +59 -0
- render/public_api/models/disk_details.py +112 -0
- render/public_api/models/disk_patch.py +77 -0
- render/public_api/models/disk_post.py +83 -0
- render/public_api/models/disk_snapshot.py +88 -0
- render/public_api/models/disk_updated.py +75 -0
- render/public_api/models/disk_with_cursor.py +73 -0
- render/public_api/models/docker_details.py +108 -0
- render/public_api/models/docker_details_patch.py +86 -0
- render/public_api/models/docker_details_post.py +86 -0
- render/public_api/models/edge_cache_disabled.py +65 -0
- render/public_api/models/edge_cache_enabled.py +65 -0
- render/public_api/models/edge_cache_purged.py +65 -0
- render/public_api/models/edge_cache_trigger.py +91 -0
- render/public_api/models/env_group.py +162 -0
- render/public_api/models/env_group_link.py +77 -0
- render/public_api/models/env_group_meta.py +126 -0
- render/public_api/models/env_group_patch_input.py +59 -0
- render/public_api/models/env_group_post_input.py +161 -0
- render/public_api/models/env_var.py +67 -0
- render/public_api/models/env_var_generate_value.py +59 -0
- render/public_api/models/env_var_key_generate_value.py +68 -0
- render/public_api/models/env_var_key_value.py +67 -0
- render/public_api/models/env_var_value.py +59 -0
- render/public_api/models/env_var_with_cursor.py +73 -0
- render/public_api/models/environment.py +152 -0
- render/public_api/models/environment_patch_input.py +112 -0
- render/public_api/models/environment_post_input.py +122 -0
- render/public_api/models/environment_resources_post_input.py +59 -0
- render/public_api/models/environment_with_cursor.py +74 -0
- render/public_api/models/error.py +79 -0
- render/public_api/models/error_code.py +10 -0
- render/public_api/models/event.py +1122 -0
- render/public_api/models/event_status.py +10 -0
- render/public_api/models/event_type.py +74 -0
- render/public_api/models/execution.py +144 -0
- render/public_api/models/execution_operation.py +11 -0
- render/public_api/models/execution_type.py +9 -0
- render/public_api/models/failure_reason.py +138 -0
- render/public_api/models/filter_application_values_collection_item.py +78 -0
- render/public_api/models/filter_application_values_collection_item_filter.py +8 -0
- render/public_api/models/filter_http_values_collection_item.py +78 -0
- render/public_api/models/filter_http_values_collection_item_filter.py +9 -0
- render/public_api/models/get_bandwidth_sources_response_200.py +75 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item.py +101 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item_labels.py +78 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item_labels_traffic_source.py +12 -0
- render/public_api/models/get_bandwidth_sources_response_200_data_item_values_item.py +68 -0
- render/public_api/models/get_cpu_aggregation_method.py +10 -0
- render/public_api/models/get_http_requests_aggregate_by.py +9 -0
- render/public_api/models/get_object_output.py +71 -0
- render/public_api/models/get_task_runs_completed_aggregate_by.py +8 -0
- render/public_api/models/get_task_runs_completed_state.py +9 -0
- render/public_api/models/header.py +83 -0
- render/public_api/models/header_input.py +76 -0
- render/public_api/models/header_with_cursor.py +73 -0
- render/public_api/models/image.py +80 -0
- render/public_api/models/image_pull_failed.py +67 -0
- render/public_api/models/initial_deploy_hook_ended.py +59 -0
- render/public_api/models/initial_deploy_hook_started.py +59 -0
- render/public_api/models/instance_count_changed.py +67 -0
- render/public_api/models/instance_type_changed.py +67 -0
- render/public_api/models/internal_routing.py +8 -0
- render/public_api/models/job.py +144 -0
- render/public_api/models/job_run_ended.py +92 -0
- render/public_api/models/job_status.py +12 -0
- render/public_api/models/job_with_cursor.py +73 -0
- render/public_api/models/key_value.py +182 -0
- render/public_api/models/key_value_available.py +44 -0
- render/public_api/models/key_value_config_restart.py +67 -0
- render/public_api/models/key_value_connection_info.py +76 -0
- render/public_api/models/key_value_detail.py +192 -0
- render/public_api/models/key_value_options.py +79 -0
- render/public_api/models/key_value_patch_input.py +138 -0
- render/public_api/models/key_value_plan.py +13 -0
- render/public_api/models/key_value_post_input.py +166 -0
- render/public_api/models/key_value_unhealthy.py +44 -0
- render/public_api/models/key_value_with_cursor.py +73 -0
- render/public_api/models/label.py +68 -0
- render/public_api/models/list_custom_domains_domain_type.py +9 -0
- render/public_api/models/list_custom_domains_verification_status.py +9 -0
- render/public_api/models/list_logs_response_200.py +102 -0
- render/public_api/models/list_logs_values_label.py +13 -0
- render/public_api/models/list_objects_response.py +92 -0
- render/public_api/models/list_postgres_suspended_item.py +9 -0
- render/public_api/models/list_postgres_users_response_200_item.py +86 -0
- render/public_api/models/list_routes_type_item.py +9 -0
- render/public_api/models/list_services_suspended_item.py +9 -0
- render/public_api/models/log.py +100 -0
- render/public_api/models/log_direction.py +9 -0
- render/public_api/models/log_label.py +70 -0
- render/public_api/models/log_label_name.py +22 -0
- render/public_api/models/log_stream_owner_update.py +80 -0
- render/public_api/models/log_stream_preview_setting.py +9 -0
- render/public_api/models/log_stream_resource_update.py +80 -0
- render/public_api/models/log_stream_setting.py +9 -0
- render/public_api/models/maintenance_ended.py +44 -0
- render/public_api/models/maintenance_mode.py +68 -0
- render/public_api/models/maintenance_mode_enabled.py +59 -0
- render/public_api/models/maintenance_mode_uri_updated.py +67 -0
- render/public_api/models/maintenance_run.py +105 -0
- render/public_api/models/maintenance_run_patch.py +69 -0
- render/public_api/models/maintenance_run_with_resource.py +114 -0
- render/public_api/models/maintenance_started.py +65 -0
- render/public_api/models/maintenance_state.py +13 -0
- render/public_api/models/maintenance_trigger.py +91 -0
- render/public_api/models/maxmemory_policy.py +15 -0
- render/public_api/models/metrics_stream.py +77 -0
- render/public_api/models/metrics_stream_input.py +86 -0
- render/public_api/models/native_environment_details.py +78 -0
- render/public_api/models/native_environment_details_patch.py +68 -0
- render/public_api/models/native_environment_details_post.py +68 -0
- render/public_api/models/notification_override.py +78 -0
- render/public_api/models/notification_override_with_cursor.py +73 -0
- render/public_api/models/notification_service_override.py +78 -0
- render/public_api/models/notification_service_override_patch.py +84 -0
- render/public_api/models/notification_setting.py +93 -0
- render/public_api/models/notification_setting_patch.py +85 -0
- render/public_api/models/notify_override.py +11 -0
- render/public_api/models/notify_preview_override.py +10 -0
- render/public_api/models/notify_setting.py +10 -0
- render/public_api/models/notify_setting_v2.py +10 -0
- render/public_api/models/object_metadata.py +77 -0
- render/public_api/models/object_with_cursor.py +73 -0
- render/public_api/models/oom_killed.py +59 -0
- render/public_api/models/otel_provider_type.py +16 -0
- render/public_api/models/outbound_ips.py +81 -0
- render/public_api/models/outbound_ips_type.py +9 -0
- render/public_api/models/owner.py +121 -0
- render/public_api/models/owner_log_stream_setting.py +86 -0
- render/public_api/models/owner_type.py +9 -0
- render/public_api/models/owner_with_cursor.py +81 -0
- render/public_api/models/paid_plan.py +13 -0
- render/public_api/models/patch_route_response_200.py +72 -0
- render/public_api/models/persistence_mode.py +10 -0
- render/public_api/models/pipeline_minutes_exhausted.py +73 -0
- render/public_api/models/plan.py +22 -0
- render/public_api/models/post_job_body.py +70 -0
- render/public_api/models/postgres.py +300 -0
- render/public_api/models/postgres_available.py +44 -0
- render/public_api/models/postgres_available_parameter_override.py +106 -0
- render/public_api/models/postgres_available_parameter_overrides_result.py +75 -0
- render/public_api/models/postgres_backup_completed.py +44 -0
- render/public_api/models/postgres_backup_failed.py +44 -0
- render/public_api/models/postgres_backup_started.py +44 -0
- render/public_api/models/postgres_cluster_leader_changed.py +59 -0
- render/public_api/models/postgres_connection_info.py +103 -0
- render/public_api/models/postgres_connection_pool_changed.py +91 -0
- render/public_api/models/postgres_connection_pool_enabled_changed.py +83 -0
- render/public_api/models/postgres_created.py +72 -0
- render/public_api/models/postgres_detail.py +336 -0
- render/public_api/models/postgres_detail_suspended.py +9 -0
- render/public_api/models/postgres_disk_size_changed.py +91 -0
- render/public_api/models/postgres_export.py +80 -0
- render/public_api/models/postgres_ha_status_changed.py +67 -0
- render/public_api/models/postgres_parameter_overrides.py +44 -0
- render/public_api/models/postgres_parameter_overrides_wrapper.py +65 -0
- render/public_api/models/postgres_patch_input.py +199 -0
- render/public_api/models/postgres_pitr_checkpoint_completed.py +44 -0
- render/public_api/models/postgres_pitr_checkpoint_failed.py +44 -0
- render/public_api/models/postgres_pitr_checkpoint_started.py +44 -0
- render/public_api/models/postgres_plans.py +35 -0
- render/public_api/models/postgres_post_input.py +254 -0
- render/public_api/models/postgres_process.py +185 -0
- render/public_api/models/postgres_processes_result.py +73 -0
- render/public_api/models/postgres_put_parameter_overrides_result.py +82 -0
- render/public_api/models/postgres_query_statistic.py +222 -0
- render/public_api/models/postgres_read_replica_stale.py +44 -0
- render/public_api/models/postgres_read_replicas_changed.py +67 -0
- render/public_api/models/postgres_replication_setup_input.py +96 -0
- render/public_api/models/postgres_restarted.py +72 -0
- render/public_api/models/postgres_size.py +96 -0
- render/public_api/models/postgres_sizes_result.py +73 -0
- render/public_api/models/postgres_suspended.py +9 -0
- render/public_api/models/postgres_table_scan.py +87 -0
- render/public_api/models/postgres_table_scans_result.py +73 -0
- render/public_api/models/postgres_top_queries_result.py +73 -0
- render/public_api/models/postgres_unavailable.py +44 -0
- render/public_api/models/postgres_upgrade_failed.py +67 -0
- render/public_api/models/postgres_upgrade_started.py +91 -0
- render/public_api/models/postgres_upgrade_succeeded.py +67 -0
- render/public_api/models/postgres_version.py +15 -0
- render/public_api/models/postgres_with_cursor.py +73 -0
- render/public_api/models/pre_deploy_ended.py +99 -0
- render/public_api/models/pre_deploy_started.py +67 -0
- render/public_api/models/preview_input.py +94 -0
- render/public_api/models/previews.py +67 -0
- render/public_api/models/previews_generation.py +10 -0
- render/public_api/models/private_service_details.py +282 -0
- render/public_api/models/private_service_details_patch.py +184 -0
- render/public_api/models/private_service_details_post.py +266 -0
- render/public_api/models/project.py +108 -0
- render/public_api/models/project_patch_input.py +60 -0
- render/public_api/models/project_post_environment_input.py +114 -0
- render/public_api/models/project_post_input.py +89 -0
- render/public_api/models/project_with_cursor.py +73 -0
- render/public_api/models/protected_status.py +9 -0
- render/public_api/models/pull_request_previews_enabled.py +9 -0
- render/public_api/models/put_object_input.py +59 -0
- render/public_api/models/put_object_output.py +79 -0
- render/public_api/models/read_replica.py +91 -0
- render/public_api/models/read_replica_input.py +83 -0
- render/public_api/models/recovery_info.py +80 -0
- render/public_api/models/recovery_info_recovery_status.py +10 -0
- render/public_api/models/recovery_input.py +114 -0
- render/public_api/models/redis.py +182 -0
- render/public_api/models/redis_connection_info.py +76 -0
- render/public_api/models/redis_detail.py +192 -0
- render/public_api/models/redis_options.py +79 -0
- render/public_api/models/redis_patch_input.py +138 -0
- render/public_api/models/redis_plan.py +13 -0
- render/public_api/models/redis_post_input.py +166 -0
- render/public_api/models/redis_with_cursor.py +73 -0
- render/public_api/models/region.py +12 -0
- render/public_api/models/registry_credential.py +95 -0
- render/public_api/models/registry_credential_registry.py +12 -0
- render/public_api/models/registry_credential_summary.py +67 -0
- render/public_api/models/render_subdomain_policy.py +9 -0
- render/public_api/models/resource.py +67 -0
- render/public_api/models/resource_log_stream_setting.py +87 -0
- render/public_api/models/resource_ref.py +77 -0
- render/public_api/models/resource_ref_type.py +17 -0
- render/public_api/models/rollback_deploy_body.py +59 -0
- render/public_api/models/route.py +93 -0
- render/public_api/models/route_patch.py +60 -0
- render/public_api/models/route_post.py +88 -0
- render/public_api/models/route_put.py +77 -0
- render/public_api/models/route_type.py +9 -0
- render/public_api/models/route_with_cursor.py +73 -0
- render/public_api/models/run_task.py +96 -0
- render/public_api/models/runtime.py +12 -0
- render/public_api/models/sandbox.py +149 -0
- render/public_api/models/sandbox_connect_request.py +62 -0
- render/public_api/models/sandbox_connect_response.py +98 -0
- render/public_api/models/sandbox_directory_listing.py +82 -0
- render/public_api/models/sandbox_exec_update_request.py +60 -0
- render/public_api/models/sandbox_exec_update_response.py +130 -0
- render/public_api/models/sandbox_file_entry.py +98 -0
- render/public_api/models/sandbox_file_entry_type.py +10 -0
- render/public_api/models/sandbox_group.py +141 -0
- render/public_api/models/sandbox_group_with_cursor.py +74 -0
- render/public_api/models/sandbox_network_policy.py +61 -0
- render/public_api/models/sandbox_network_policy_default.py +9 -0
- render/public_api/models/sandbox_plan.py +10 -0
- render/public_api/models/sandbox_post.py +138 -0
- render/public_api/models/sandbox_post_env.py +44 -0
- render/public_api/models/sandbox_status.py +13 -0
- render/public_api/models/sandbox_with_cursor.py +74 -0
- render/public_api/models/scale_service_body.py +59 -0
- render/public_api/models/schemas_build_filter.py +70 -0
- render/public_api/models/schemas_image.py +96 -0
- render/public_api/models/schemas_user.py +68 -0
- render/public_api/models/secret_file.py +67 -0
- render/public_api/models/secret_file_input.py +67 -0
- render/public_api/models/secret_file_with_cursor.py +73 -0
- render/public_api/models/server_available.py +44 -0
- render/public_api/models/server_failed.py +81 -0
- render/public_api/models/server_hardware_failure.py +44 -0
- render/public_api/models/server_port.py +69 -0
- render/public_api/models/server_port_protocol.py +9 -0
- render/public_api/models/server_restarted.py +66 -0
- render/public_api/models/service_disk.py +78 -0
- render/public_api/models/service_env.py +15 -0
- render/public_api/models/service_event.py +770 -0
- render/public_api/models/service_event_type.py +48 -0
- render/public_api/models/service_event_with_cursor.py +66 -0
- render/public_api/models/service_instance.py +69 -0
- render/public_api/models/service_patch.py +273 -0
- render/public_api/models/service_post.py +357 -0
- render/public_api/models/service_resumed.py +44 -0
- render/public_api/models/service_runtime.py +15 -0
- render/public_api/models/service_suspended.py +44 -0
- render/public_api/models/service_type.py +12 -0
- render/public_api/models/service_type_short.py +12 -0
- render/public_api/models/setup_postgres_replication_response_201.py +44 -0
- render/public_api/models/snapshot_restore_post.py +72 -0
- render/public_api/models/static_site_details.py +183 -0
- render/public_api/models/static_site_details_patch.py +147 -0
- render/public_api/models/static_site_details_post.py +189 -0
- render/public_api/models/status.py +12 -0
- render/public_api/models/stream_sandbox_logs_accept.py +8 -0
- render/public_api/models/stream_task_runs_events_accept.py +8 -0
- render/public_api/models/suspender_added.py +83 -0
- render/public_api/models/suspender_removed.py +83 -0
- render/public_api/models/suspender_type.py +14 -0
- render/public_api/models/sync.py +118 -0
- render/public_api/models/sync_state.py +12 -0
- render/public_api/models/sync_with_cursor.py +74 -0
- render/public_api/models/task.py +97 -0
- render/public_api/models/task_attempt.py +121 -0
- render/public_api/models/task_attempt_details.py +141 -0
- render/public_api/models/task_data_type_1.py +44 -0
- render/public_api/models/task_run.py +168 -0
- render/public_api/models/task_run_details.py +216 -0
- render/public_api/models/task_run_status.py +14 -0
- render/public_api/models/task_run_with_cursor.py +73 -0
- render/public_api/models/task_with_cursor.py +73 -0
- render/public_api/models/team_member.py +102 -0
- render/public_api/models/team_member_role.py +12 -0
- render/public_api/models/team_member_status.py +9 -0
- render/public_api/models/time_series.py +100 -0
- render/public_api/models/time_series_value.py +70 -0
- render/public_api/models/update_env_group_secret_file_body.py +59 -0
- render/public_api/models/update_registry_credential_body.py +85 -0
- render/public_api/models/update_workspace_member_body.py +61 -0
- render/public_api/models/user.py +67 -0
- render/public_api/models/validate_blueprint_request.py +84 -0
- render/public_api/models/validate_blueprint_response.py +105 -0
- render/public_api/models/validation_error.py +88 -0
- render/public_api/models/validation_plan_summary.py +107 -0
- render/public_api/models/web_service_details.py +385 -0
- render/public_api/models/web_service_details_patch.py +270 -0
- render/public_api/models/web_service_details_post.py +354 -0
- render/public_api/models/webhook.py +110 -0
- render/public_api/models/webhook_event.py +115 -0
- render/public_api/models/webhook_event_with_cursor.py +73 -0
- render/public_api/models/webhook_patch_input.py +98 -0
- render/public_api/models/webhook_post_input.py +102 -0
- render/public_api/models/webhook_with_cursor.py +73 -0
- render/public_api/models/workflow.py +163 -0
- render/public_api/models/workflow_create.py +169 -0
- render/public_api/models/workflow_update.py +109 -0
- render/public_api/models/workflow_version.py +95 -0
- render/public_api/models/workflow_version_status.py +13 -0
- render/public_api/models/workflow_version_with_cursor.py +73 -0
- render/public_api/models/workflow_with_cursor.py +73 -0
- render/public_api/models/zero_downtime_redeploy_ended.py +44 -0
- render/public_api/models/zero_downtime_redeploy_started.py +59 -0
- render/public_api/py.typed +1 -0
- render/public_api/types.py +54 -0
- render/py.typed +0 -0
- render/render.py +81 -0
- render/render_async.py +80 -0
- render/version.py +27 -0
- render/workflows/__init__.py +25 -0
- render/workflows/_callback_models.py +194 -0
- render/workflows/_uds_http.py +128 -0
- render/workflows/app.py +263 -0
- render/workflows/cli.py +62 -0
- render/workflows/client.py +354 -0
- render/workflows/context.py +75 -0
- render/workflows/executor.py +86 -0
- render/workflows/runner.py +174 -0
- render/workflows/task.py +348 -0
- render/workflows/tests/__init__.py +0 -0
- render/workflows/tests/test_app.py +343 -0
- render/workflows/tests/test_cli.py +134 -0
- render/workflows/tests/test_client.py +59 -0
- render/workflows/tests/test_context.py +118 -0
- render/workflows/tests/test_end_to_end.py +307 -0
- render/workflows/tests/test_executor.py +221 -0
- render/workflows/tests/test_lazy_imports.py +100 -0
- render/workflows/tests/test_registration.py +293 -0
- render/workflows/tests/test_translate_errors.py +126 -0
- render/workflows/tests/test_uds_http.py +199 -0
- render/workflows/tests/test_version.py +15 -0
- render-1.0.1.dist-info/METADATA +382 -0
- render-1.0.1.dist-info/RECORD +799 -0
- render-1.0.1.dist-info/WHEEL +4 -0
- render-1.0.1.dist-info/entry_points.txt +2 -0
- render-1.0.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# Auto-generated sync version. Do not edit — run scripts/unasync.py instead.
|
|
2
|
+
|
|
3
|
+
"""Typed wrapper over the generated public_api sandbox endpoints."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import posixpath
|
|
10
|
+
import stat
|
|
11
|
+
from collections.abc import Iterator, Sequence
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING, Any, NoReturn
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from render.client.errors import (
|
|
19
|
+
ClientError,
|
|
20
|
+
RateLimitError,
|
|
21
|
+
RenderError,
|
|
22
|
+
ServerError,
|
|
23
|
+
)
|
|
24
|
+
from render.client.util_sync import (
|
|
25
|
+
handle_api_error,
|
|
26
|
+
handle_http_errors,
|
|
27
|
+
handle_httpx_exception,
|
|
28
|
+
)
|
|
29
|
+
from render.experimental.sandbox._tar import (
|
|
30
|
+
close_content,
|
|
31
|
+
iter_file,
|
|
32
|
+
iter_tar_gzip,
|
|
33
|
+
stat_path,
|
|
34
|
+
)
|
|
35
|
+
from render.experimental.sandbox.errors import (
|
|
36
|
+
SandboxDownloadError,
|
|
37
|
+
SandboxExecError,
|
|
38
|
+
SandboxExecStreamError,
|
|
39
|
+
SandboxFileNotFoundError,
|
|
40
|
+
SandboxNotFoundError,
|
|
41
|
+
)
|
|
42
|
+
from render.experimental.sandbox.files import (
|
|
43
|
+
PartialDownload,
|
|
44
|
+
check_content_encoding,
|
|
45
|
+
disposition_filename,
|
|
46
|
+
media_type,
|
|
47
|
+
parent_dir,
|
|
48
|
+
prepare_extract_dir,
|
|
49
|
+
resolve_file_dest,
|
|
50
|
+
run_blocking,
|
|
51
|
+
)
|
|
52
|
+
from render.experimental.sandbox.types import (
|
|
53
|
+
Sandbox,
|
|
54
|
+
SandboxExecEvent,
|
|
55
|
+
SandboxExecExit,
|
|
56
|
+
SandboxExecOutput,
|
|
57
|
+
SandboxList,
|
|
58
|
+
)
|
|
59
|
+
from render.public_api.api.sandboxes import (
|
|
60
|
+
create_sandbox,
|
|
61
|
+
list_sandboxes,
|
|
62
|
+
retrieve_sandbox,
|
|
63
|
+
terminate_sandbox,
|
|
64
|
+
)
|
|
65
|
+
from render.public_api.models.error import Error
|
|
66
|
+
from render.public_api.models.sandbox import Sandbox as GeneratedSandbox
|
|
67
|
+
from render.public_api.models.sandbox_network_policy import SandboxNetworkPolicy
|
|
68
|
+
from render.public_api.models.sandbox_network_policy_default import (
|
|
69
|
+
SandboxNetworkPolicyDefault,
|
|
70
|
+
)
|
|
71
|
+
from render.public_api.models.sandbox_plan import SandboxPlan
|
|
72
|
+
from render.public_api.models.sandbox_post import SandboxPOST
|
|
73
|
+
from render.public_api.models.sandbox_post_env import SandboxPOSTEnv
|
|
74
|
+
from render.public_api.models.sandbox_status import SandboxStatus
|
|
75
|
+
from render.public_api.models.sandbox_with_cursor import SandboxWithCursor
|
|
76
|
+
from render.public_api.types import UNSET, Response
|
|
77
|
+
|
|
78
|
+
if TYPE_CHECKING:
|
|
79
|
+
from render.public_api.client import AuthenticatedClient, Client
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# The code the sandbox agent sends in a 404 body when the remote path is missing.
|
|
83
|
+
_FILE_NOT_FOUND_CODE = "file_not_found"
|
|
84
|
+
|
|
85
|
+
# Aliased at module scope so the annotations on _list_api_call resolve the
|
|
86
|
+
# builtin list, not the SyncSandboxApi.list method that shadows it in the class body.
|
|
87
|
+
_SandboxWithCursorList = list[SandboxWithCursor]
|
|
88
|
+
_SandboxStatusList = list[SandboxStatus]
|
|
89
|
+
|
|
90
|
+
# File transfer content types. The content type states intent and nothing else:
|
|
91
|
+
# a single file travels as octet-stream and a directory as an x-tar archive the
|
|
92
|
+
# server extracts, with Content-Encoding carrying wire compression separately.
|
|
93
|
+
_CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"
|
|
94
|
+
_CONTENT_TYPE_TAR = "application/x-tar"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _to_sandbox(model: GeneratedSandbox) -> Sandbox:
|
|
98
|
+
terminated = model.terminated_at
|
|
99
|
+
return Sandbox(
|
|
100
|
+
id=model.id,
|
|
101
|
+
status=model.status.value,
|
|
102
|
+
plan=model.plan.value,
|
|
103
|
+
network_policy=model.network_policy.default.value,
|
|
104
|
+
region=model.region,
|
|
105
|
+
timeout_seconds=model.timeout_seconds,
|
|
106
|
+
created_at=model.created_at,
|
|
107
|
+
terminated_at=terminated if isinstance(terminated, datetime) else None,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _normalize_statuses(status: str | Sequence[str] | None) -> list[SandboxStatus]:
|
|
112
|
+
if status is None:
|
|
113
|
+
return []
|
|
114
|
+
values = [status] if isinstance(status, str) else list(status)
|
|
115
|
+
return [SandboxStatus(value) for value in values]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SyncSandboxApi:
|
|
119
|
+
"""Typed wrapper over the generated sandbox endpoints."""
|
|
120
|
+
|
|
121
|
+
def __init__(self, client: AuthenticatedClient | Client):
|
|
122
|
+
self.client = client
|
|
123
|
+
|
|
124
|
+
def create(
|
|
125
|
+
self,
|
|
126
|
+
owner_id: str,
|
|
127
|
+
plan: str | None,
|
|
128
|
+
timeout_seconds: int | None,
|
|
129
|
+
network_policy: str | None,
|
|
130
|
+
region: str | None,
|
|
131
|
+
env: dict[str, str] | None,
|
|
132
|
+
) -> Sandbox:
|
|
133
|
+
body = SandboxPOST(owner_id=owner_id)
|
|
134
|
+
if plan is not None:
|
|
135
|
+
body.plan = SandboxPlan(plan)
|
|
136
|
+
if timeout_seconds is not None:
|
|
137
|
+
body.timeout_seconds = timeout_seconds
|
|
138
|
+
if region is not None:
|
|
139
|
+
body.region = region
|
|
140
|
+
if network_policy is not None:
|
|
141
|
+
body.network_policy = SandboxNetworkPolicy(
|
|
142
|
+
default=SandboxNetworkPolicyDefault(network_policy)
|
|
143
|
+
)
|
|
144
|
+
if env is not None:
|
|
145
|
+
body.env = SandboxPOSTEnv.from_dict(env)
|
|
146
|
+
|
|
147
|
+
response = self._create_api_call(body)
|
|
148
|
+
if not isinstance(response.parsed, GeneratedSandbox):
|
|
149
|
+
raise RenderError("Failed to create sandbox: unexpected response type")
|
|
150
|
+
return _to_sandbox(response.parsed)
|
|
151
|
+
|
|
152
|
+
def get(self, sandbox_id: str, owner_id: str) -> Sandbox:
|
|
153
|
+
try:
|
|
154
|
+
response = retrieve_sandbox.sync_detailed(
|
|
155
|
+
sandbox_id, client=self.client, owner_id=owner_id
|
|
156
|
+
)
|
|
157
|
+
except httpx.RequestError as exc:
|
|
158
|
+
handle_httpx_exception(exc, "retrieve sandbox")
|
|
159
|
+
except json.JSONDecodeError as exc:
|
|
160
|
+
body = exc.doc.strip() if exc.doc else "empty response"
|
|
161
|
+
raise RenderError(
|
|
162
|
+
f"retrieve sandbox failed: server returned a non-JSON response: {body}"
|
|
163
|
+
) from exc
|
|
164
|
+
if response.status_code == 404:
|
|
165
|
+
raise SandboxNotFoundError(f"sandbox {sandbox_id} not found")
|
|
166
|
+
handle_api_error(response, "retrieve sandbox")
|
|
167
|
+
if not isinstance(response.parsed, GeneratedSandbox):
|
|
168
|
+
raise RenderError("Failed to retrieve sandbox: unexpected response type")
|
|
169
|
+
return _to_sandbox(response.parsed)
|
|
170
|
+
|
|
171
|
+
def list(
|
|
172
|
+
self,
|
|
173
|
+
owner_id: str,
|
|
174
|
+
status: str | Sequence[str] | None,
|
|
175
|
+
cursor: str | None,
|
|
176
|
+
limit: int | None,
|
|
177
|
+
) -> SandboxList:
|
|
178
|
+
statuses = _normalize_statuses(status)
|
|
179
|
+
response = self._list_api_call(owner_id, statuses, cursor, limit)
|
|
180
|
+
parsed = response.parsed
|
|
181
|
+
if not isinstance(parsed, list):
|
|
182
|
+
raise RenderError("Failed to list sandboxes: unexpected response type")
|
|
183
|
+
sandboxes = [_to_sandbox(item.sandbox) for item in parsed]
|
|
184
|
+
next_cursor = parsed[-1].cursor if parsed else None
|
|
185
|
+
return SandboxList(sandboxes=sandboxes, next_cursor=next_cursor)
|
|
186
|
+
|
|
187
|
+
@handle_http_errors("list sandboxes")
|
|
188
|
+
def _list_api_call(
|
|
189
|
+
self,
|
|
190
|
+
owner_id: str,
|
|
191
|
+
statuses: _SandboxStatusList,
|
|
192
|
+
cursor: str | None,
|
|
193
|
+
limit: int | None,
|
|
194
|
+
) -> Response[Error | _SandboxWithCursorList]:
|
|
195
|
+
return list_sandboxes.sync_detailed(
|
|
196
|
+
client=self.client,
|
|
197
|
+
owner_id=[owner_id],
|
|
198
|
+
status=statuses or UNSET,
|
|
199
|
+
cursor=cursor if cursor is not None else UNSET,
|
|
200
|
+
limit=limit if limit is not None else UNSET,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def terminate(self, sandbox_id: str, owner_id: str) -> None:
|
|
204
|
+
try:
|
|
205
|
+
response = terminate_sandbox.sync_detailed(
|
|
206
|
+
sandbox_id, client=self.client, owner_id=owner_id
|
|
207
|
+
)
|
|
208
|
+
except httpx.RequestError as exc:
|
|
209
|
+
handle_httpx_exception(exc, "terminate sandbox")
|
|
210
|
+
except json.JSONDecodeError as exc:
|
|
211
|
+
body = exc.doc.strip() if exc.doc else "empty response"
|
|
212
|
+
raise RenderError(
|
|
213
|
+
f"terminate sandbox failed: server returned a non-JSON response: {body}"
|
|
214
|
+
) from exc
|
|
215
|
+
if response.status_code == 404:
|
|
216
|
+
raise SandboxNotFoundError(f"sandbox {sandbox_id} not found")
|
|
217
|
+
handle_api_error(response, "terminate sandbox")
|
|
218
|
+
|
|
219
|
+
def _mint_run_token(
|
|
220
|
+
self, sandbox_id: str, owner_id: str, operation: str, command: str
|
|
221
|
+
) -> dict[str, Any]:
|
|
222
|
+
# Command rides along so the API keeps a sanitized copy for the audit trail.
|
|
223
|
+
api_client = self.client.get_httpx_client()
|
|
224
|
+
try:
|
|
225
|
+
response = api_client.post(
|
|
226
|
+
f"/sandboxes/{sandbox_id}/runs/{operation}/token",
|
|
227
|
+
params={"ownerId": owner_id},
|
|
228
|
+
json={"command": command},
|
|
229
|
+
)
|
|
230
|
+
except httpx.RequestError as exc:
|
|
231
|
+
handle_httpx_exception(exc, "connect sandbox run")
|
|
232
|
+
if response.status_code >= 400:
|
|
233
|
+
_raise_sandbox_http_error(
|
|
234
|
+
sandbox_id, response.status_code, response.text, "exec"
|
|
235
|
+
)
|
|
236
|
+
return response.json()
|
|
237
|
+
|
|
238
|
+
def _mint_file_token(
|
|
239
|
+
self, sandbox_id: str, owner_id: str, operation: str, path: str
|
|
240
|
+
) -> dict[str, Any]:
|
|
241
|
+
api_client = self.client.get_httpx_client()
|
|
242
|
+
try:
|
|
243
|
+
response = api_client.post(
|
|
244
|
+
f"/sandboxes/{sandbox_id}/files/{operation}/token",
|
|
245
|
+
params={"ownerId": owner_id, "path": path},
|
|
246
|
+
)
|
|
247
|
+
except httpx.RequestError as exc:
|
|
248
|
+
handle_httpx_exception(exc, "connect sandbox files")
|
|
249
|
+
if response.status_code >= 400:
|
|
250
|
+
_raise_sandbox_http_error(
|
|
251
|
+
sandbox_id, response.status_code, response.text, operation
|
|
252
|
+
)
|
|
253
|
+
return response.json()
|
|
254
|
+
|
|
255
|
+
def upload(
|
|
256
|
+
self,
|
|
257
|
+
sandbox_id: str,
|
|
258
|
+
local_path: str | os.PathLike[str],
|
|
259
|
+
remote_path: str,
|
|
260
|
+
owner_id: str,
|
|
261
|
+
) -> None:
|
|
262
|
+
source = Path(local_path)
|
|
263
|
+
# The server rejects any path it would have to clean, so clean it here
|
|
264
|
+
# instead of turning a trailing slash into a 400. The CLI does the same
|
|
265
|
+
# before minting, which keeps the two clients in step. Guard the empty
|
|
266
|
+
# path first: normpath turns it into ".", which would quietly copy to
|
|
267
|
+
# the home directory instead of failing.
|
|
268
|
+
if not remote_path:
|
|
269
|
+
raise ValueError("remote_path is required")
|
|
270
|
+
remote_path = posixpath.normpath(remote_path)
|
|
271
|
+
# stat, not lstat: a symlinked directory uploads as the directory it
|
|
272
|
+
# points at. Statting before the mint also keeps a missing local path
|
|
273
|
+
# from spending a token.
|
|
274
|
+
info = stat_path(source)
|
|
275
|
+
if not (stat.S_ISDIR(info.st_mode) or stat.S_ISREG(info.st_mode)):
|
|
276
|
+
# Opening a fifo blocks until a writer appears, which may be never,
|
|
277
|
+
# and that open would block the event loop rather than time out.
|
|
278
|
+
# Refuse before the mint so a bad path does not spend a token.
|
|
279
|
+
raise ValueError(
|
|
280
|
+
f"{source} is not a file or directory; "
|
|
281
|
+
"sockets, fifos and devices cannot be copied to a sandbox"
|
|
282
|
+
)
|
|
283
|
+
connection = self._mint_file_token(sandbox_id, owner_id, "upload", remote_path)
|
|
284
|
+
|
|
285
|
+
headers = {"Authorization": f"Bearer {connection['token']}"}
|
|
286
|
+
content: Iterator[bytes]
|
|
287
|
+
if stat.S_ISDIR(info.st_mode):
|
|
288
|
+
headers["Content-Type"] = _CONTENT_TYPE_TAR
|
|
289
|
+
# The archive's length is not known until it has been produced, so
|
|
290
|
+
# the body goes out chunked. gzip is the wire encoding only: the
|
|
291
|
+
# server gunzips it and extracts the tar underneath.
|
|
292
|
+
headers["Content-Encoding"] = "gzip"
|
|
293
|
+
content = iter_tar_gzip(source)
|
|
294
|
+
else:
|
|
295
|
+
headers["Content-Type"] = _CONTENT_TYPE_OCTET_STREAM
|
|
296
|
+
# An explicit length keeps httpx from sending an iterator body
|
|
297
|
+
# chunked, and lets the sandbox size the write up front.
|
|
298
|
+
headers["Content-Length"] = str(info.st_size)
|
|
299
|
+
content = iter_file(source)
|
|
300
|
+
|
|
301
|
+
# Keep the connect timeout, but let a large upload take as long as it
|
|
302
|
+
# takes, as the object client does.
|
|
303
|
+
timeout = httpx.Timeout(5.0, read=None, write=None)
|
|
304
|
+
try:
|
|
305
|
+
with httpx.Client(timeout=timeout) as proxy_client:
|
|
306
|
+
response = proxy_client.request(
|
|
307
|
+
connection["method"],
|
|
308
|
+
connection["uri"],
|
|
309
|
+
headers=headers,
|
|
310
|
+
content=content,
|
|
311
|
+
)
|
|
312
|
+
except httpx.RequestError as exc:
|
|
313
|
+
handle_httpx_exception(exc, "upload to sandbox")
|
|
314
|
+
finally:
|
|
315
|
+
# httpx closes the body on today's failure paths, but that is its
|
|
316
|
+
# internals, not a contract. Closing here is what actually
|
|
317
|
+
# guarantees the archive producer stops when a request dies early.
|
|
318
|
+
close_content(content)
|
|
319
|
+
# Any 2xx, not just the 204 the sandbox sends today: the API deploys
|
|
320
|
+
# ahead of the SDK, so pinning the exact code would break on a benign
|
|
321
|
+
# change.
|
|
322
|
+
if not 200 <= response.status_code < 300:
|
|
323
|
+
_raise_sandbox_http_error(
|
|
324
|
+
sandbox_id, response.status_code, response.text, "upload"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def download_file(
|
|
328
|
+
self, sandbox_id: str, remote_path: str, local_path: str, owner_id: str
|
|
329
|
+
) -> str:
|
|
330
|
+
"""Copy remote_path out of the sandbox and return the local path written.
|
|
331
|
+
|
|
332
|
+
A directory arrives as an x-tar archive and is extracted under
|
|
333
|
+
local_path; anything else is written as a single file.
|
|
334
|
+
"""
|
|
335
|
+
connection = self._mint_file_token(
|
|
336
|
+
sandbox_id, owner_id, "download", remote_path
|
|
337
|
+
)
|
|
338
|
+
headers = {
|
|
339
|
+
"Authorization": f"Bearer {connection['token']}",
|
|
340
|
+
# Ask only for the coding httpx undoes for us. Left to itself it
|
|
341
|
+
# also advertises deflate, and br/zstd when those are installed,
|
|
342
|
+
# any of which would arrive as a body we refuse to write.
|
|
343
|
+
"Accept-Encoding": "gzip",
|
|
344
|
+
}
|
|
345
|
+
# read bounds the wait for the next chunk, not the transfer, so a large
|
|
346
|
+
# download still has as long as it needs while a body that stops
|
|
347
|
+
# arriving fails instead of hanging. Same budget as exec_stream.
|
|
348
|
+
timeout = httpx.Timeout(5.0, read=45.0, write=None)
|
|
349
|
+
try:
|
|
350
|
+
with (
|
|
351
|
+
httpx.Client(timeout=timeout) as proxy_client,
|
|
352
|
+
proxy_client.stream(
|
|
353
|
+
connection["method"], connection["uri"], headers=headers
|
|
354
|
+
) as response,
|
|
355
|
+
):
|
|
356
|
+
if response.status_code >= 400:
|
|
357
|
+
body = (response.read()).decode("utf-8", errors="replace")
|
|
358
|
+
_raise_sandbox_http_error(
|
|
359
|
+
sandbox_id, response.status_code, body, "download"
|
|
360
|
+
)
|
|
361
|
+
check_content_encoding(response.headers.get("content-encoding", ""))
|
|
362
|
+
|
|
363
|
+
if media_type(response.headers.get("content-type", "")) == (
|
|
364
|
+
_CONTENT_TYPE_TAR
|
|
365
|
+
):
|
|
366
|
+
# The archive is spooled next to the destination rather than
|
|
367
|
+
# extracted as it arrives: feeding tarfile from an async
|
|
368
|
+
# stream would take a thread to bridge, and the memory
|
|
369
|
+
# footprint is the same either way.
|
|
370
|
+
with PartialDownload(prepare_extract_dir(local_path)) as partial:
|
|
371
|
+
for chunk in response.iter_bytes():
|
|
372
|
+
partial.write(chunk)
|
|
373
|
+
run_blocking(partial.extract, local_path)
|
|
374
|
+
return local_path
|
|
375
|
+
|
|
376
|
+
dest = resolve_file_dest(
|
|
377
|
+
local_path,
|
|
378
|
+
disposition_filename(
|
|
379
|
+
response.headers.get("content-disposition", "")
|
|
380
|
+
),
|
|
381
|
+
remote_path,
|
|
382
|
+
)
|
|
383
|
+
with PartialDownload(parent_dir(dest)) as partial:
|
|
384
|
+
for chunk in response.iter_bytes():
|
|
385
|
+
partial.write(chunk)
|
|
386
|
+
return run_blocking(partial.commit, dest)
|
|
387
|
+
except httpx.RequestError as exc:
|
|
388
|
+
handle_httpx_exception(exc, "download sandbox file")
|
|
389
|
+
except OSError as exc:
|
|
390
|
+
# Spooling, extracting and renaming all touch the local disk, and a
|
|
391
|
+
# bare OSError from any of them tells a caller nothing about which
|
|
392
|
+
# download failed.
|
|
393
|
+
raise SandboxDownloadError(
|
|
394
|
+
f"could not write the download to {local_path}: {exc}"
|
|
395
|
+
) from exc
|
|
396
|
+
|
|
397
|
+
def exec_stream(
|
|
398
|
+
self, sandbox_id: str, command: str, owner_id: str, operation: str = "stream"
|
|
399
|
+
) -> Iterator[SandboxExecEvent]:
|
|
400
|
+
connection = self._mint_run_token(sandbox_id, owner_id, operation, command)
|
|
401
|
+
token = connection["token"]
|
|
402
|
+
uri = connection["uri"]
|
|
403
|
+
method = connection["method"]
|
|
404
|
+
headers = {
|
|
405
|
+
"Authorization": f"Bearer {token}",
|
|
406
|
+
"Accept": "text/event-stream",
|
|
407
|
+
"Content-Type": "application/json",
|
|
408
|
+
}
|
|
409
|
+
# Read timeout at 3x the server's 15s SSE keep-alive interval, so a dead
|
|
410
|
+
# connection is detected without cutting off a live but idle stream.
|
|
411
|
+
timeout = httpx.Timeout(5.0, read=45.0, write=None)
|
|
412
|
+
try:
|
|
413
|
+
with (
|
|
414
|
+
httpx.Client(timeout=timeout) as proxy_client,
|
|
415
|
+
proxy_client.stream(
|
|
416
|
+
method, uri, headers=headers, json={"command": command}
|
|
417
|
+
) as response,
|
|
418
|
+
):
|
|
419
|
+
if response.status_code >= 400:
|
|
420
|
+
body = (response.read()).decode("utf-8", errors="replace")
|
|
421
|
+
_raise_sandbox_http_error(
|
|
422
|
+
sandbox_id, response.status_code, body, "exec"
|
|
423
|
+
)
|
|
424
|
+
for name, data in _iter_sse_events(response.iter_lines()):
|
|
425
|
+
if name == "output":
|
|
426
|
+
payload = _load_event(data)
|
|
427
|
+
yield SandboxExecOutput(
|
|
428
|
+
stream=payload["stream"], data=payload["data"]
|
|
429
|
+
)
|
|
430
|
+
elif name == "exit":
|
|
431
|
+
payload = _load_event(data)
|
|
432
|
+
yield SandboxExecExit(exit_code=payload["exit_code"])
|
|
433
|
+
return
|
|
434
|
+
elif name == "error":
|
|
435
|
+
payload = _load_event(data)
|
|
436
|
+
raise SandboxExecStreamError(
|
|
437
|
+
payload.get("status", 0), payload.get("message", "")
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
raise SandboxExecError(f"unknown sandbox exec event {name!r}")
|
|
441
|
+
raise SandboxExecError(
|
|
442
|
+
"sandbox exec stream ended without an exit event"
|
|
443
|
+
)
|
|
444
|
+
except httpx.RequestError as exc:
|
|
445
|
+
handle_httpx_exception(exc, "exec sandbox")
|
|
446
|
+
|
|
447
|
+
@handle_http_errors("create sandbox")
|
|
448
|
+
def _create_api_call(self, body: SandboxPOST) -> Response[Error | GeneratedSandbox]:
|
|
449
|
+
return create_sandbox.sync_detailed(client=self.client, body=body)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _load_event(data: str) -> dict[str, Any]:
|
|
453
|
+
try:
|
|
454
|
+
return json.loads(data)
|
|
455
|
+
except json.JSONDecodeError as exc:
|
|
456
|
+
raise SandboxExecError(
|
|
457
|
+
f"failed to parse sandbox exec event data: {data}"
|
|
458
|
+
) from exc
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _file_error_code(body: str) -> str:
|
|
462
|
+
"""The ``code`` of a sandbox agent error body, or "" if it has none."""
|
|
463
|
+
try:
|
|
464
|
+
payload = json.loads(body)
|
|
465
|
+
except ValueError:
|
|
466
|
+
return ""
|
|
467
|
+
code = payload.get("code") if isinstance(payload, dict) else None
|
|
468
|
+
return code if isinstance(code, str) else ""
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _raise_sandbox_http_error(
|
|
472
|
+
sandbox_id: str, status_code: int, body: str, action: str
|
|
473
|
+
) -> NoReturn:
|
|
474
|
+
message = f"{action} failed with status {status_code}"
|
|
475
|
+
if body:
|
|
476
|
+
message = f"{message}: {body}"
|
|
477
|
+
if status_code == 404:
|
|
478
|
+
# The agent 404s a missing remote path with this code, and the sandbox
|
|
479
|
+
# is alive when it does. Any other 404 is the sandbox itself being gone,
|
|
480
|
+
# including the one the mint endpoint returns.
|
|
481
|
+
if _file_error_code(body) == _FILE_NOT_FOUND_CODE:
|
|
482
|
+
raise SandboxFileNotFoundError(message)
|
|
483
|
+
raise SandboxNotFoundError(f"sandbox {sandbox_id} not found")
|
|
484
|
+
if status_code == 429:
|
|
485
|
+
raise RateLimitError(message)
|
|
486
|
+
if 400 <= status_code < 500:
|
|
487
|
+
raise ClientError(message)
|
|
488
|
+
raise ServerError(message)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _iter_sse_events(
|
|
492
|
+
lines: Iterator[str],
|
|
493
|
+
) -> Iterator[tuple[str, str]]:
|
|
494
|
+
event_name = ""
|
|
495
|
+
data_parts: list[str] = []
|
|
496
|
+
for raw in lines:
|
|
497
|
+
line = raw.rstrip("\r")
|
|
498
|
+
if line == "":
|
|
499
|
+
if event_name or data_parts:
|
|
500
|
+
yield event_name, "\n".join(data_parts)
|
|
501
|
+
event_name = ""
|
|
502
|
+
data_parts = []
|
|
503
|
+
continue
|
|
504
|
+
if line.startswith(":"):
|
|
505
|
+
continue
|
|
506
|
+
if line.startswith("event:"):
|
|
507
|
+
event_name = line[len("event:") :].strip()
|
|
508
|
+
elif line.startswith("data:"):
|
|
509
|
+
chunk = line[len("data:") :]
|
|
510
|
+
if chunk.startswith(" "):
|
|
511
|
+
chunk = chunk[1:]
|
|
512
|
+
data_parts.append(chunk)
|
|
513
|
+
if event_name or data_parts:
|
|
514
|
+
yield event_name, "\n".join(data_parts)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""High-level client for creating and operating Render sandboxes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import AsyncIterator, Sequence
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from render.client.errors import RenderError
|
|
10
|
+
from render.experimental.sandbox.api import SandboxApi
|
|
11
|
+
from render.experimental.sandbox.files import normalize_remote_path
|
|
12
|
+
from render.experimental.sandbox.types import Sandbox, SandboxExecEvent, SandboxList
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from render.public_api.client import AuthenticatedClient, Client
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SandboxClient:
|
|
19
|
+
"""High-level client for creating and operating Render sandboxes."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
client: AuthenticatedClient | Client,
|
|
24
|
+
default_owner_id: str | None = None,
|
|
25
|
+
default_region: str | None = None,
|
|
26
|
+
):
|
|
27
|
+
self.client = client
|
|
28
|
+
self.api = SandboxApi(client)
|
|
29
|
+
self._default_owner_id = default_owner_id
|
|
30
|
+
self._default_region = default_region
|
|
31
|
+
|
|
32
|
+
def _resolve_owner_id(self, owner_id: str | None) -> str:
|
|
33
|
+
resolved = owner_id or self._default_owner_id
|
|
34
|
+
if not resolved:
|
|
35
|
+
raise RenderError(
|
|
36
|
+
"owner_id is required. Provide it as a parameter or set the RENDER_WORKSPACE_ID environment variable."
|
|
37
|
+
)
|
|
38
|
+
return resolved
|
|
39
|
+
|
|
40
|
+
async def create(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
owner_id: str | None = None,
|
|
44
|
+
plan: str | None = None,
|
|
45
|
+
timeout_seconds: int | None = None,
|
|
46
|
+
network_policy: str | None = None,
|
|
47
|
+
region: str | None = None,
|
|
48
|
+
env: dict[str, str] | None = None,
|
|
49
|
+
) -> Sandbox:
|
|
50
|
+
"""Create a sandbox and return its initial snapshot.
|
|
51
|
+
|
|
52
|
+
All parameters are optional. Unspecified fields fall back to the
|
|
53
|
+
workspace defaults enforced by the API (plan starter, 7200s timeout,
|
|
54
|
+
workspace default region and network policy).
|
|
55
|
+
"""
|
|
56
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
57
|
+
resolved_region = region or self._default_region
|
|
58
|
+
return await self.api.create(
|
|
59
|
+
owner_id=resolved_owner_id,
|
|
60
|
+
plan=plan,
|
|
61
|
+
timeout_seconds=timeout_seconds,
|
|
62
|
+
network_policy=network_policy,
|
|
63
|
+
region=resolved_region,
|
|
64
|
+
env=env,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def from_id(self, sandbox_id: str, *, owner_id: str | None = None) -> Sandbox:
|
|
68
|
+
"""Reconnect to an existing sandbox by id.
|
|
69
|
+
|
|
70
|
+
Raises SandboxNotFoundError if the sandbox does not exist or has been
|
|
71
|
+
terminated.
|
|
72
|
+
"""
|
|
73
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
74
|
+
return await self.api.get(sandbox_id, resolved_owner_id)
|
|
75
|
+
|
|
76
|
+
async def list(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
owner_id: str | None = None,
|
|
80
|
+
status: str | Sequence[str] | None = None,
|
|
81
|
+
cursor: str | None = None,
|
|
82
|
+
limit: int | None = None,
|
|
83
|
+
) -> SandboxList:
|
|
84
|
+
"""List sandboxes for a workspace.
|
|
85
|
+
|
|
86
|
+
status filters by sandbox status, one or a sequence of them (each one
|
|
87
|
+
of creating, running, suspended, resuming, errored, terminated). limit
|
|
88
|
+
is capped at 100 by the API.
|
|
89
|
+
"""
|
|
90
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
91
|
+
return await self.api.list(resolved_owner_id, status, cursor, limit)
|
|
92
|
+
|
|
93
|
+
async def terminate(self, sandbox_id: str, *, owner_id: str | None = None) -> None:
|
|
94
|
+
"""Terminate a sandbox.
|
|
95
|
+
|
|
96
|
+
Idempotent for an already-terminated sandbox (the API returns 204).
|
|
97
|
+
Raises SandboxNotFoundError if the sandbox id was never valid.
|
|
98
|
+
"""
|
|
99
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
100
|
+
await self.api.terminate(sandbox_id, resolved_owner_id)
|
|
101
|
+
|
|
102
|
+
async def copy_to(
|
|
103
|
+
self,
|
|
104
|
+
sandbox_id: str,
|
|
105
|
+
local_path: str | os.PathLike[str],
|
|
106
|
+
remote_path: str,
|
|
107
|
+
*,
|
|
108
|
+
owner_id: str | None = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Copy a local file or directory into the sandbox at remote_path.
|
|
111
|
+
|
|
112
|
+
A relative remote_path resolves under the sandbox's home directory and
|
|
113
|
+
an absolute one addresses the filesystem, as scp does. The path is
|
|
114
|
+
normalized before it is sent, so a trailing slash or a redundant
|
|
115
|
+
separator is accepted rather than rejected by the sandbox.
|
|
116
|
+
|
|
117
|
+
A file is uploaded as raw bytes. A directory is streamed as an archive
|
|
118
|
+
that the sandbox extracts at remote_path: names are relative to
|
|
119
|
+
local_path, symlinks are stored rather than followed, and sockets,
|
|
120
|
+
fifos and other special files are skipped. Passing one of those special
|
|
121
|
+
files as local_path raises ValueError.
|
|
122
|
+
"""
|
|
123
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
124
|
+
await self.api.upload(sandbox_id, local_path, remote_path, resolved_owner_id)
|
|
125
|
+
|
|
126
|
+
def exec(
|
|
127
|
+
self,
|
|
128
|
+
sandbox_id: str,
|
|
129
|
+
command: str,
|
|
130
|
+
*,
|
|
131
|
+
owner_id: str | None = None,
|
|
132
|
+
) -> AsyncIterator[SandboxExecEvent]:
|
|
133
|
+
"""Run a command in a sandbox and stream its output.
|
|
134
|
+
|
|
135
|
+
command is passed to ``bash -c`` in the sandbox. Yields
|
|
136
|
+
SandboxExecOutput chunks as they arrive and a final SandboxExecExit.
|
|
137
|
+
A non-zero exit code is reported via SandboxExecExit, not an exception.
|
|
138
|
+
Raises SandboxExecStreamError if the sandbox reports a terminal error.
|
|
139
|
+
"""
|
|
140
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
141
|
+
return self.api.exec_stream(sandbox_id, command, resolved_owner_id)
|
|
142
|
+
|
|
143
|
+
async def copy_from(
|
|
144
|
+
self,
|
|
145
|
+
sandbox_id: str,
|
|
146
|
+
remote_path: str,
|
|
147
|
+
local_path: str | os.PathLike[str],
|
|
148
|
+
*,
|
|
149
|
+
owner_id: str | None = None,
|
|
150
|
+
) -> str:
|
|
151
|
+
"""Copy a file or directory out of a sandbox, returning the path written.
|
|
152
|
+
|
|
153
|
+
A directory is extracted under local_path. A file is written to
|
|
154
|
+
local_path, or into it under the name the sandbox suggests when
|
|
155
|
+
local_path is an existing directory. remote_path is cleaned before it
|
|
156
|
+
is sent, since the API rejects a path carrying "." , ".." or redundant
|
|
157
|
+
separators. Raises SandboxFileNotFoundError if the sandbox has no such
|
|
158
|
+
path. Raises SandboxDownloadError if the response is unsafe or the download
|
|
159
|
+
cannot be written or extracted locally. Directory extraction is not atomic
|
|
160
|
+
and may leave partial contents after a failure.
|
|
161
|
+
"""
|
|
162
|
+
resolved_owner_id = self._resolve_owner_id(owner_id)
|
|
163
|
+
return await self.api.download_file(
|
|
164
|
+
sandbox_id,
|
|
165
|
+
normalize_remote_path(remote_path),
|
|
166
|
+
os.fspath(local_path),
|
|
167
|
+
resolved_owner_id,
|
|
168
|
+
)
|