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.
Files changed (799) hide show
  1. render/__init__.py +98 -0
  2. render/client/__init__.py +119 -0
  3. render/client/client.py +110 -0
  4. render/client/errors.py +39 -0
  5. render/client/sse.py +90 -0
  6. render/client/sse_sync.py +20 -0
  7. render/client/tests/__init__.py +0 -0
  8. render/client/tests/test_client.py +379 -0
  9. render/client/tests/test_method_parity.py +118 -0
  10. render/client/tests/test_render.py +52 -0
  11. render/client/tests/test_sse.py +67 -0
  12. render/client/tests/test_util.py +303 -0
  13. render/client/tests/test_util_sync.py +73 -0
  14. render/client/tests/test_workflows_sync.py +313 -0
  15. render/client/types.py +52 -0
  16. render/client/util.py +285 -0
  17. render/client/util_sync.py +83 -0
  18. render/client/workflows.py +363 -0
  19. render/client/workflows_sync.py +298 -0
  20. render/experimental/__init__.py +37 -0
  21. render/experimental/experimental.py +93 -0
  22. render/experimental/experimental_sync.py +78 -0
  23. render/experimental/key_value/__init__.py +33 -0
  24. render/experimental/key_value/api.py +243 -0
  25. render/experimental/key_value/compare.py +83 -0
  26. render/experimental/key_value/provider.py +285 -0
  27. render/experimental/key_value/tests/__init__.py +0 -0
  28. render/experimental/key_value/tests/test_api.py +371 -0
  29. render/experimental/key_value/tests/test_compare.py +136 -0
  30. render/experimental/key_value/tests/test_provider.py +1016 -0
  31. render/experimental/key_value/types.py +130 -0
  32. render/experimental/key_value/utils.py +11 -0
  33. render/experimental/object/__init__.py +38 -0
  34. render/experimental/object/api.py +250 -0
  35. render/experimental/object/api_sync.py +250 -0
  36. render/experimental/object/client.py +545 -0
  37. render/experimental/object/client_sync.py +547 -0
  38. render/experimental/object/e2e_helpers.py +78 -0
  39. render/experimental/object/tests/__init__.py +0 -0
  40. render/experimental/object/tests/test_client.py +228 -0
  41. render/experimental/object/tests/test_e2e.py +125 -0
  42. render/experimental/object/types.py +87 -0
  43. render/experimental/sandbox/__init__.py +31 -0
  44. render/experimental/sandbox/_tar.py +278 -0
  45. render/experimental/sandbox/api.py +518 -0
  46. render/experimental/sandbox/api_sync.py +514 -0
  47. render/experimental/sandbox/client.py +168 -0
  48. render/experimental/sandbox/client_sync.py +170 -0
  49. render/experimental/sandbox/errors.py +37 -0
  50. render/experimental/sandbox/files.py +378 -0
  51. render/experimental/sandbox/tests/__init__.py +0 -0
  52. render/experimental/sandbox/tests/test_client.py +802 -0
  53. render/experimental/sandbox/tests/test_download.py +728 -0
  54. render/experimental/sandbox/tests/test_e2e.py +252 -0
  55. render/experimental/sandbox/tests/test_tar.py +153 -0
  56. render/experimental/sandbox/tests/test_types.py +57 -0
  57. render/experimental/sandbox/types.py +46 -0
  58. render/public_api/__init__.py +8 -0
  59. render/public_api/api/__init__.py +1 -0
  60. render/public_api/api/artifact_sources/__init__.py +1 -0
  61. render/public_api/api/artifact_sources/add_or_update_artifact_source_secret_file.py +252 -0
  62. render/public_api/api/artifact_sources/create_artifact_source.py +243 -0
  63. render/public_api/api/artifact_sources/delete_artifact_source.py +188 -0
  64. render/public_api/api/artifact_sources/delete_artifact_source_env_var.py +223 -0
  65. render/public_api/api/artifact_sources/delete_artifact_source_secret_file.py +223 -0
  66. render/public_api/api/artifact_sources/get_artifact_source.py +185 -0
  67. render/public_api/api/artifact_sources/get_env_vars_for_artifact_source.py +241 -0
  68. render/public_api/api/artifact_sources/link_artifact_source_to_env_group.py +215 -0
  69. render/public_api/api/artifact_sources/list_artifact_sources.py +338 -0
  70. render/public_api/api/artifact_sources/list_artifacts_in_artifact_source.py +241 -0
  71. render/public_api/api/artifact_sources/list_secret_files_for_artifact_source.py +241 -0
  72. render/public_api/api/artifact_sources/retrieve_artifact_source_env_var.py +225 -0
  73. render/public_api/api/artifact_sources/retrieve_artifact_source_secret_file.py +225 -0
  74. render/public_api/api/artifact_sources/unlink_artifact_source_from_env_group.py +209 -0
  75. render/public_api/api/artifact_sources/update_artifact_source.py +244 -0
  76. render/public_api/api/artifact_sources/update_artifact_source_env_var.py +257 -0
  77. render/public_api/api/artifact_sources/update_env_vars_for_artifact_source.py +245 -0
  78. render/public_api/api/artifact_sources/update_secret_files_for_artifact_source.py +255 -0
  79. render/public_api/api/audit_logs/__init__.py +1 -0
  80. render/public_api/api/audit_logs/list_organization_audit_logs.py +303 -0
  81. render/public_api/api/audit_logs/list_owner_audit_logs.py +303 -0
  82. render/public_api/api/blueprints/__init__.py +1 -0
  83. render/public_api/api/blueprints/disconnect_blueprint.py +210 -0
  84. render/public_api/api/blueprints/list_blueprint_syncs.py +241 -0
  85. render/public_api/api/blueprints/list_blueprints.py +251 -0
  86. render/public_api/api/blueprints/retrieve_blueprint.py +200 -0
  87. render/public_api/api/blueprints/update_blueprint.py +222 -0
  88. render/public_api/api/blueprints/validate_blueprint.py +217 -0
  89. render/public_api/api/custom_domains/__init__.py +1 -0
  90. render/public_api/api/custom_domains/create_custom_domain.py +242 -0
  91. render/public_api/api/custom_domains/delete_custom_domain.py +216 -0
  92. render/public_api/api/custom_domains/list_custom_domains.py +346 -0
  93. render/public_api/api/custom_domains/refresh_custom_domain.py +216 -0
  94. render/public_api/api/custom_domains/retrieve_custom_domain.py +218 -0
  95. render/public_api/api/dedicated_i_ps/__init__.py +1 -0
  96. render/public_api/api/dedicated_i_ps/create_dedicated_ip.py +234 -0
  97. render/public_api/api/dedicated_i_ps/delete_dedicated_ip.py +198 -0
  98. render/public_api/api/dedicated_i_ps/list_dedicated_ips.py +223 -0
  99. render/public_api/api/dedicated_i_ps/retrieve_dedicated_ip.py +200 -0
  100. render/public_api/api/dedicated_i_ps/update_dedicated_ip.py +244 -0
  101. render/public_api/api/deploys/__init__.py +1 -0
  102. render/public_api/api/deploys/cancel_deploy.py +216 -0
  103. render/public_api/api/deploys/create_deploy.py +231 -0
  104. render/public_api/api/deploys/list_deploys.py +377 -0
  105. render/public_api/api/deploys/retrieve_deploy.py +213 -0
  106. render/public_api/api/deploys/rollback_deploy.py +241 -0
  107. render/public_api/api/disks/__init__.py +1 -0
  108. render/public_api/api/disks/add_disk.py +222 -0
  109. render/public_api/api/disks/delete_disk.py +210 -0
  110. render/public_api/api/disks/list_disks.py +381 -0
  111. render/public_api/api/disks/list_snapshots.py +214 -0
  112. render/public_api/api/disks/restore_snapshot.py +255 -0
  113. render/public_api/api/disks/retrieve_disk.py +200 -0
  114. render/public_api/api/disks/update_disk.py +243 -0
  115. render/public_api/api/environment_groups/__init__.py +1 -0
  116. render/public_api/api/environment_groups/create_env_group.py +199 -0
  117. render/public_api/api/environment_groups/delete_env_group.py +192 -0
  118. render/public_api/api/environment_groups/delete_env_group_env_var.py +201 -0
  119. render/public_api/api/environment_groups/delete_env_group_secret_file.py +201 -0
  120. render/public_api/api/environment_groups/link_service_to_env_group.py +211 -0
  121. render/public_api/api/environment_groups/list_env_groups.py +352 -0
  122. render/public_api/api/environment_groups/retrieve_env_group.py +190 -0
  123. render/public_api/api/environment_groups/retrieve_env_group_env_var.py +203 -0
  124. render/public_api/api/environment_groups/retrieve_env_group_secret_file.py +203 -0
  125. render/public_api/api/environment_groups/unlink_service_from_env_group.py +209 -0
  126. render/public_api/api/environment_groups/update_env_group.py +212 -0
  127. render/public_api/api/environment_groups/update_env_group_env_var.py +230 -0
  128. render/public_api/api/environment_groups/update_env_group_secret_file.py +225 -0
  129. render/public_api/api/events/__init__.py +1 -0
  130. render/public_api/api/events/retrieve_event.py +205 -0
  131. render/public_api/api/key_value/__init__.py +1 -0
  132. render/public_api/api/key_value/create_key_value.py +199 -0
  133. render/public_api/api/key_value/delete_key_value.py +188 -0
  134. render/public_api/api/key_value/list_key_value.py +380 -0
  135. render/public_api/api/key_value/resume_key_value.py +203 -0
  136. render/public_api/api/key_value/retrieve_key_value.py +190 -0
  137. render/public_api/api/key_value/retrieve_key_value_connection_info.py +194 -0
  138. render/public_api/api/key_value/suspend_key_value.py +203 -0
  139. render/public_api/api/key_value/update_key_value.py +221 -0
  140. render/public_api/api/logs/__init__.py +1 -0
  141. render/public_api/api/logs/delete_owner_log_stream.py +188 -0
  142. render/public_api/api/logs/delete_resource_log_stream.py +192 -0
  143. render/public_api/api/logs/get_owner_log_stream.py +205 -0
  144. render/public_api/api/logs/get_resource_log_stream.py +209 -0
  145. render/public_api/api/logs/list_logs.py +535 -0
  146. render/public_api/api/logs/list_logs_values.py +527 -0
  147. render/public_api/api/logs/list_resource_log_streams.py +317 -0
  148. render/public_api/api/logs/subscribe_logs.py +527 -0
  149. render/public_api/api/logs/update_owner_log_stream.py +231 -0
  150. render/public_api/api/logs/update_resource_log_stream.py +231 -0
  151. render/public_api/api/maintenance/__init__.py +1 -0
  152. render/public_api/api/maintenance/list_maintenance.py +249 -0
  153. render/public_api/api/maintenance/retrieve_maintenance.py +190 -0
  154. render/public_api/api/maintenance/trigger_maintenance.py +212 -0
  155. render/public_api/api/maintenance/update_maintenance.py +222 -0
  156. render/public_api/api/metrics/__init__.py +1 -0
  157. render/public_api/api/metrics/delete_owner_metrics_stream.py +172 -0
  158. render/public_api/api/metrics/get_active_connections.py +237 -0
  159. render/public_api/api/metrics/get_bandwidth.py +237 -0
  160. render/public_api/api/metrics/get_bandwidth_sources.py +250 -0
  161. render/public_api/api/metrics/get_cpu.py +287 -0
  162. render/public_api/api/metrics/get_cpu_limit.py +267 -0
  163. render/public_api/api/metrics/get_cpu_target.py +267 -0
  164. render/public_api/api/metrics/get_disk_capacity.py +252 -0
  165. render/public_api/api/metrics/get_disk_usage.py +252 -0
  166. render/public_api/api/metrics/get_http_latency.py +297 -0
  167. render/public_api/api/metrics/get_http_requests.py +302 -0
  168. render/public_api/api/metrics/get_instance_count.py +252 -0
  169. render/public_api/api/metrics/get_memory.py +267 -0
  170. render/public_api/api/metrics/get_memory_limit.py +267 -0
  171. render/public_api/api/metrics/get_memory_target.py +267 -0
  172. render/public_api/api/metrics/get_owner_metrics_stream.py +174 -0
  173. render/public_api/api/metrics/get_replication_lag.py +237 -0
  174. render/public_api/api/metrics/get_task_runs_completed.py +281 -0
  175. render/public_api/api/metrics/get_task_runs_queued.py +237 -0
  176. render/public_api/api/metrics/list_application_filter_values.py +254 -0
  177. render/public_api/api/metrics/list_http_filter_values.py +282 -0
  178. render/public_api/api/metrics/list_path_filter_values.py +289 -0
  179. render/public_api/api/metrics/upsert_owner_metrics_stream.py +192 -0
  180. render/public_api/api/notification_settings/__init__.py +1 -0
  181. render/public_api/api/notification_settings/list_notification_overrides.py +255 -0
  182. render/public_api/api/notification_settings/patch_owner_notification_settings.py +207 -0
  183. render/public_api/api/notification_settings/patch_service_notification_overrides.py +207 -0
  184. render/public_api/api/notification_settings/retrieve_owner_notification_settings.py +193 -0
  185. render/public_api/api/notification_settings/retrieve_service_notification_overrides.py +193 -0
  186. render/public_api/api/object_storage/__init__.py +1 -0
  187. render/public_api/api/object_storage/delete_object.py +215 -0
  188. render/public_api/api/object_storage/get_object.py +221 -0
  189. render/public_api/api/object_storage/list_objects.py +240 -0
  190. render/public_api/api/object_storage/put_object.py +248 -0
  191. render/public_api/api/one_off_jobs/__init__.py +1 -0
  192. render/public_api/api/one_off_jobs/cancel_job.py +203 -0
  193. render/public_api/api/one_off_jobs/list_job.py +367 -0
  194. render/public_api/api/one_off_jobs/post_job.py +216 -0
  195. render/public_api/api/one_off_jobs/retrieve_job.py +203 -0
  196. render/public_api/api/postgres/__init__.py +1 -0
  197. render/public_api/api/postgres/create_postgres.py +199 -0
  198. render/public_api/api/postgres/create_postgres_export.py +207 -0
  199. render/public_api/api/postgres/create_postgres_user.py +229 -0
  200. render/public_api/api/postgres/delete_postgres.py +192 -0
  201. render/public_api/api/postgres/delete_postgres_user.py +201 -0
  202. render/public_api/api/postgres/failover_postgres.py +207 -0
  203. render/public_api/api/postgres/list_available_postgres_parameter_overrides.py +190 -0
  204. render/public_api/api/postgres/list_postgres.py +418 -0
  205. render/public_api/api/postgres/list_postgres_export.py +199 -0
  206. render/public_api/api/postgres/list_postgres_parameter_overrides.py +190 -0
  207. render/public_api/api/postgres/list_postgres_processes.py +190 -0
  208. render/public_api/api/postgres/list_postgres_sizes.py +190 -0
  209. render/public_api/api/postgres/list_postgres_table_scans.py +190 -0
  210. render/public_api/api/postgres/list_postgres_top_queries.py +194 -0
  211. render/public_api/api/postgres/list_postgres_users.py +195 -0
  212. render/public_api/api/postgres/promote_postgres.py +203 -0
  213. render/public_api/api/postgres/recover_postgres.py +231 -0
  214. render/public_api/api/postgres/restart_postgres.py +203 -0
  215. render/public_api/api/postgres/resume_postgres.py +203 -0
  216. render/public_api/api/postgres/retrieve_postgres.py +190 -0
  217. render/public_api/api/postgres/retrieve_postgres_connection_info.py +194 -0
  218. render/public_api/api/postgres/retrieve_postgres_recovery_info.py +194 -0
  219. render/public_api/api/postgres/setup_postgres_replication.py +231 -0
  220. render/public_api/api/postgres/suspend_postgres.py +203 -0
  221. render/public_api/api/postgres/update_postgres.py +217 -0
  222. render/public_api/api/postgres/update_postgres_parameter_overrides.py +217 -0
  223. render/public_api/api/projects_environments/__init__.py +1 -0
  224. render/public_api/api/projects_environments/add_resources_to_environment.py +212 -0
  225. render/public_api/api/projects_environments/create_environment.py +199 -0
  226. render/public_api/api/projects_environments/create_project.py +199 -0
  227. render/public_api/api/projects_environments/delete_environment.py +216 -0
  228. render/public_api/api/projects_environments/delete_project.py +216 -0
  229. render/public_api/api/projects_environments/list_environments.py +369 -0
  230. render/public_api/api/projects_environments/list_projects.py +329 -0
  231. render/public_api/api/projects_environments/remove_resources_from_environment.py +211 -0
  232. render/public_api/api/projects_environments/retrieve_environment.py +190 -0
  233. render/public_api/api/projects_environments/retrieve_project.py +190 -0
  234. render/public_api/api/projects_environments/update_environment.py +212 -0
  235. render/public_api/api/projects_environments/update_project.py +224 -0
  236. render/public_api/api/redis_deprecated/__init__.py +1 -0
  237. render/public_api/api/redis_deprecated/create_redis.py +199 -0
  238. render/public_api/api/redis_deprecated/delete_redis.py +188 -0
  239. render/public_api/api/redis_deprecated/list_redis.py +384 -0
  240. render/public_api/api/redis_deprecated/resume_redis.py +203 -0
  241. render/public_api/api/redis_deprecated/retrieve_redis.py +190 -0
  242. render/public_api/api/redis_deprecated/retrieve_redis_connection_info.py +194 -0
  243. render/public_api/api/redis_deprecated/suspend_redis.py +203 -0
  244. render/public_api/api/redis_deprecated/update_redis.py +217 -0
  245. render/public_api/api/registry_credentials/__init__.py +1 -0
  246. render/public_api/api/registry_credentials/create_registry_credential.py +209 -0
  247. render/public_api/api/registry_credentials/delete_registry_credential.py +198 -0
  248. render/public_api/api/registry_credentials/list_registry_credentials.py +370 -0
  249. render/public_api/api/registry_credentials/retrieve_registry_credential.py +200 -0
  250. render/public_api/api/registry_credentials/update_registry_credential.py +241 -0
  251. render/public_api/api/sandboxes/__init__.py +1 -0
  252. render/public_api/api/sandboxes/connect_sandbox_files.py +281 -0
  253. render/public_api/api/sandboxes/connect_sandbox_run.py +271 -0
  254. render/public_api/api/sandboxes/create_sandbox.py +199 -0
  255. render/public_api/api/sandboxes/list_sandbox_files.py +244 -0
  256. render/public_api/api/sandboxes/list_sandbox_groups.py +216 -0
  257. render/public_api/api/sandboxes/list_sandboxes.py +269 -0
  258. render/public_api/api/sandboxes/retrieve_sandbox.py +211 -0
  259. render/public_api/api/sandboxes/retrieve_sandbox_execution.py +224 -0
  260. render/public_api/api/sandboxes/stream_sandbox_logs.py +274 -0
  261. render/public_api/api/sandboxes/terminate_sandbox.py +217 -0
  262. render/public_api/api/sandboxes/update_sandbox_exec.py +258 -0
  263. render/public_api/api/services/__init__.py +1 -0
  264. render/public_api/api/services/add_or_update_secret_file.py +252 -0
  265. render/public_api/api/services/autoscale_service.py +230 -0
  266. render/public_api/api/services/create_ephemeral_shell.py +217 -0
  267. render/public_api/api/services/create_service.py +204 -0
  268. render/public_api/api/services/delete_autoscaling_config.py +198 -0
  269. render/public_api/api/services/delete_env_var.py +223 -0
  270. render/public_api/api/services/delete_secret_file.py +223 -0
  271. render/public_api/api/services/delete_service.py +198 -0
  272. render/public_api/api/services/get_env_vars_for_service.py +241 -0
  273. render/public_api/api/services/list_events.py +288 -0
  274. render/public_api/api/services/list_instances.py +190 -0
  275. render/public_api/api/services/list_secret_files_for_service.py +241 -0
  276. render/public_api/api/services/list_services.py +439 -0
  277. render/public_api/api/services/preview_service.py +227 -0
  278. render/public_api/api/services/purge_cache.py +203 -0
  279. render/public_api/api/services/restart_service.py +211 -0
  280. render/public_api/api/services/resume_service.py +203 -0
  281. render/public_api/api/services/retrieve_env_var.py +225 -0
  282. render/public_api/api/services/retrieve_secret_file.py +225 -0
  283. render/public_api/api/services/retrieve_service.py +190 -0
  284. render/public_api/api/services/retrieve_service_outbound_ips.py +207 -0
  285. render/public_api/api/services/scale_service.py +237 -0
  286. render/public_api/api/services/suspend_service.py +203 -0
  287. render/public_api/api/services/update_env_var.py +257 -0
  288. render/public_api/api/services/update_env_vars_for_service.py +241 -0
  289. render/public_api/api/services/update_secret_files_for_service.py +255 -0
  290. render/public_api/api/services/update_service.py +227 -0
  291. render/public_api/api/services_cron_jobs/__init__.py +1 -0
  292. render/public_api/api/services_cron_jobs/cancel_cron_job_run.py +183 -0
  293. render/public_api/api/services_cron_jobs/run_cron_job.py +185 -0
  294. render/public_api/api/services_headers/__init__.py +1 -0
  295. render/public_api/api/services_headers/add_headers.py +222 -0
  296. render/public_api/api/services_headers/delete_header.py +211 -0
  297. render/public_api/api/services_headers/list_headers.py +302 -0
  298. render/public_api/api/services_headers/update_headers.py +243 -0
  299. render/public_api/api/services_routes/__init__.py +1 -0
  300. render/public_api/api/services_routes/add_route.py +227 -0
  301. render/public_api/api/services_routes/delete_route.py +211 -0
  302. render/public_api/api/services_routes/list_routes.py +306 -0
  303. render/public_api/api/services_routes/patch_route.py +259 -0
  304. render/public_api/api/services_routes/put_routes.py +259 -0
  305. render/public_api/api/users/__init__.py +1 -0
  306. render/public_api/api/users/get_user.py +161 -0
  307. render/public_api/api/webhooks/__init__.py +1 -0
  308. render/public_api/api/webhooks/create_webhook.py +199 -0
  309. render/public_api/api/webhooks/delete_webhook.py +183 -0
  310. render/public_api/api/webhooks/list_webhook_events.py +268 -0
  311. render/public_api/api/webhooks/list_webhooks.py +237 -0
  312. render/public_api/api/webhooks/retrieve_webhook.py +185 -0
  313. render/public_api/api/webhooks/update_webhook.py +212 -0
  314. render/public_api/api/workflow_tasks_beta/__init__.py +1 -0
  315. render/public_api/api/workflow_tasks_beta/cancel_task_run.py +188 -0
  316. render/public_api/api/workflow_tasks_beta/create_task.py +204 -0
  317. render/public_api/api/workflow_tasks_beta/get_task.py +190 -0
  318. render/public_api/api/workflow_tasks_beta/get_task_run.py +190 -0
  319. render/public_api/api/workflow_tasks_beta/list_task_runs.py +317 -0
  320. render/public_api/api/workflow_tasks_beta/list_tasks.py +298 -0
  321. render/public_api/api/workflow_tasks_beta/stream_task_runs_events.py +230 -0
  322. render/public_api/api/workflows_beta/__init__.py +1 -0
  323. render/public_api/api/workflows_beta/create_workflow.py +199 -0
  324. render/public_api/api/workflows_beta/create_workflow_version.py +197 -0
  325. render/public_api/api/workflows_beta/delete_workflow.py +188 -0
  326. render/public_api/api/workflows_beta/get_workflow.py +190 -0
  327. render/public_api/api/workflows_beta/get_workflow_version.py +190 -0
  328. render/public_api/api/workflows_beta/list_workflow_versions.py +275 -0
  329. render/public_api/api/workflows_beta/list_workflows.py +298 -0
  330. render/public_api/api/workflows_beta/update_workflow.py +212 -0
  331. render/public_api/api/workspaces/__init__.py +1 -0
  332. render/public_api/api/workspaces/list_owners.py +255 -0
  333. render/public_api/api/workspaces/remove_workspace_member.py +206 -0
  334. render/public_api/api/workspaces/retrieve_owner.py +207 -0
  335. render/public_api/api/workspaces/retrieve_owner_members.py +200 -0
  336. render/public_api/api/workspaces/update_workspace_member.py +235 -0
  337. render/public_api/client.py +268 -0
  338. render/public_api/errors.py +16 -0
  339. render/public_api/models/__init__.py +861 -0
  340. render/public_api/models/add_headers_response_201.py +72 -0
  341. render/public_api/models/add_or_update_artifact_source_secret_file_body.py +59 -0
  342. render/public_api/models/add_or_update_secret_file_body.py +59 -0
  343. render/public_api/models/artifact.py +145 -0
  344. render/public_api/models/artifact_fetch_failed.py +67 -0
  345. render/public_api/models/artifact_source.py +162 -0
  346. render/public_api/models/artifact_source_changed.py +70 -0
  347. render/public_api/models/artifact_source_git.py +167 -0
  348. render/public_api/models/artifact_source_git_region.py +12 -0
  349. render/public_api/models/artifact_source_git_runtime.py +14 -0
  350. render/public_api/models/artifact_source_image.py +78 -0
  351. render/public_api/models/artifact_source_patch_git.py +175 -0
  352. render/public_api/models/artifact_source_patch_git_region.py +12 -0
  353. render/public_api/models/artifact_source_patch_git_runtime.py +14 -0
  354. render/public_api/models/artifact_source_patch_image.py +71 -0
  355. render/public_api/models/artifact_source_patch_input.py +101 -0
  356. render/public_api/models/artifact_source_post_input.py +213 -0
  357. render/public_api/models/artifact_source_service_link.py +67 -0
  358. render/public_api/models/artifact_source_with_cursor.py +73 -0
  359. render/public_api/models/audit_log.py +113 -0
  360. render/public_api/models/audit_log_actor.py +80 -0
  361. render/public_api/models/audit_log_actor_type.py +10 -0
  362. render/public_api/models/audit_log_event.py +87 -0
  363. render/public_api/models/audit_log_metadata.py +49 -0
  364. render/public_api/models/audit_log_status.py +9 -0
  365. render/public_api/models/audit_log_with_cursor.py +73 -0
  366. render/public_api/models/auto_deploy.py +9 -0
  367. render/public_api/models/auto_deploy_disabled.py +79 -0
  368. render/public_api/models/auto_deploy_enabled.py +68 -0
  369. render/public_api/models/auto_deploy_trigger.py +10 -0
  370. render/public_api/models/autoscaling_config.py +89 -0
  371. render/public_api/models/autoscaling_config_changed.py +83 -0
  372. render/public_api/models/autoscaling_criteria.py +73 -0
  373. render/public_api/models/autoscaling_criteria_percentage.py +68 -0
  374. render/public_api/models/autoscaling_ended.py +67 -0
  375. render/public_api/models/autoscaling_started.py +105 -0
  376. render/public_api/models/background_worker_details.py +256 -0
  377. render/public_api/models/background_worker_details_patch.py +184 -0
  378. render/public_api/models/background_worker_details_post.py +266 -0
  379. render/public_api/models/blueprint.py +128 -0
  380. render/public_api/models/blueprint_detail.py +150 -0
  381. render/public_api/models/blueprint_patch.py +77 -0
  382. render/public_api/models/blueprint_with_cursor.py +74 -0
  383. render/public_api/models/branch_deleted.py +67 -0
  384. render/public_api/models/build.py +148 -0
  385. render/public_api/models/build_config.py +96 -0
  386. render/public_api/models/build_deploy_end_reason.py +122 -0
  387. render/public_api/models/build_deploy_end_reason_id.py +59 -0
  388. render/public_api/models/build_deploy_trigger.py +150 -0
  389. render/public_api/models/build_ended.py +91 -0
  390. render/public_api/models/build_filter.py +67 -0
  391. render/public_api/models/build_plan.py +9 -0
  392. render/public_api/models/build_runtime.py +14 -0
  393. render/public_api/models/build_started.py +73 -0
  394. render/public_api/models/build_status.py +13 -0
  395. render/public_api/models/cache.py +61 -0
  396. render/public_api/models/cache_profile.py +10 -0
  397. render/public_api/models/cidr_block_and_description.py +67 -0
  398. render/public_api/models/commit_ignored.py +67 -0
  399. render/public_api/models/commit_ref.py +59 -0
  400. render/public_api/models/connect_sandbox_files_operation.py +9 -0
  401. render/public_api/models/connect_sandbox_run_operation.py +9 -0
  402. render/public_api/models/create_custom_domain_body.py +59 -0
  403. render/public_api/models/create_deploy_body.py +130 -0
  404. render/public_api/models/create_deploy_body_clear_cache.py +9 -0
  405. render/public_api/models/create_ephemeral_shell_body.py +69 -0
  406. render/public_api/models/create_ephemeral_shell_response_201.py +59 -0
  407. render/public_api/models/create_registry_credential_body.py +93 -0
  408. render/public_api/models/create_version.py +70 -0
  409. render/public_api/models/credential_create_input.py +59 -0
  410. render/public_api/models/cron_job_details.py +163 -0
  411. render/public_api/models/cron_job_details_patch.py +138 -0
  412. render/public_api/models/cron_job_details_post.py +172 -0
  413. render/public_api/models/cron_job_run.py +123 -0
  414. render/public_api/models/cron_job_run_ended.py +110 -0
  415. render/public_api/models/cron_job_run_started.py +59 -0
  416. render/public_api/models/cron_job_run_status.py +11 -0
  417. render/public_api/models/custom_domain.py +135 -0
  418. render/public_api/models/custom_domain_domain_type.py +9 -0
  419. render/public_api/models/custom_domain_server.py +68 -0
  420. render/public_api/models/custom_domain_verification_status.py +9 -0
  421. render/public_api/models/custom_domain_with_cursor.py +73 -0
  422. render/public_api/models/database_role.py +9 -0
  423. render/public_api/models/database_status.py +18 -0
  424. render/public_api/models/dedicated_ip.py +146 -0
  425. render/public_api/models/dedicated_ip_status.py +14 -0
  426. render/public_api/models/dedicated_ippatch.py +81 -0
  427. render/public_api/models/dedicated_ippost.py +100 -0
  428. render/public_api/models/deploy.py +211 -0
  429. render/public_api/models/deploy_commit.py +86 -0
  430. render/public_api/models/deploy_ended.py +102 -0
  431. render/public_api/models/deploy_image.py +78 -0
  432. render/public_api/models/deploy_mode.py +9 -0
  433. render/public_api/models/deploy_started.py +85 -0
  434. render/public_api/models/deploy_status.py +18 -0
  435. render/public_api/models/deploy_trigger.py +17 -0
  436. render/public_api/models/deploy_with_cursor.py +81 -0
  437. render/public_api/models/disk.py +83 -0
  438. render/public_api/models/disk_created.py +67 -0
  439. render/public_api/models/disk_deleted.py +59 -0
  440. render/public_api/models/disk_details.py +112 -0
  441. render/public_api/models/disk_patch.py +77 -0
  442. render/public_api/models/disk_post.py +83 -0
  443. render/public_api/models/disk_snapshot.py +88 -0
  444. render/public_api/models/disk_updated.py +75 -0
  445. render/public_api/models/disk_with_cursor.py +73 -0
  446. render/public_api/models/docker_details.py +108 -0
  447. render/public_api/models/docker_details_patch.py +86 -0
  448. render/public_api/models/docker_details_post.py +86 -0
  449. render/public_api/models/edge_cache_disabled.py +65 -0
  450. render/public_api/models/edge_cache_enabled.py +65 -0
  451. render/public_api/models/edge_cache_purged.py +65 -0
  452. render/public_api/models/edge_cache_trigger.py +91 -0
  453. render/public_api/models/env_group.py +162 -0
  454. render/public_api/models/env_group_link.py +77 -0
  455. render/public_api/models/env_group_meta.py +126 -0
  456. render/public_api/models/env_group_patch_input.py +59 -0
  457. render/public_api/models/env_group_post_input.py +161 -0
  458. render/public_api/models/env_var.py +67 -0
  459. render/public_api/models/env_var_generate_value.py +59 -0
  460. render/public_api/models/env_var_key_generate_value.py +68 -0
  461. render/public_api/models/env_var_key_value.py +67 -0
  462. render/public_api/models/env_var_value.py +59 -0
  463. render/public_api/models/env_var_with_cursor.py +73 -0
  464. render/public_api/models/environment.py +152 -0
  465. render/public_api/models/environment_patch_input.py +112 -0
  466. render/public_api/models/environment_post_input.py +122 -0
  467. render/public_api/models/environment_resources_post_input.py +59 -0
  468. render/public_api/models/environment_with_cursor.py +74 -0
  469. render/public_api/models/error.py +79 -0
  470. render/public_api/models/error_code.py +10 -0
  471. render/public_api/models/event.py +1122 -0
  472. render/public_api/models/event_status.py +10 -0
  473. render/public_api/models/event_type.py +74 -0
  474. render/public_api/models/execution.py +144 -0
  475. render/public_api/models/execution_operation.py +11 -0
  476. render/public_api/models/execution_type.py +9 -0
  477. render/public_api/models/failure_reason.py +138 -0
  478. render/public_api/models/filter_application_values_collection_item.py +78 -0
  479. render/public_api/models/filter_application_values_collection_item_filter.py +8 -0
  480. render/public_api/models/filter_http_values_collection_item.py +78 -0
  481. render/public_api/models/filter_http_values_collection_item_filter.py +9 -0
  482. render/public_api/models/get_bandwidth_sources_response_200.py +75 -0
  483. render/public_api/models/get_bandwidth_sources_response_200_data_item.py +101 -0
  484. render/public_api/models/get_bandwidth_sources_response_200_data_item_labels.py +78 -0
  485. render/public_api/models/get_bandwidth_sources_response_200_data_item_labels_traffic_source.py +12 -0
  486. render/public_api/models/get_bandwidth_sources_response_200_data_item_values_item.py +68 -0
  487. render/public_api/models/get_cpu_aggregation_method.py +10 -0
  488. render/public_api/models/get_http_requests_aggregate_by.py +9 -0
  489. render/public_api/models/get_object_output.py +71 -0
  490. render/public_api/models/get_task_runs_completed_aggregate_by.py +8 -0
  491. render/public_api/models/get_task_runs_completed_state.py +9 -0
  492. render/public_api/models/header.py +83 -0
  493. render/public_api/models/header_input.py +76 -0
  494. render/public_api/models/header_with_cursor.py +73 -0
  495. render/public_api/models/image.py +80 -0
  496. render/public_api/models/image_pull_failed.py +67 -0
  497. render/public_api/models/initial_deploy_hook_ended.py +59 -0
  498. render/public_api/models/initial_deploy_hook_started.py +59 -0
  499. render/public_api/models/instance_count_changed.py +67 -0
  500. render/public_api/models/instance_type_changed.py +67 -0
  501. render/public_api/models/internal_routing.py +8 -0
  502. render/public_api/models/job.py +144 -0
  503. render/public_api/models/job_run_ended.py +92 -0
  504. render/public_api/models/job_status.py +12 -0
  505. render/public_api/models/job_with_cursor.py +73 -0
  506. render/public_api/models/key_value.py +182 -0
  507. render/public_api/models/key_value_available.py +44 -0
  508. render/public_api/models/key_value_config_restart.py +67 -0
  509. render/public_api/models/key_value_connection_info.py +76 -0
  510. render/public_api/models/key_value_detail.py +192 -0
  511. render/public_api/models/key_value_options.py +79 -0
  512. render/public_api/models/key_value_patch_input.py +138 -0
  513. render/public_api/models/key_value_plan.py +13 -0
  514. render/public_api/models/key_value_post_input.py +166 -0
  515. render/public_api/models/key_value_unhealthy.py +44 -0
  516. render/public_api/models/key_value_with_cursor.py +73 -0
  517. render/public_api/models/label.py +68 -0
  518. render/public_api/models/list_custom_domains_domain_type.py +9 -0
  519. render/public_api/models/list_custom_domains_verification_status.py +9 -0
  520. render/public_api/models/list_logs_response_200.py +102 -0
  521. render/public_api/models/list_logs_values_label.py +13 -0
  522. render/public_api/models/list_objects_response.py +92 -0
  523. render/public_api/models/list_postgres_suspended_item.py +9 -0
  524. render/public_api/models/list_postgres_users_response_200_item.py +86 -0
  525. render/public_api/models/list_routes_type_item.py +9 -0
  526. render/public_api/models/list_services_suspended_item.py +9 -0
  527. render/public_api/models/log.py +100 -0
  528. render/public_api/models/log_direction.py +9 -0
  529. render/public_api/models/log_label.py +70 -0
  530. render/public_api/models/log_label_name.py +22 -0
  531. render/public_api/models/log_stream_owner_update.py +80 -0
  532. render/public_api/models/log_stream_preview_setting.py +9 -0
  533. render/public_api/models/log_stream_resource_update.py +80 -0
  534. render/public_api/models/log_stream_setting.py +9 -0
  535. render/public_api/models/maintenance_ended.py +44 -0
  536. render/public_api/models/maintenance_mode.py +68 -0
  537. render/public_api/models/maintenance_mode_enabled.py +59 -0
  538. render/public_api/models/maintenance_mode_uri_updated.py +67 -0
  539. render/public_api/models/maintenance_run.py +105 -0
  540. render/public_api/models/maintenance_run_patch.py +69 -0
  541. render/public_api/models/maintenance_run_with_resource.py +114 -0
  542. render/public_api/models/maintenance_started.py +65 -0
  543. render/public_api/models/maintenance_state.py +13 -0
  544. render/public_api/models/maintenance_trigger.py +91 -0
  545. render/public_api/models/maxmemory_policy.py +15 -0
  546. render/public_api/models/metrics_stream.py +77 -0
  547. render/public_api/models/metrics_stream_input.py +86 -0
  548. render/public_api/models/native_environment_details.py +78 -0
  549. render/public_api/models/native_environment_details_patch.py +68 -0
  550. render/public_api/models/native_environment_details_post.py +68 -0
  551. render/public_api/models/notification_override.py +78 -0
  552. render/public_api/models/notification_override_with_cursor.py +73 -0
  553. render/public_api/models/notification_service_override.py +78 -0
  554. render/public_api/models/notification_service_override_patch.py +84 -0
  555. render/public_api/models/notification_setting.py +93 -0
  556. render/public_api/models/notification_setting_patch.py +85 -0
  557. render/public_api/models/notify_override.py +11 -0
  558. render/public_api/models/notify_preview_override.py +10 -0
  559. render/public_api/models/notify_setting.py +10 -0
  560. render/public_api/models/notify_setting_v2.py +10 -0
  561. render/public_api/models/object_metadata.py +77 -0
  562. render/public_api/models/object_with_cursor.py +73 -0
  563. render/public_api/models/oom_killed.py +59 -0
  564. render/public_api/models/otel_provider_type.py +16 -0
  565. render/public_api/models/outbound_ips.py +81 -0
  566. render/public_api/models/outbound_ips_type.py +9 -0
  567. render/public_api/models/owner.py +121 -0
  568. render/public_api/models/owner_log_stream_setting.py +86 -0
  569. render/public_api/models/owner_type.py +9 -0
  570. render/public_api/models/owner_with_cursor.py +81 -0
  571. render/public_api/models/paid_plan.py +13 -0
  572. render/public_api/models/patch_route_response_200.py +72 -0
  573. render/public_api/models/persistence_mode.py +10 -0
  574. render/public_api/models/pipeline_minutes_exhausted.py +73 -0
  575. render/public_api/models/plan.py +22 -0
  576. render/public_api/models/post_job_body.py +70 -0
  577. render/public_api/models/postgres.py +300 -0
  578. render/public_api/models/postgres_available.py +44 -0
  579. render/public_api/models/postgres_available_parameter_override.py +106 -0
  580. render/public_api/models/postgres_available_parameter_overrides_result.py +75 -0
  581. render/public_api/models/postgres_backup_completed.py +44 -0
  582. render/public_api/models/postgres_backup_failed.py +44 -0
  583. render/public_api/models/postgres_backup_started.py +44 -0
  584. render/public_api/models/postgres_cluster_leader_changed.py +59 -0
  585. render/public_api/models/postgres_connection_info.py +103 -0
  586. render/public_api/models/postgres_connection_pool_changed.py +91 -0
  587. render/public_api/models/postgres_connection_pool_enabled_changed.py +83 -0
  588. render/public_api/models/postgres_created.py +72 -0
  589. render/public_api/models/postgres_detail.py +336 -0
  590. render/public_api/models/postgres_detail_suspended.py +9 -0
  591. render/public_api/models/postgres_disk_size_changed.py +91 -0
  592. render/public_api/models/postgres_export.py +80 -0
  593. render/public_api/models/postgres_ha_status_changed.py +67 -0
  594. render/public_api/models/postgres_parameter_overrides.py +44 -0
  595. render/public_api/models/postgres_parameter_overrides_wrapper.py +65 -0
  596. render/public_api/models/postgres_patch_input.py +199 -0
  597. render/public_api/models/postgres_pitr_checkpoint_completed.py +44 -0
  598. render/public_api/models/postgres_pitr_checkpoint_failed.py +44 -0
  599. render/public_api/models/postgres_pitr_checkpoint_started.py +44 -0
  600. render/public_api/models/postgres_plans.py +35 -0
  601. render/public_api/models/postgres_post_input.py +254 -0
  602. render/public_api/models/postgres_process.py +185 -0
  603. render/public_api/models/postgres_processes_result.py +73 -0
  604. render/public_api/models/postgres_put_parameter_overrides_result.py +82 -0
  605. render/public_api/models/postgres_query_statistic.py +222 -0
  606. render/public_api/models/postgres_read_replica_stale.py +44 -0
  607. render/public_api/models/postgres_read_replicas_changed.py +67 -0
  608. render/public_api/models/postgres_replication_setup_input.py +96 -0
  609. render/public_api/models/postgres_restarted.py +72 -0
  610. render/public_api/models/postgres_size.py +96 -0
  611. render/public_api/models/postgres_sizes_result.py +73 -0
  612. render/public_api/models/postgres_suspended.py +9 -0
  613. render/public_api/models/postgres_table_scan.py +87 -0
  614. render/public_api/models/postgres_table_scans_result.py +73 -0
  615. render/public_api/models/postgres_top_queries_result.py +73 -0
  616. render/public_api/models/postgres_unavailable.py +44 -0
  617. render/public_api/models/postgres_upgrade_failed.py +67 -0
  618. render/public_api/models/postgres_upgrade_started.py +91 -0
  619. render/public_api/models/postgres_upgrade_succeeded.py +67 -0
  620. render/public_api/models/postgres_version.py +15 -0
  621. render/public_api/models/postgres_with_cursor.py +73 -0
  622. render/public_api/models/pre_deploy_ended.py +99 -0
  623. render/public_api/models/pre_deploy_started.py +67 -0
  624. render/public_api/models/preview_input.py +94 -0
  625. render/public_api/models/previews.py +67 -0
  626. render/public_api/models/previews_generation.py +10 -0
  627. render/public_api/models/private_service_details.py +282 -0
  628. render/public_api/models/private_service_details_patch.py +184 -0
  629. render/public_api/models/private_service_details_post.py +266 -0
  630. render/public_api/models/project.py +108 -0
  631. render/public_api/models/project_patch_input.py +60 -0
  632. render/public_api/models/project_post_environment_input.py +114 -0
  633. render/public_api/models/project_post_input.py +89 -0
  634. render/public_api/models/project_with_cursor.py +73 -0
  635. render/public_api/models/protected_status.py +9 -0
  636. render/public_api/models/pull_request_previews_enabled.py +9 -0
  637. render/public_api/models/put_object_input.py +59 -0
  638. render/public_api/models/put_object_output.py +79 -0
  639. render/public_api/models/read_replica.py +91 -0
  640. render/public_api/models/read_replica_input.py +83 -0
  641. render/public_api/models/recovery_info.py +80 -0
  642. render/public_api/models/recovery_info_recovery_status.py +10 -0
  643. render/public_api/models/recovery_input.py +114 -0
  644. render/public_api/models/redis.py +182 -0
  645. render/public_api/models/redis_connection_info.py +76 -0
  646. render/public_api/models/redis_detail.py +192 -0
  647. render/public_api/models/redis_options.py +79 -0
  648. render/public_api/models/redis_patch_input.py +138 -0
  649. render/public_api/models/redis_plan.py +13 -0
  650. render/public_api/models/redis_post_input.py +166 -0
  651. render/public_api/models/redis_with_cursor.py +73 -0
  652. render/public_api/models/region.py +12 -0
  653. render/public_api/models/registry_credential.py +95 -0
  654. render/public_api/models/registry_credential_registry.py +12 -0
  655. render/public_api/models/registry_credential_summary.py +67 -0
  656. render/public_api/models/render_subdomain_policy.py +9 -0
  657. render/public_api/models/resource.py +67 -0
  658. render/public_api/models/resource_log_stream_setting.py +87 -0
  659. render/public_api/models/resource_ref.py +77 -0
  660. render/public_api/models/resource_ref_type.py +17 -0
  661. render/public_api/models/rollback_deploy_body.py +59 -0
  662. render/public_api/models/route.py +93 -0
  663. render/public_api/models/route_patch.py +60 -0
  664. render/public_api/models/route_post.py +88 -0
  665. render/public_api/models/route_put.py +77 -0
  666. render/public_api/models/route_type.py +9 -0
  667. render/public_api/models/route_with_cursor.py +73 -0
  668. render/public_api/models/run_task.py +96 -0
  669. render/public_api/models/runtime.py +12 -0
  670. render/public_api/models/sandbox.py +149 -0
  671. render/public_api/models/sandbox_connect_request.py +62 -0
  672. render/public_api/models/sandbox_connect_response.py +98 -0
  673. render/public_api/models/sandbox_directory_listing.py +82 -0
  674. render/public_api/models/sandbox_exec_update_request.py +60 -0
  675. render/public_api/models/sandbox_exec_update_response.py +130 -0
  676. render/public_api/models/sandbox_file_entry.py +98 -0
  677. render/public_api/models/sandbox_file_entry_type.py +10 -0
  678. render/public_api/models/sandbox_group.py +141 -0
  679. render/public_api/models/sandbox_group_with_cursor.py +74 -0
  680. render/public_api/models/sandbox_network_policy.py +61 -0
  681. render/public_api/models/sandbox_network_policy_default.py +9 -0
  682. render/public_api/models/sandbox_plan.py +10 -0
  683. render/public_api/models/sandbox_post.py +138 -0
  684. render/public_api/models/sandbox_post_env.py +44 -0
  685. render/public_api/models/sandbox_status.py +13 -0
  686. render/public_api/models/sandbox_with_cursor.py +74 -0
  687. render/public_api/models/scale_service_body.py +59 -0
  688. render/public_api/models/schemas_build_filter.py +70 -0
  689. render/public_api/models/schemas_image.py +96 -0
  690. render/public_api/models/schemas_user.py +68 -0
  691. render/public_api/models/secret_file.py +67 -0
  692. render/public_api/models/secret_file_input.py +67 -0
  693. render/public_api/models/secret_file_with_cursor.py +73 -0
  694. render/public_api/models/server_available.py +44 -0
  695. render/public_api/models/server_failed.py +81 -0
  696. render/public_api/models/server_hardware_failure.py +44 -0
  697. render/public_api/models/server_port.py +69 -0
  698. render/public_api/models/server_port_protocol.py +9 -0
  699. render/public_api/models/server_restarted.py +66 -0
  700. render/public_api/models/service_disk.py +78 -0
  701. render/public_api/models/service_env.py +15 -0
  702. render/public_api/models/service_event.py +770 -0
  703. render/public_api/models/service_event_type.py +48 -0
  704. render/public_api/models/service_event_with_cursor.py +66 -0
  705. render/public_api/models/service_instance.py +69 -0
  706. render/public_api/models/service_patch.py +273 -0
  707. render/public_api/models/service_post.py +357 -0
  708. render/public_api/models/service_resumed.py +44 -0
  709. render/public_api/models/service_runtime.py +15 -0
  710. render/public_api/models/service_suspended.py +44 -0
  711. render/public_api/models/service_type.py +12 -0
  712. render/public_api/models/service_type_short.py +12 -0
  713. render/public_api/models/setup_postgres_replication_response_201.py +44 -0
  714. render/public_api/models/snapshot_restore_post.py +72 -0
  715. render/public_api/models/static_site_details.py +183 -0
  716. render/public_api/models/static_site_details_patch.py +147 -0
  717. render/public_api/models/static_site_details_post.py +189 -0
  718. render/public_api/models/status.py +12 -0
  719. render/public_api/models/stream_sandbox_logs_accept.py +8 -0
  720. render/public_api/models/stream_task_runs_events_accept.py +8 -0
  721. render/public_api/models/suspender_added.py +83 -0
  722. render/public_api/models/suspender_removed.py +83 -0
  723. render/public_api/models/suspender_type.py +14 -0
  724. render/public_api/models/sync.py +118 -0
  725. render/public_api/models/sync_state.py +12 -0
  726. render/public_api/models/sync_with_cursor.py +74 -0
  727. render/public_api/models/task.py +97 -0
  728. render/public_api/models/task_attempt.py +121 -0
  729. render/public_api/models/task_attempt_details.py +141 -0
  730. render/public_api/models/task_data_type_1.py +44 -0
  731. render/public_api/models/task_run.py +168 -0
  732. render/public_api/models/task_run_details.py +216 -0
  733. render/public_api/models/task_run_status.py +14 -0
  734. render/public_api/models/task_run_with_cursor.py +73 -0
  735. render/public_api/models/task_with_cursor.py +73 -0
  736. render/public_api/models/team_member.py +102 -0
  737. render/public_api/models/team_member_role.py +12 -0
  738. render/public_api/models/team_member_status.py +9 -0
  739. render/public_api/models/time_series.py +100 -0
  740. render/public_api/models/time_series_value.py +70 -0
  741. render/public_api/models/update_env_group_secret_file_body.py +59 -0
  742. render/public_api/models/update_registry_credential_body.py +85 -0
  743. render/public_api/models/update_workspace_member_body.py +61 -0
  744. render/public_api/models/user.py +67 -0
  745. render/public_api/models/validate_blueprint_request.py +84 -0
  746. render/public_api/models/validate_blueprint_response.py +105 -0
  747. render/public_api/models/validation_error.py +88 -0
  748. render/public_api/models/validation_plan_summary.py +107 -0
  749. render/public_api/models/web_service_details.py +385 -0
  750. render/public_api/models/web_service_details_patch.py +270 -0
  751. render/public_api/models/web_service_details_post.py +354 -0
  752. render/public_api/models/webhook.py +110 -0
  753. render/public_api/models/webhook_event.py +115 -0
  754. render/public_api/models/webhook_event_with_cursor.py +73 -0
  755. render/public_api/models/webhook_patch_input.py +98 -0
  756. render/public_api/models/webhook_post_input.py +102 -0
  757. render/public_api/models/webhook_with_cursor.py +73 -0
  758. render/public_api/models/workflow.py +163 -0
  759. render/public_api/models/workflow_create.py +169 -0
  760. render/public_api/models/workflow_update.py +109 -0
  761. render/public_api/models/workflow_version.py +95 -0
  762. render/public_api/models/workflow_version_status.py +13 -0
  763. render/public_api/models/workflow_version_with_cursor.py +73 -0
  764. render/public_api/models/workflow_with_cursor.py +73 -0
  765. render/public_api/models/zero_downtime_redeploy_ended.py +44 -0
  766. render/public_api/models/zero_downtime_redeploy_started.py +59 -0
  767. render/public_api/py.typed +1 -0
  768. render/public_api/types.py +54 -0
  769. render/py.typed +0 -0
  770. render/render.py +81 -0
  771. render/render_async.py +80 -0
  772. render/version.py +27 -0
  773. render/workflows/__init__.py +25 -0
  774. render/workflows/_callback_models.py +194 -0
  775. render/workflows/_uds_http.py +128 -0
  776. render/workflows/app.py +263 -0
  777. render/workflows/cli.py +62 -0
  778. render/workflows/client.py +354 -0
  779. render/workflows/context.py +75 -0
  780. render/workflows/executor.py +86 -0
  781. render/workflows/runner.py +174 -0
  782. render/workflows/task.py +348 -0
  783. render/workflows/tests/__init__.py +0 -0
  784. render/workflows/tests/test_app.py +343 -0
  785. render/workflows/tests/test_cli.py +134 -0
  786. render/workflows/tests/test_client.py +59 -0
  787. render/workflows/tests/test_context.py +118 -0
  788. render/workflows/tests/test_end_to_end.py +307 -0
  789. render/workflows/tests/test_executor.py +221 -0
  790. render/workflows/tests/test_lazy_imports.py +100 -0
  791. render/workflows/tests/test_registration.py +293 -0
  792. render/workflows/tests/test_translate_errors.py +126 -0
  793. render/workflows/tests/test_uds_http.py +199 -0
  794. render/workflows/tests/test_version.py +15 -0
  795. render-1.0.1.dist-info/METADATA +382 -0
  796. render-1.0.1.dist-info/RECORD +799 -0
  797. render-1.0.1.dist-info/WHEEL +4 -0
  798. render-1.0.1.dist-info/entry_points.txt +2 -0
  799. render-1.0.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,728 @@
1
+ """Tests for sandbox downloads: the copy_from path and its filesystem half."""
2
+
3
+ import gzip
4
+ import io
5
+ import os
6
+ import stat
7
+ import tarfile
8
+ import threading
9
+
10
+ import httpx
11
+ import pytest
12
+
13
+ from render.client.errors import ClientError, RenderError
14
+ from render.experimental.sandbox.client import SandboxClient
15
+ from render.experimental.sandbox.errors import (
16
+ SandboxDownloadError,
17
+ SandboxFileNotFoundError,
18
+ SandboxNotFoundError,
19
+ )
20
+ from render.experimental.sandbox.files import (
21
+ base_name,
22
+ check_content_encoding,
23
+ disposition_filename,
24
+ extract_tar,
25
+ media_type,
26
+ normalize_remote_path,
27
+ resolve_file_dest,
28
+ )
29
+ from render.public_api.client import AuthenticatedClient
30
+
31
+ CONNECT_JSON = {
32
+ "token": "file-token-xyz",
33
+ "uri": "https://proxy.test/files/download",
34
+ "method": "GET",
35
+ "expiresAt": "2026-07-21T00:05:00Z",
36
+ }
37
+
38
+
39
+ def _tar(entries, *, terminated=True):
40
+ """Build a tar archive from (name, kind, payload, mode) tuples.
41
+
42
+ payload is bytes for a regular file and a link target for a link. With
43
+ terminated False the end-of-archive marker is cut off, which is what a
44
+ transfer interrupted on a block boundary looks like.
45
+ """
46
+ buf = io.BytesIO()
47
+ with tarfile.open(fileobj=buf, mode="w") as archive:
48
+ for name, kind, payload, mode in entries:
49
+ info = tarfile.TarInfo(name)
50
+ info.type = kind
51
+ info.mode = mode
52
+ if kind == tarfile.REGTYPE:
53
+ info.size = len(payload)
54
+ archive.addfile(info, io.BytesIO(payload))
55
+ else:
56
+ info.linkname = payload if isinstance(payload, str) else ""
57
+ archive.addfile(info)
58
+ data = buf.getvalue()
59
+ body = data.rstrip(b"\x00")
60
+ padded = body + b"\x00" * (-len(body) % 512)
61
+ # Python pads to 10240 bytes; the sandbox agent's Go writer emits the two
62
+ # zero blocks and stops, so test against that shape.
63
+ return padded + b"\x00" * 1024 if terminated else padded
64
+
65
+
66
+ def _file(name, content=b"", mode=0o644):
67
+ return (name, tarfile.REGTYPE, content, mode)
68
+
69
+
70
+ def _dir(name, mode=0o755):
71
+ return (name, tarfile.DIRTYPE, "", mode)
72
+
73
+
74
+ def _symlink(name, target):
75
+ return (name, tarfile.SYMTYPE, target, 0o777)
76
+
77
+
78
+ def _extract(dest, data):
79
+ extract_tar(str(dest), io.BytesIO(data))
80
+
81
+
82
+ # --- filesystem helpers -----------------------------------------------------
83
+
84
+
85
+ def test_extract_writes_the_tree(tmp_path):
86
+ dest = tmp_path / "out"
87
+ _extract(
88
+ dest,
89
+ _tar([_dir("sub"), _file("sub/a.txt", b"hello"), _file("b.txt", b"world")]),
90
+ )
91
+
92
+ assert (dest / "sub" / "a.txt").read_bytes() == b"hello"
93
+ assert (dest / "b.txt").read_bytes() == b"world"
94
+
95
+
96
+ def test_extract_creates_missing_parent_dirs(tmp_path):
97
+ dest = tmp_path / "out"
98
+ _extract(dest, _tar([_file("deep/nested/a.txt", b"hi")]))
99
+
100
+ assert (dest / "deep" / "nested" / "a.txt").read_bytes() == b"hi"
101
+
102
+
103
+ def test_extract_creates_symlinks(tmp_path):
104
+ dest = tmp_path / "out"
105
+ _extract(dest, _tar([_file("a.txt", b"hi"), _symlink("link", "a.txt")]))
106
+
107
+ assert os.readlink(dest / "link") == "a.txt"
108
+ assert (dest / "link").read_bytes() == b"hi"
109
+
110
+
111
+ def test_extract_drops_setuid(tmp_path):
112
+ dest = tmp_path / "out"
113
+ _extract(dest, _tar([_file("a.txt", b"hi", mode=0o4755)]))
114
+
115
+ mode = (dest / "a.txt").stat().st_mode
116
+ assert not mode & stat.S_ISUID
117
+ assert mode & 0o600 == 0o600
118
+
119
+
120
+ def test_extract_refuses_an_entry_escaping_the_destination(tmp_path):
121
+ dest = tmp_path / "out"
122
+ with pytest.raises(SandboxDownloadError):
123
+ _extract(dest, _tar([_file("../evil.txt", b"pwned")]))
124
+
125
+ assert not (tmp_path / "evil.txt").exists()
126
+
127
+
128
+ def test_extract_refuses_an_absolute_entry(tmp_path):
129
+ dest = tmp_path / "out"
130
+ with pytest.raises(SandboxDownloadError):
131
+ _extract(dest, _tar([_file("/etc/evil.txt", b"pwned")]))
132
+
133
+
134
+ def test_extract_refuses_a_write_through_an_escaping_symlink(tmp_path):
135
+ outside = tmp_path / "outside"
136
+ outside.mkdir()
137
+ dest = tmp_path / "out"
138
+
139
+ with pytest.raises(SandboxDownloadError):
140
+ _extract(
141
+ dest,
142
+ _tar(
143
+ [_symlink("escape", str(outside)), _file("escape/evil.txt", b"pwned")]
144
+ ),
145
+ )
146
+
147
+ assert list(outside.iterdir()) == []
148
+
149
+
150
+ def test_extract_replaces_a_symlink_at_the_leaf_rather_than_following_it(tmp_path):
151
+ target = tmp_path / "outside.txt"
152
+ target.write_bytes(b"original")
153
+ dest = tmp_path / "out"
154
+
155
+ _extract(
156
+ dest,
157
+ _tar([_symlink("evil.txt", str(target)), _file("evil.txt", b"pwned")]),
158
+ )
159
+
160
+ assert target.read_bytes() == b"original"
161
+ written = dest / "evil.txt"
162
+ assert not written.is_symlink()
163
+ assert written.read_bytes() == b"pwned"
164
+
165
+
166
+ def test_extract_replaces_a_fifo_at_the_target(tmp_path):
167
+ """Opening a FIFO blocks until a writer appears, which may be never, so
168
+ extraction has to clear one instead of opening it."""
169
+ dest = tmp_path / "out"
170
+ dest.mkdir()
171
+ os.mkfifo(dest / "a.txt")
172
+ data = _tar([_file("a.txt", b"hi")])
173
+
174
+ # In a thread so a regression fails the test instead of hanging the run.
175
+ worker = threading.Thread(target=_extract, args=(dest, data), daemon=True)
176
+ worker.start()
177
+ worker.join(timeout=10)
178
+
179
+ assert not worker.is_alive(), "extraction blocked on the FIFO"
180
+ assert not stat.S_ISFIFO((dest / "a.txt").stat().st_mode)
181
+ assert (dest / "a.txt").read_bytes() == b"hi"
182
+
183
+
184
+ def test_extract_links_hard_links_to_their_target(tmp_path):
185
+ dest = tmp_path / "out"
186
+ _extract(
187
+ dest,
188
+ _tar([_file("a.txt", b"hi"), ("b.txt", tarfile.LNKTYPE, "a.txt", 0o644)]),
189
+ )
190
+
191
+ assert (dest / "b.txt").read_bytes() == b"hi"
192
+ assert (dest / "a.txt").stat().st_ino == (dest / "b.txt").stat().st_ino
193
+
194
+
195
+ def test_extract_refuses_a_hard_link_escaping_the_destination(tmp_path):
196
+ dest = tmp_path / "out"
197
+ with pytest.raises(SandboxDownloadError):
198
+ _extract(dest, _tar([("b.txt", tarfile.LNKTYPE, "../outside.txt", 0o644)]))
199
+
200
+
201
+ def test_extract_skips_special_entries(tmp_path):
202
+ dest = tmp_path / "out"
203
+ _extract(dest, _tar([("fifo", tarfile.FIFOTYPE, "", 0o644), _file("a.txt", b"hi")]))
204
+
205
+ assert not (dest / "fifo").exists()
206
+ assert (dest / "a.txt").read_bytes() == b"hi"
207
+
208
+
209
+ def test_extract_reports_a_malformed_archive_as_a_download_error(tmp_path):
210
+ """A bad header raises tarfile.TarError, which says nothing about the
211
+ sandbox to a caller catching RenderError."""
212
+ dest = tmp_path / "out"
213
+ data = bytearray(_tar([_file("a.txt", b"hi")]))
214
+ # Corrupt the mode field so the header checksum no longer matches.
215
+ data[100:108] = b"garbage!"
216
+
217
+ with pytest.raises(SandboxDownloadError, match="malformed"):
218
+ _extract(dest, bytes(data))
219
+
220
+
221
+ def test_extract_refuses_to_replace_a_directory_with_a_file(tmp_path):
222
+ dest = tmp_path / "out"
223
+ (dest / "a.txt").mkdir(parents=True)
224
+ (dest / "a.txt" / "keep.txt").write_bytes(b"mine")
225
+
226
+ with pytest.raises(SandboxDownloadError, match="would replace the directory"):
227
+ _extract(dest, _tar([_file("a.txt", b"pwned")]))
228
+
229
+ assert (dest / "a.txt" / "keep.txt").read_bytes() == b"mine"
230
+
231
+
232
+ def test_extract_refuses_a_file_where_the_archive_needs_a_directory(tmp_path):
233
+ dest = tmp_path / "out"
234
+ dest.mkdir()
235
+ (dest / "sub").write_bytes(b"i am a file")
236
+
237
+ with pytest.raises(SandboxDownloadError, match="needs a directory"):
238
+ _extract(dest, _tar([_file("sub/a.txt", b"hi")]))
239
+
240
+
241
+ def test_extract_refuses_a_truncated_archive(tmp_path):
242
+ dest = tmp_path / "out"
243
+ data = _tar([_file("a.txt", b"hi" * 500)], terminated=False)
244
+
245
+ with pytest.raises(SandboxDownloadError, match="end-of-archive"):
246
+ _extract(dest, data)
247
+
248
+
249
+ def test_extract_refuses_an_archive_cut_mid_entry(tmp_path):
250
+ dest = tmp_path / "out"
251
+ data = _tar([_file("a.txt", b"hi" * 500)])
252
+
253
+ with pytest.raises(SandboxDownloadError, match="end-of-archive"):
254
+ _extract(dest, data[:600])
255
+
256
+
257
+ def test_base_name_reduces_untrusted_names():
258
+ assert base_name("../../etc/passwd") == "passwd"
259
+ assert base_name("a/b.txt") == "b.txt"
260
+ assert base_name("C:\\evil.txt") == "evil.txt"
261
+ assert base_name("..") == ""
262
+ assert base_name("/") == ""
263
+ assert base_name("") == ""
264
+
265
+
266
+ def test_media_type_drops_parameters():
267
+ assert media_type("application/X-Tar; charset=utf-8") == "application/x-tar"
268
+ assert media_type("") == ""
269
+
270
+
271
+ def test_disposition_filename_reads_the_header():
272
+ assert disposition_filename('attachment; filename="a b.txt"') == "a b.txt"
273
+ assert disposition_filename("attachment") == ""
274
+ assert disposition_filename("") == ""
275
+
276
+
277
+ def test_check_content_encoding_accepts_what_httpx_decodes():
278
+ for header in ("", "identity", "gzip", "x-gzip", " GZIP "):
279
+ check_content_encoding(header)
280
+
281
+
282
+ def test_check_content_encoding_refuses_anything_else():
283
+ for header in ("br", "zstd", "gzip, gzip"):
284
+ with pytest.raises(SandboxDownloadError):
285
+ check_content_encoding(header)
286
+
287
+
288
+ def test_normalize_remote_path_matches_go_path_clean():
289
+ assert normalize_remote_path("/app/data") == "/app/data"
290
+ assert normalize_remote_path("/app/data/") == "/app/data"
291
+ assert normalize_remote_path("workspace/") == "workspace"
292
+ assert normalize_remote_path("/app/./data/../data.txt") == "/app/data.txt"
293
+ assert normalize_remote_path("/app//x") == "/app/x"
294
+ assert normalize_remote_path("//app/x") == "/app/x"
295
+ assert normalize_remote_path("///app/x") == "/app/x"
296
+
297
+
298
+ def test_normalize_remote_path_rejects_an_empty_path():
299
+ """Cleaning "" gives ".", which would quietly transfer the home directory;
300
+ copy_to rejects it the same way."""
301
+ with pytest.raises(ValueError, match="remote_path is required"):
302
+ normalize_remote_path("")
303
+
304
+
305
+ def test_resolve_file_dest_picks_a_name_inside_a_directory(tmp_path):
306
+ dest = resolve_file_dest(str(tmp_path), "a.txt", "/remote/b.txt")
307
+ assert dest == str(tmp_path / "a.txt")
308
+
309
+
310
+ def test_resolve_file_dest_sanitizes_the_suggested_name(tmp_path):
311
+ dest = resolve_file_dest(str(tmp_path), "../../evil.txt", "/remote/b.txt")
312
+ assert dest == str(tmp_path / "evil.txt")
313
+
314
+
315
+ def test_resolve_file_dest_falls_back_to_the_remote_name(tmp_path):
316
+ dest = resolve_file_dest(str(tmp_path), "", "/remote/b.txt")
317
+ assert dest == str(tmp_path / "b.txt")
318
+
319
+
320
+ def test_resolve_file_dest_keeps_a_non_directory_path(tmp_path):
321
+ target = tmp_path / "chosen.txt"
322
+ assert resolve_file_dest(str(target), "a.txt", "/remote/b.txt") == str(target)
323
+
324
+
325
+ def test_resolve_file_dest_raises_without_a_usable_name(tmp_path):
326
+ with pytest.raises(SandboxDownloadError):
327
+ resolve_file_dest(str(tmp_path), "", "/")
328
+
329
+
330
+ # --- copy_from --------------------------------------------------------------
331
+
332
+
333
+ def _sandbox_client(handler, *, default_owner_id="tea-test"):
334
+ internal = AuthenticatedClient(
335
+ base_url="https://api.test/v1",
336
+ token="test-token",
337
+ httpx_args={"transport": httpx.MockTransport(handler)},
338
+ )
339
+ return SandboxClient(internal, default_owner_id=default_owner_id)
340
+
341
+
342
+ async def _noop_handler(request: httpx.Request) -> httpx.Response:
343
+ raise AssertionError("the API client should not be called in this test")
344
+
345
+
346
+ def _patch_proxy(mocker, handler):
347
+ """Patch the fresh httpx.AsyncClient the download uses for the proxy stream."""
348
+ mocker.patch(
349
+ "render.experimental.sandbox.api.httpx.AsyncClient",
350
+ return_value=httpx.AsyncClient(transport=httpx.MockTransport(handler)),
351
+ )
352
+
353
+
354
+ def _download_client(mocker, content, headers):
355
+ """A client whose token mint is stubbed and whose proxy serves content."""
356
+ captured = {}
357
+
358
+ async def handler(request: httpx.Request) -> httpx.Response:
359
+ captured["method"] = request.method
360
+ captured["auth"] = request.headers.get("authorization")
361
+ captured["accept_encoding"] = request.headers.get("accept-encoding")
362
+ return httpx.Response(200, content=content, headers=headers)
363
+
364
+ client = _sandbox_client(_noop_handler)
365
+ mocker.patch.object(
366
+ client.api, "_mint_file_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
367
+ )
368
+ _patch_proxy(mocker, handler)
369
+ return client, captured
370
+
371
+
372
+ @pytest.mark.asyncio
373
+ async def test_mint_file_token_requests_the_token_endpoint():
374
+ captured = {}
375
+
376
+ async def handler(request: httpx.Request) -> httpx.Response:
377
+ captured["method"] = request.method
378
+ captured["path"] = request.url.path
379
+ captured["query"] = dict(request.url.params)
380
+ return httpx.Response(201, json=CONNECT_JSON)
381
+
382
+ client = _sandbox_client(handler)
383
+ connection = await client.api._mint_file_token(
384
+ "sbx-123", "tea-test", "download", "/app/data.txt"
385
+ )
386
+
387
+ assert captured["method"] == "POST"
388
+ assert captured["path"] == "/v1/sandboxes/sbx-123/files/download/token"
389
+ assert captured["query"]["ownerId"] == "tea-test"
390
+ assert captured["query"]["path"] == "/app/data.txt"
391
+ assert connection["uri"] == "https://proxy.test/files/download"
392
+
393
+
394
+ @pytest.mark.asyncio
395
+ async def test_copy_from_writes_a_single_file(tmp_path, mocker):
396
+ client, captured = _download_client(
397
+ mocker, b"payload", {"content-type": "application/octet-stream"}
398
+ )
399
+ dest = tmp_path / "local.txt"
400
+
401
+ written = await client.copy_from("sbx-123", "/app/data.txt", dest)
402
+
403
+ assert captured["method"] == "GET"
404
+ assert captured["auth"] == "Bearer file-token-xyz"
405
+ assert written == str(dest)
406
+ assert dest.read_bytes() == b"payload"
407
+ assert dest.stat().st_mode & 0o600 == 0o600
408
+ assert [p.name for p in tmp_path.iterdir()] == ["local.txt"]
409
+
410
+
411
+ @pytest.mark.asyncio
412
+ async def test_copy_from_asks_only_for_a_coding_httpx_undoes(tmp_path, mocker):
413
+ """httpx would otherwise advertise deflate too, and a deflate body would
414
+ reach the encoding check instead of being written."""
415
+ client, captured = _download_client(
416
+ mocker, b"payload", {"content-type": "application/octet-stream"}
417
+ )
418
+
419
+ await client.copy_from("sbx-123", "/app/data.txt", tmp_path / "x")
420
+
421
+ assert captured["accept_encoding"] == "gzip"
422
+
423
+
424
+ @pytest.mark.asyncio
425
+ async def test_copy_from_cleans_the_remote_path_before_minting(tmp_path, mocker):
426
+ client, _ = _download_client(
427
+ mocker, b"payload", {"content-type": "application/octet-stream"}
428
+ )
429
+
430
+ await client.copy_from("sbx-123", "/app/./data/../data.txt", tmp_path / "x")
431
+
432
+ assert client.api._mint_file_token.await_args.args[3] == "/app/data.txt"
433
+
434
+
435
+ @pytest.mark.asyncio
436
+ async def test_copy_from_accepts_a_path_object(tmp_path, mocker):
437
+ client, _ = _download_client(
438
+ mocker, b"payload", {"content-type": "application/octet-stream"}
439
+ )
440
+
441
+ written = await client.copy_from("sbx-123", "/app/data.txt", tmp_path / "out.txt")
442
+
443
+ assert written == str(tmp_path / "out.txt")
444
+
445
+
446
+ @pytest.mark.asyncio
447
+ async def test_copy_from_into_a_directory_uses_the_suggested_name(tmp_path, mocker):
448
+ client, _ = _download_client(
449
+ mocker,
450
+ b"payload",
451
+ {
452
+ "content-type": "application/octet-stream",
453
+ "content-disposition": 'attachment; filename="data.txt"',
454
+ },
455
+ )
456
+
457
+ written = await client.copy_from("sbx-123", "/app/data.txt", tmp_path)
458
+
459
+ assert written == str(tmp_path / "data.txt")
460
+ assert (tmp_path / "data.txt").read_bytes() == b"payload"
461
+
462
+
463
+ @pytest.mark.asyncio
464
+ async def test_copy_from_sanitizes_the_suggested_name(tmp_path, mocker):
465
+ client, _ = _download_client(
466
+ mocker,
467
+ b"payload",
468
+ {
469
+ "content-type": "application/octet-stream",
470
+ "content-disposition": 'attachment; filename="../../evil.txt"',
471
+ },
472
+ )
473
+ dest = tmp_path / "into"
474
+ dest.mkdir()
475
+
476
+ written = await client.copy_from("sbx-123", "/app/data.txt", dest)
477
+
478
+ assert written == str(dest / "evil.txt")
479
+ assert not (tmp_path / "evil.txt").exists()
480
+
481
+
482
+ @pytest.mark.asyncio
483
+ async def test_copy_from_falls_back_to_the_remote_name(tmp_path, mocker):
484
+ client, _ = _download_client(
485
+ mocker, b"payload", {"content-type": "application/octet-stream"}
486
+ )
487
+
488
+ written = await client.copy_from("sbx-123", "/app/data.txt", tmp_path)
489
+
490
+ assert written == str(tmp_path / "data.txt")
491
+
492
+
493
+ @pytest.mark.asyncio
494
+ async def test_copy_from_saves_legacy_gzip_as_a_file(tmp_path, mocker):
495
+ """A pre-#34092 agent sends application/gzip; it is a payload type, so the
496
+ body is written as-is rather than extracted."""
497
+ archive = gzip.compress(_tar([_file("a.txt", b"hi")]))
498
+ client, _ = _download_client(mocker, archive, {"content-type": "application/gzip"})
499
+ dest = tmp_path / "dir.tar.gz"
500
+
501
+ written = await client.copy_from("sbx-123", "/app/dir", dest)
502
+
503
+ assert written == str(dest)
504
+ assert dest.read_bytes() == archive
505
+
506
+
507
+ @pytest.mark.asyncio
508
+ async def test_copy_from_extracts_a_gzip_encoded_tar(tmp_path, mocker):
509
+ """The agent sends x-tar under Content-Encoding: gzip, which httpx has
510
+ already decoded by the time the body reaches us."""
511
+ archive = _tar([_dir("sub"), _file("sub/a.txt", b"hello")])
512
+ client, _ = _download_client(
513
+ mocker,
514
+ gzip.compress(archive),
515
+ {"content-type": "application/x-tar", "content-encoding": "gzip"},
516
+ )
517
+ dest = tmp_path / "tree"
518
+
519
+ written = await client.copy_from("sbx-123", "/app/dir", dest)
520
+
521
+ assert written == str(dest)
522
+ assert (dest / "sub" / "a.txt").read_bytes() == b"hello"
523
+
524
+
525
+ @pytest.mark.asyncio
526
+ async def test_copy_from_extracts_an_identity_tar(tmp_path, mocker):
527
+ client, _ = _download_client(
528
+ mocker,
529
+ _tar([_file("a.txt", b"hello")]),
530
+ {"content-type": "application/x-tar; charset=binary"},
531
+ )
532
+ dest = tmp_path / "tree"
533
+
534
+ await client.copy_from("sbx-123", "/app/dir", dest)
535
+
536
+ assert (dest / "a.txt").read_bytes() == b"hello"
537
+
538
+
539
+ @pytest.mark.asyncio
540
+ async def test_copy_from_leaves_no_spool_file_behind(tmp_path, mocker):
541
+ client, _ = _download_client(
542
+ mocker,
543
+ _tar([_file("a.txt", b"hi" * 500)], terminated=False),
544
+ {"content-type": "application/x-tar"},
545
+ )
546
+ dest = tmp_path / "tree"
547
+
548
+ with pytest.raises(SandboxDownloadError):
549
+ await client.copy_from("sbx-123", "/app/dir", dest)
550
+
551
+ assert [p.name for p in tmp_path.iterdir()] == ["tree"]
552
+
553
+
554
+ @pytest.mark.asyncio
555
+ async def test_copy_from_refuses_an_encoding_it_cannot_undo(tmp_path, mocker):
556
+ client, _ = _download_client(
557
+ mocker,
558
+ b"payload",
559
+ {"content-type": "application/octet-stream", "content-encoding": "br"},
560
+ )
561
+ dest = tmp_path / "local.txt"
562
+
563
+ with pytest.raises(SandboxDownloadError):
564
+ await client.copy_from("sbx-123", "/app/data.txt", dest)
565
+
566
+ assert not dest.exists()
567
+
568
+
569
+ def _proxy_error_client(mocker, status, **response_kwargs):
570
+ """A client whose token mint succeeds and whose proxy returns an error."""
571
+
572
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
573
+ return httpx.Response(status, **response_kwargs)
574
+
575
+ client = _sandbox_client(_noop_handler)
576
+ mocker.patch.object(
577
+ client.api, "_mint_file_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
578
+ )
579
+ _patch_proxy(mocker, proxy_handler)
580
+ return client
581
+
582
+
583
+ @pytest.mark.asyncio
584
+ async def test_copy_from_missing_remote_path_raises_file_not_found(tmp_path, mocker):
585
+ """The agent 404s a bad remote_path with this code while the sandbox is
586
+ alive, so it must not look like a terminated sandbox."""
587
+ client = _proxy_error_client(
588
+ mocker,
589
+ 404,
590
+ json={"code": "file_not_found", "message": "no file or directory at path"},
591
+ )
592
+
593
+ with pytest.raises(SandboxFileNotFoundError) as excinfo:
594
+ await client.copy_from("sbx-123", "/no/such/file", tmp_path / "x")
595
+
596
+ assert not isinstance(excinfo.value, SandboxNotFoundError)
597
+ assert "no file or directory at path" in str(excinfo.value)
598
+
599
+
600
+ @pytest.mark.asyncio
601
+ async def test_copy_from_proxy_404_without_the_code_raises_not_found(tmp_path, mocker):
602
+ client = _proxy_error_client(mocker, 404, json={"message": "sandbox is gone"})
603
+
604
+ with pytest.raises(SandboxNotFoundError):
605
+ await client.copy_from("sbx-123", "/app/data.txt", tmp_path / "x")
606
+
607
+
608
+ @pytest.mark.asyncio
609
+ async def test_copy_from_proxy_404_with_a_non_json_body_raises_not_found(
610
+ tmp_path, mocker
611
+ ):
612
+ client = _proxy_error_client(mocker, 404, text="404 page not found")
613
+
614
+ with pytest.raises(SandboxNotFoundError):
615
+ await client.copy_from("sbx-123", "/app/data.txt", tmp_path / "x")
616
+
617
+
618
+ @pytest.mark.asyncio
619
+ async def test_copy_from_reports_a_local_write_failure_as_a_download_error(
620
+ tmp_path, mocker
621
+ ):
622
+ """A bare OSError from the spool, the extract or the rename says nothing
623
+ about which download failed."""
624
+ client, _ = _download_client(
625
+ mocker, b"payload", {"content-type": "application/octet-stream"}
626
+ )
627
+ missing_parent = tmp_path / "no" / "such" / "dir" / "out.txt"
628
+
629
+ with pytest.raises(SandboxDownloadError, match="could not write the download"):
630
+ await client.copy_from("sbx-123", "/app/data.txt", missing_parent)
631
+
632
+
633
+ @pytest.mark.asyncio
634
+ async def test_copy_from_bounds_the_wait_for_the_next_chunk(tmp_path, mocker):
635
+ """read=None would let a body that stops arriving hang forever."""
636
+ captured = {}
637
+
638
+ # Bound before the patch below, so building the stub does not re-enter it.
639
+ real_async_client = httpx.AsyncClient
640
+
641
+ def fake_client(*args, **kwargs):
642
+ captured["timeout"] = kwargs["timeout"]
643
+ return real_async_client(
644
+ transport=httpx.MockTransport(
645
+ lambda request: httpx.Response(
646
+ 200,
647
+ content=b"payload",
648
+ headers={"content-type": "application/octet-stream"},
649
+ )
650
+ )
651
+ )
652
+
653
+ client = _sandbox_client(_noop_handler)
654
+ mocker.patch.object(
655
+ client.api, "_mint_file_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
656
+ )
657
+ mocker.patch("render.experimental.sandbox.api.httpx.AsyncClient", new=fake_client)
658
+
659
+ await client.copy_from("sbx-123", "/app/data.txt", tmp_path / "out.txt")
660
+
661
+ assert captured["timeout"].read == 45.0
662
+ assert captured["timeout"].connect == 5.0
663
+
664
+
665
+ @pytest.mark.asyncio
666
+ async def test_copy_from_mint_404_raises_not_found(tmp_path):
667
+ async def handler(request: httpx.Request) -> httpx.Response:
668
+ return httpx.Response(404, json={"message": "not found"})
669
+
670
+ client = _sandbox_client(handler)
671
+ with pytest.raises(SandboxNotFoundError):
672
+ await client.copy_from("sbx-missing", "/app/data.txt", tmp_path / "x")
673
+
674
+
675
+ @pytest.mark.asyncio
676
+ async def test_copy_from_proxy_error_raises_client_error(tmp_path, mocker):
677
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
678
+ return httpx.Response(400, text="no such file")
679
+
680
+ client = _sandbox_client(_noop_handler)
681
+ mocker.patch.object(
682
+ client.api, "_mint_file_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
683
+ )
684
+ _patch_proxy(mocker, proxy_handler)
685
+
686
+ with pytest.raises(ClientError, match="no such file"):
687
+ await client.copy_from("sbx-123", "/app/missing", tmp_path / "x")
688
+
689
+
690
+ @pytest.mark.asyncio
691
+ async def test_copy_from_requires_owner_id(tmp_path):
692
+ client = _sandbox_client(_noop_handler, default_owner_id=None)
693
+ with pytest.raises(RenderError):
694
+ await client.copy_from("sbx-123", "/app/data.txt", tmp_path / "x")
695
+
696
+
697
+ def test_sync_copy_from_writes_a_single_file(tmp_path, mocker):
698
+ from render.experimental.sandbox.client_sync import SyncSandboxClient
699
+
700
+ def api_handler(request: httpx.Request) -> httpx.Response:
701
+ return httpx.Response(201, json=CONNECT_JSON)
702
+
703
+ def proxy_handler(request: httpx.Request) -> httpx.Response:
704
+ return httpx.Response(
705
+ 200,
706
+ content=b"payload",
707
+ headers={"content-type": "application/octet-stream"},
708
+ )
709
+
710
+ internal = AuthenticatedClient(
711
+ base_url="https://api.test/v1",
712
+ token="test-token",
713
+ httpx_args={"transport": httpx.MockTransport(api_handler)},
714
+ )
715
+ # Build the API client before patching: httpx.Client is one attribute on one
716
+ # module, so the patch below would otherwise catch this construction too.
717
+ internal.get_httpx_client()
718
+ mocker.patch(
719
+ "render.experimental.sandbox.api_sync.httpx.Client",
720
+ return_value=httpx.Client(transport=httpx.MockTransport(proxy_handler)),
721
+ )
722
+ client = SyncSandboxClient(internal, default_owner_id="tea-test")
723
+ dest = tmp_path / "local.txt"
724
+
725
+ written = client.copy_from("sbx-123", "/app/data.txt", dest)
726
+
727
+ assert written == str(dest)
728
+ assert dest.read_bytes() == b"payload"