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,802 @@
1
+ import gzip
2
+ import io
3
+ import json
4
+ import os
5
+ import tarfile
6
+ from typing import cast
7
+
8
+ import httpx
9
+ import pytest
10
+
11
+ from render.client.errors import ClientError, RenderError
12
+ from render.experimental.sandbox.client import SandboxClient
13
+ from render.experimental.sandbox.errors import (
14
+ SandboxExecError,
15
+ SandboxExecStreamError,
16
+ SandboxNotFoundError,
17
+ )
18
+ from render.experimental.sandbox.types import SandboxExecExit, SandboxExecOutput
19
+ from render.public_api.client import AuthenticatedClient
20
+
21
+ SANDBOX_JSON = {
22
+ "id": "sbx-abc",
23
+ "status": "running",
24
+ "plan": "standard",
25
+ "networkPolicy": {"default": "deny-all"},
26
+ "region": "oregon",
27
+ "timeoutSeconds": 300,
28
+ "createdAt": "2026-07-17T00:00:00Z",
29
+ "terminatedAt": None,
30
+ }
31
+
32
+
33
+ def _sandbox_client(handler, *, default_owner_id="tea-test"):
34
+ transport = httpx.MockTransport(handler)
35
+ internal = AuthenticatedClient(
36
+ base_url="https://api.test/v1",
37
+ token="test-token",
38
+ httpx_args={"transport": transport},
39
+ )
40
+ return SandboxClient(internal, default_owner_id=default_owner_id)
41
+
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_create_sends_sandbox_post_and_returns_sandbox():
45
+ captured = {}
46
+
47
+ async def handler(request: httpx.Request) -> httpx.Response:
48
+ captured["method"] = request.method
49
+ captured["path"] = request.url.path
50
+ captured["body"] = json.loads(request.content)
51
+ return httpx.Response(201, json=SANDBOX_JSON)
52
+
53
+ client = _sandbox_client(handler)
54
+ sandbox = await client.create(plan="standard", timeout_seconds=300)
55
+
56
+ assert captured["method"] == "POST"
57
+ assert captured["path"] == "/v1/sandboxes"
58
+ assert captured["body"]["ownerId"] == "tea-test"
59
+ assert captured["body"]["plan"] == "standard"
60
+ assert captured["body"]["timeoutSeconds"] == 300
61
+ assert "env" not in captured["body"]
62
+ assert sandbox.id == "sbx-abc"
63
+ assert sandbox.status == "running"
64
+ assert sandbox.network_policy == "deny-all"
65
+ assert sandbox.terminated_at is None
66
+
67
+
68
+ @pytest.mark.asyncio
69
+ async def test_create_sends_env_in_post_body():
70
+ captured = {}
71
+
72
+ async def handler(request: httpx.Request) -> httpx.Response:
73
+ captured["body"] = json.loads(request.content)
74
+ return httpx.Response(201, json=SANDBOX_JSON)
75
+
76
+ client = _sandbox_client(handler)
77
+ await client.create(env={"FOO": "bar", "BAZ": "qux"})
78
+
79
+ assert captured["body"]["env"] == {"FOO": "bar", "BAZ": "qux"}
80
+
81
+
82
+ @pytest.mark.asyncio
83
+ async def test_create_requires_owner_id():
84
+ async def handler(request: httpx.Request) -> httpx.Response:
85
+ raise AssertionError("handler should not be reached")
86
+
87
+ client = _sandbox_client(handler, default_owner_id=None)
88
+ with pytest.raises(RenderError):
89
+ await client.create()
90
+
91
+
92
+ @pytest.mark.asyncio
93
+ async def test_from_id_returns_sandbox():
94
+ captured = {}
95
+
96
+ async def handler(request: httpx.Request) -> httpx.Response:
97
+ captured["path"] = request.url.path
98
+ captured["query"] = dict(request.url.params)
99
+ return httpx.Response(200, json=SANDBOX_JSON)
100
+
101
+ client = _sandbox_client(handler)
102
+ sandbox = await client.from_id("sbx-abc")
103
+
104
+ assert captured["path"] == "/v1/sandboxes/sbx-abc"
105
+ assert captured["query"]["ownerId"] == "tea-test"
106
+ assert sandbox.id == "sbx-abc"
107
+
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_from_id_raises_not_found_on_404():
111
+ async def handler(request: httpx.Request) -> httpx.Response:
112
+ return httpx.Response(404, json={"message": "not found"})
113
+
114
+ client = _sandbox_client(handler)
115
+ with pytest.raises(SandboxNotFoundError):
116
+ await client.from_id("sbx-missing")
117
+
118
+
119
+ @pytest.mark.asyncio
120
+ async def test_list_returns_sandboxes_and_cursor():
121
+ captured = {}
122
+
123
+ async def handler(request: httpx.Request) -> httpx.Response:
124
+ captured["path"] = request.url.path
125
+ captured["query"] = dict(request.url.params)
126
+ captured["status_values"] = request.url.params.get_list("status")
127
+ body = [
128
+ {"sandbox": SANDBOX_JSON, "cursor": "cur-1"},
129
+ {"sandbox": {**SANDBOX_JSON, "id": "sbx-def"}, "cursor": "cur-2"},
130
+ ]
131
+ return httpx.Response(200, json=body)
132
+
133
+ client = _sandbox_client(handler)
134
+ page = await client.list(status="running", limit=50)
135
+
136
+ assert captured["path"] == "/v1/sandboxes"
137
+ assert captured["query"]["ownerId"] == "tea-test"
138
+ assert captured["status_values"] == ["running"]
139
+ assert captured["query"]["limit"] == "50"
140
+ assert [s.id for s in page.sandboxes] == ["sbx-abc", "sbx-def"]
141
+ assert page.next_cursor == "cur-2"
142
+
143
+
144
+ @pytest.mark.asyncio
145
+ async def test_list_empty_has_no_cursor():
146
+ async def handler(request: httpx.Request) -> httpx.Response:
147
+ return httpx.Response(200, json=[])
148
+
149
+ client = _sandbox_client(handler)
150
+ page = await client.list()
151
+ assert page.sandboxes == []
152
+ assert page.next_cursor is None
153
+
154
+
155
+ @pytest.mark.asyncio
156
+ async def test_list_sends_one_status_query_param_per_value():
157
+ captured = {}
158
+
159
+ async def handler(request: httpx.Request) -> httpx.Response:
160
+ captured["status"] = request.url.params.get_list("status")
161
+ return httpx.Response(200, json=[])
162
+
163
+ client = _sandbox_client(handler)
164
+ await client.list(status=["running", "creating"])
165
+
166
+ assert captured["status"] == ["running", "creating"]
167
+
168
+
169
+ @pytest.mark.asyncio
170
+ async def test_list_sends_a_single_status_string_as_one_value():
171
+ captured = {}
172
+
173
+ async def handler(request: httpx.Request) -> httpx.Response:
174
+ captured["status"] = request.url.params.get_list("status")
175
+ return httpx.Response(200, json=[])
176
+
177
+ client = _sandbox_client(handler)
178
+ await client.list(status="running")
179
+
180
+ assert captured["status"] == ["running"]
181
+
182
+
183
+ @pytest.mark.asyncio
184
+ async def test_list_omits_status_when_no_filter_is_given():
185
+ captured = {}
186
+
187
+ async def handler(request: httpx.Request) -> httpx.Response:
188
+ captured["query"] = dict(request.url.params)
189
+ return httpx.Response(200, json=[])
190
+
191
+ client = _sandbox_client(handler)
192
+
193
+ await client.list()
194
+ assert "status" not in captured["query"]
195
+
196
+ await client.list(status=[])
197
+ assert "status" not in captured["query"]
198
+
199
+
200
+ @pytest.mark.asyncio
201
+ async def test_list_rejects_an_unknown_status():
202
+ async def handler(request: httpx.Request) -> httpx.Response:
203
+ raise AssertionError("handler should not be reached")
204
+
205
+ client = _sandbox_client(handler)
206
+
207
+ with pytest.raises(ValueError, match="bogus"):
208
+ await client.list(status=["running", "bogus"])
209
+
210
+ with pytest.raises(ValueError, match="bogus"):
211
+ await client.list(status="bogus")
212
+
213
+
214
+ def _sync_sandbox_client(handler, *, default_owner_id="tea-test"):
215
+ from render.experimental.sandbox.client_sync import SyncSandboxClient
216
+
217
+ internal = AuthenticatedClient(
218
+ base_url="https://api.test/v1",
219
+ token="test-token",
220
+ httpx_args={"transport": httpx.MockTransport(handler)},
221
+ )
222
+ return SyncSandboxClient(internal, default_owner_id=default_owner_id)
223
+
224
+
225
+ def test_sync_list_sends_one_status_query_param_per_value():
226
+ captured = {}
227
+
228
+ def handler(request: httpx.Request) -> httpx.Response:
229
+ captured["status"] = request.url.params.get_list("status")
230
+ return httpx.Response(200, json=[{"sandbox": SANDBOX_JSON, "cursor": "cur-1"}])
231
+
232
+ client = _sync_sandbox_client(handler)
233
+ page = client.list(status=["running", "suspended"])
234
+
235
+ assert captured["status"] == ["running", "suspended"]
236
+ assert [s.id for s in page.sandboxes] == ["sbx-abc"]
237
+ assert page.next_cursor == "cur-1"
238
+
239
+
240
+ def test_sync_list_sends_a_single_status_string_as_one_value():
241
+ captured = {}
242
+
243
+ def handler(request: httpx.Request) -> httpx.Response:
244
+ captured["status"] = request.url.params.get_list("status")
245
+ return httpx.Response(200, json=[])
246
+
247
+ client = _sync_sandbox_client(handler)
248
+ client.list(status="running")
249
+
250
+ assert captured["status"] == ["running"]
251
+
252
+
253
+ def test_sync_list_omits_status_when_no_filter_is_given():
254
+ captured = {}
255
+
256
+ def handler(request: httpx.Request) -> httpx.Response:
257
+ captured["query"] = dict(request.url.params)
258
+ return httpx.Response(200, json=[])
259
+
260
+ client = _sync_sandbox_client(handler)
261
+
262
+ client.list()
263
+ assert "status" not in captured["query"]
264
+
265
+ client.list(status=[])
266
+ assert "status" not in captured["query"]
267
+
268
+
269
+ def test_sync_list_rejects_an_unknown_status():
270
+ def handler(request: httpx.Request) -> httpx.Response:
271
+ raise AssertionError("handler should not be reached")
272
+
273
+ client = _sync_sandbox_client(handler)
274
+
275
+ with pytest.raises(ValueError, match="bogus"):
276
+ client.list(status=["running", "bogus"])
277
+
278
+ with pytest.raises(ValueError, match="bogus"):
279
+ client.list(status="bogus")
280
+
281
+
282
+ @pytest.mark.asyncio
283
+ async def test_terminate_posts_to_terminate_and_returns_none():
284
+ captured = {}
285
+
286
+ async def handler(request: httpx.Request) -> httpx.Response:
287
+ captured["method"] = request.method
288
+ captured["path"] = request.url.path
289
+ captured["query"] = dict(request.url.params)
290
+ return httpx.Response(204)
291
+
292
+ client = _sandbox_client(handler)
293
+ result = await client.terminate("sbx-abc")
294
+
295
+ assert captured["method"] == "POST"
296
+ assert captured["path"] == "/v1/sandboxes/sbx-abc/terminate"
297
+ assert captured["query"]["ownerId"] == "tea-test"
298
+ assert result is None
299
+
300
+
301
+ @pytest.mark.asyncio
302
+ async def test_terminate_raises_not_found_on_404():
303
+ async def handler(request: httpx.Request) -> httpx.Response:
304
+ return httpx.Response(404, json={"message": "not found"})
305
+
306
+ client = _sandbox_client(handler)
307
+ with pytest.raises(SandboxNotFoundError):
308
+ await client.terminate("sbx-missing")
309
+
310
+
311
+ CONNECT_JSON = {
312
+ "executionId": "exc-1",
313
+ "token": "run-token-xyz",
314
+ "uri": "https://proxy.test/runs/stream",
315
+ "method": "POST",
316
+ "expiresAt": "2026-07-21T00:05:00Z",
317
+ }
318
+
319
+
320
+ async def _collect(agen):
321
+ return [event async for event in agen]
322
+
323
+
324
+ async def _noop_handler(request: httpx.Request) -> httpx.Response:
325
+ raise AssertionError("the API client should not be called in this test")
326
+
327
+
328
+ def _patch_proxy(mocker, handler):
329
+ """Patch the fresh httpx.AsyncClient that exec uses for the proxy stream."""
330
+ mocker.patch(
331
+ "render.experimental.sandbox.api.httpx.AsyncClient",
332
+ return_value=httpx.AsyncClient(transport=httpx.MockTransport(handler)),
333
+ )
334
+
335
+
336
+ @pytest.mark.asyncio
337
+ async def test_mint_run_token_requests_the_token_endpoint():
338
+ captured = {}
339
+
340
+ async def handler(request: httpx.Request) -> httpx.Response:
341
+ captured["method"] = request.method
342
+ captured["path"] = request.url.path
343
+ captured["query"] = dict(request.url.params)
344
+ captured["body"] = json.loads(request.content)
345
+ return httpx.Response(201, json=CONNECT_JSON)
346
+
347
+ client = _sandbox_client(handler)
348
+ connection = await client.api._mint_run_token(
349
+ "sbx-123", "tea-test", "stream", "ls -la"
350
+ )
351
+
352
+ assert captured["method"] == "POST"
353
+ assert captured["path"] == "/v1/sandboxes/sbx-123/runs/stream/token"
354
+ assert captured["query"]["ownerId"] == "tea-test"
355
+ assert captured["body"] == {"command": "ls -la"}
356
+ assert connection["token"] == "run-token-xyz" # noqa: S105
357
+ assert connection["uri"] == "https://proxy.test/runs/stream"
358
+
359
+
360
+ @pytest.mark.asyncio
361
+ async def test_exec_streams_from_the_proxy_uri(mocker):
362
+ proxy = {}
363
+ sse = (
364
+ b'event: output\ndata: {"stream":"stdout","data":"hi\\n"}\n\n'
365
+ b'event: output\ndata: {"stream":"stderr","data":"warn\\n"}\n\n'
366
+ b'event: exit\ndata: {"exit_code":7}\n\n'
367
+ )
368
+
369
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
370
+ proxy["method"] = request.method
371
+ proxy["url"] = str(request.url)
372
+ proxy["auth"] = request.headers.get("authorization")
373
+ proxy["accept"] = request.headers.get("accept")
374
+ proxy["body"] = json.loads(request.content)
375
+ return httpx.Response(
376
+ 200, content=sse, headers={"content-type": "text/event-stream"}
377
+ )
378
+
379
+ client = _sandbox_client(_noop_handler)
380
+ mocker.patch.object(
381
+ client.api, "_mint_run_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
382
+ )
383
+ _patch_proxy(mocker, proxy_handler)
384
+
385
+ events = await _collect(client.exec("sbx-123", "echo hi"))
386
+
387
+ assert proxy["method"] == "POST"
388
+ assert proxy["url"] == "https://proxy.test/runs/stream"
389
+ assert proxy["auth"] == "Bearer run-token-xyz"
390
+ assert proxy["accept"] == "text/event-stream"
391
+ assert proxy["body"] == {"command": "echo hi"}
392
+ assert events == [
393
+ SandboxExecOutput(stream="stdout", data="hi\n"),
394
+ SandboxExecOutput(stream="stderr", data="warn\n"),
395
+ SandboxExecExit(exit_code=7),
396
+ ]
397
+
398
+
399
+ @pytest.mark.asyncio
400
+ async def test_exec_error_event_raises_stream_error(mocker):
401
+ sse = b'event: error\ndata: {"status":408,"message":"exec timed out"}\n\n'
402
+
403
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
404
+ return httpx.Response(
405
+ 200, content=sse, headers={"content-type": "text/event-stream"}
406
+ )
407
+
408
+ client = _sandbox_client(_noop_handler)
409
+ mocker.patch.object(
410
+ client.api, "_mint_run_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
411
+ )
412
+ _patch_proxy(mocker, proxy_handler)
413
+
414
+ with pytest.raises(SandboxExecStreamError) as excinfo:
415
+ await _collect(client.exec("sbx-123", "boom"))
416
+ assert excinfo.value.status == 408
417
+ assert excinfo.value.message == "exec timed out"
418
+
419
+
420
+ @pytest.mark.asyncio
421
+ async def test_exec_missing_terminal_event_raises(mocker):
422
+ sse = b'event: output\ndata: {"stream":"stdout","data":"hi\\n"}\n\n'
423
+
424
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
425
+ return httpx.Response(
426
+ 200, content=sse, headers={"content-type": "text/event-stream"}
427
+ )
428
+
429
+ client = _sandbox_client(_noop_handler)
430
+ mocker.patch.object(
431
+ client.api, "_mint_run_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
432
+ )
433
+ _patch_proxy(mocker, proxy_handler)
434
+
435
+ with pytest.raises(SandboxExecError):
436
+ await _collect(client.exec("sbx-123", "hi"))
437
+
438
+
439
+ @pytest.mark.asyncio
440
+ async def test_exec_proxy_non_2xx_raises_client_error(mocker):
441
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
442
+ return httpx.Response(400, text="sandbox not ready")
443
+
444
+ client = _sandbox_client(_noop_handler)
445
+ mocker.patch.object(
446
+ client.api, "_mint_run_token", new=mocker.AsyncMock(return_value=CONNECT_JSON)
447
+ )
448
+ _patch_proxy(mocker, proxy_handler)
449
+
450
+ with pytest.raises(ClientError):
451
+ await _collect(client.exec("sbx-123", "hi"))
452
+
453
+
454
+ @pytest.mark.asyncio
455
+ async def test_exec_mint_404_raises_not_found():
456
+ async def handler(request: httpx.Request) -> httpx.Response:
457
+ return httpx.Response(404, json={"message": "not found"})
458
+
459
+ client = _sandbox_client(handler)
460
+ with pytest.raises(SandboxNotFoundError):
461
+ await _collect(client.exec("sbx-missing", "hi"))
462
+
463
+
464
+ @pytest.mark.asyncio
465
+ async def test_exec_requires_owner_id():
466
+ client = _sandbox_client(_noop_handler, default_owner_id=None)
467
+ with pytest.raises(RenderError):
468
+ await _collect(client.exec("sbx-123", "hi"))
469
+
470
+
471
+ def test_experimental_service_exposes_sandboxes(monkeypatch):
472
+ monkeypatch.setenv("RENDER_API_KEY", "test-token")
473
+ monkeypatch.setenv("RENDER_WORKSPACE_ID", "tea-default")
474
+ monkeypatch.setenv("RENDER_REGION", "oregon")
475
+
476
+ from render.client.client import Client
477
+
478
+ client = Client()
479
+ sandboxes = client.experimental.sandboxes
480
+ assert isinstance(sandboxes, SandboxClient)
481
+ assert sandboxes._default_owner_id == "tea-default"
482
+ assert sandboxes._default_region == "oregon"
483
+
484
+
485
+ def test_sync_experimental_service_exposes_sandboxes(monkeypatch):
486
+ monkeypatch.setenv("RENDER_API_KEY", "test-token")
487
+ monkeypatch.setenv("RENDER_WORKSPACE_ID", "tea-default")
488
+
489
+ from render import Render
490
+ from render.experimental.sandbox.client_sync import SyncSandboxClient
491
+
492
+ render = Render()
493
+ sandboxes = render.experimental.sandboxes
494
+ assert isinstance(sandboxes, SyncSandboxClient)
495
+ assert sandboxes._default_owner_id == "tea-default"
496
+
497
+
498
+ @pytest.mark.asyncio
499
+ async def test_from_id_wraps_non_json_error_response():
500
+ async def handler(request: httpx.Request) -> httpx.Response:
501
+ return httpx.Response(500, text="upstream boom")
502
+
503
+ client = _sandbox_client(handler)
504
+ with pytest.raises(RenderError):
505
+ await client.from_id("sbx-abc")
506
+
507
+
508
+ @pytest.mark.asyncio
509
+ async def test_terminate_wraps_non_json_error_response():
510
+ async def handler(request: httpx.Request) -> httpx.Response:
511
+ return httpx.Response(500, text="upstream boom")
512
+
513
+ client = _sandbox_client(handler)
514
+ with pytest.raises(RenderError):
515
+ await client.terminate("sbx-abc")
516
+
517
+
518
+ def test_render_async_exposes_sandboxes():
519
+ from render import RenderAsync
520
+
521
+ render = RenderAsync(token="test")
522
+ assert isinstance(render.experimental.sandboxes, SandboxClient)
523
+
524
+
525
+ FILES_CONNECT_JSON = {
526
+ "token": "file-token-xyz",
527
+ "uri": "https://proxy.test/files/upload",
528
+ "method": "PUT",
529
+ "expiresAt": "2026-07-21T00:05:00Z",
530
+ }
531
+
532
+
533
+ def _capture_upload(captured):
534
+ async def handler(request: httpx.Request) -> httpx.Response:
535
+ # The body is a stream; it has to be read before .content is available.
536
+ captured["body"] = await request.aread()
537
+ captured["method"] = request.method
538
+ captured["url"] = str(request.url)
539
+ captured["headers"] = dict(request.headers)
540
+ return httpx.Response(204)
541
+
542
+ return handler
543
+
544
+
545
+ @pytest.mark.asyncio
546
+ async def test_mint_file_token_requests_the_files_token_endpoint():
547
+ captured = {}
548
+
549
+ async def handler(request: httpx.Request) -> httpx.Response:
550
+ captured["method"] = request.method
551
+ captured["path"] = request.url.path
552
+ captured["query"] = dict(request.url.params)
553
+ return httpx.Response(201, json=FILES_CONNECT_JSON)
554
+
555
+ client = _sandbox_client(handler)
556
+ connection = await client.api._mint_file_token(
557
+ "sbx-123", "tea-test", "upload", "/app/data"
558
+ )
559
+
560
+ assert captured["method"] == "POST"
561
+ assert captured["path"] == "/v1/sandboxes/sbx-123/files/upload/token"
562
+ assert captured["query"]["ownerId"] == "tea-test"
563
+ assert captured["query"]["path"] == "/app/data"
564
+ assert connection["token"] == "file-token-xyz" # noqa: S105
565
+
566
+
567
+ @pytest.mark.asyncio
568
+ async def test_copy_to_file_puts_raw_bytes_with_a_length(mocker, tmp_path):
569
+ source = tmp_path / "hello.txt"
570
+ source.write_bytes(b"hello sandbox\n")
571
+ captured = {}
572
+
573
+ client = _sandbox_client(_noop_handler)
574
+ mocker.patch.object(
575
+ client.api,
576
+ "_mint_file_token",
577
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
578
+ )
579
+ _patch_proxy(mocker, _capture_upload(captured))
580
+
581
+ await client.copy_to("sbx-123", source, "/app/hello.txt")
582
+
583
+ assert captured["method"] == "PUT"
584
+ assert captured["url"] == "https://proxy.test/files/upload"
585
+ assert captured["headers"]["authorization"] == "Bearer file-token-xyz"
586
+ assert captured["headers"]["content-type"] == "application/octet-stream"
587
+ assert captured["headers"]["content-length"] == "14"
588
+ assert "content-encoding" not in captured["headers"]
589
+ # An explicit length has to win over httpx's chunked default for an
590
+ # iterator body, or the sandbox gets a body it cannot size.
591
+ assert "transfer-encoding" not in captured["headers"]
592
+ assert captured["body"] == b"hello sandbox\n"
593
+
594
+
595
+ @pytest.mark.asyncio
596
+ async def test_copy_to_directory_puts_a_gzipped_tar(mocker, tmp_path):
597
+ (tmp_path / "a.txt").write_text("a\n")
598
+ (tmp_path / "sub").mkdir()
599
+ (tmp_path / "sub" / "b.txt").write_text("b\n")
600
+ (tmp_path / "link.txt").symlink_to("sub/b.txt")
601
+ captured = {}
602
+
603
+ client = _sandbox_client(_noop_handler)
604
+ mocker.patch.object(
605
+ client.api,
606
+ "_mint_file_token",
607
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
608
+ )
609
+ _patch_proxy(mocker, _capture_upload(captured))
610
+
611
+ await client.copy_to("sbx-123", tmp_path, "/app/tree")
612
+
613
+ assert captured["headers"]["content-type"] == "application/x-tar"
614
+ assert captured["headers"]["content-encoding"] == "gzip"
615
+ # The archive's size is not known until it is produced.
616
+ assert "content-length" not in captured["headers"]
617
+
618
+ raw = gzip.decompress(captured["body"])
619
+ with tarfile.open(fileobj=io.BytesIO(raw)) as tar:
620
+ members = {member.name: member for member in tar.getmembers()}
621
+ assert set(members) == {"a.txt", "sub", "sub/b.txt", "link.txt"}
622
+ assert members["link.txt"].issym()
623
+
624
+
625
+ @pytest.mark.asyncio
626
+ async def test_copy_to_accepts_any_2xx(mocker, tmp_path):
627
+ source = tmp_path / "hello.txt"
628
+ source.write_text("hi\n")
629
+
630
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
631
+ await request.aread()
632
+ return httpx.Response(200)
633
+
634
+ client = _sandbox_client(_noop_handler)
635
+ mocker.patch.object(
636
+ client.api,
637
+ "_mint_file_token",
638
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
639
+ )
640
+ _patch_proxy(mocker, proxy_handler)
641
+
642
+ await client.copy_to("sbx-123", source, "/app/hello.txt")
643
+
644
+
645
+ @pytest.mark.asyncio
646
+ async def test_copy_to_proxy_non_2xx_raises_client_error(mocker, tmp_path):
647
+ source = tmp_path / "hello.txt"
648
+ source.write_text("hi\n")
649
+
650
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
651
+ await request.aread()
652
+ return httpx.Response(409, text="path is a directory")
653
+
654
+ client = _sandbox_client(_noop_handler)
655
+ mocker.patch.object(
656
+ client.api,
657
+ "_mint_file_token",
658
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
659
+ )
660
+ _patch_proxy(mocker, proxy_handler)
661
+
662
+ with pytest.raises(ClientError):
663
+ await client.copy_to("sbx-123", source, "/app/hello.txt")
664
+
665
+
666
+ @pytest.mark.asyncio
667
+ async def test_copy_to_mint_404_raises_not_found(tmp_path):
668
+ source = tmp_path / "hello.txt"
669
+ source.write_text("hi\n")
670
+
671
+ async def handler(request: httpx.Request) -> httpx.Response:
672
+ return httpx.Response(404, json={"message": "not found"})
673
+
674
+ client = _sandbox_client(handler)
675
+ with pytest.raises(SandboxNotFoundError):
676
+ await client.copy_to("sbx-missing", source, "/app/hello.txt")
677
+
678
+
679
+ @pytest.mark.asyncio
680
+ async def test_copy_to_missing_local_path_never_mints_a_token(mocker, tmp_path):
681
+ client = _sandbox_client(_noop_handler)
682
+ mint = mocker.patch.object(
683
+ client.api,
684
+ "_mint_file_token",
685
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
686
+ )
687
+
688
+ with pytest.raises(FileNotFoundError):
689
+ await client.copy_to("sbx-123", tmp_path / "absent.txt", "/app/absent.txt")
690
+ mint.assert_not_called()
691
+
692
+
693
+ @pytest.mark.asyncio
694
+ async def test_copy_to_requires_owner_id(tmp_path):
695
+ source = tmp_path / "hello.txt"
696
+ source.write_text("hi\n")
697
+
698
+ client = _sandbox_client(_noop_handler, default_owner_id=None)
699
+ with pytest.raises(RenderError):
700
+ await client.copy_to("sbx-123", source, "/app/hello.txt")
701
+
702
+
703
+ @pytest.mark.asyncio
704
+ async def test_copy_to_rejects_a_fifo_before_minting(mocker, tmp_path):
705
+ fifo = tmp_path / "pipe"
706
+ os.mkfifo(fifo)
707
+
708
+ client = _sandbox_client(_noop_handler)
709
+ mint = mocker.patch.object(
710
+ client.api,
711
+ "_mint_file_token",
712
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
713
+ )
714
+
715
+ # Opening a fifo with no writer would block the event loop forever.
716
+ with pytest.raises(ValueError, match="not a file or directory"):
717
+ await client.copy_to("sbx-123", fifo, "pipe")
718
+ mint.assert_not_called()
719
+
720
+
721
+ @pytest.mark.asyncio
722
+ async def test_copy_to_normalizes_the_remote_path(mocker, tmp_path):
723
+ source = tmp_path / "hello.txt"
724
+ source.write_text("hi\n")
725
+ captured = {}
726
+
727
+ client = _sandbox_client(_noop_handler)
728
+ mint = mocker.patch.object(
729
+ client.api,
730
+ "_mint_file_token",
731
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
732
+ )
733
+ _patch_proxy(mocker, _capture_upload(captured))
734
+
735
+ # The sandbox rejects any path it would have to clean, so a trailing slash
736
+ # or a redundant separator must not reach it.
737
+ await client.copy_to("sbx-123", source, "workspace//data/")
738
+
739
+ assert mint.await_args.args[3] == "workspace/data"
740
+
741
+
742
+ @pytest.mark.asyncio
743
+ async def test_copy_to_closes_the_body_when_the_request_fails(mocker, tmp_path):
744
+ (tmp_path / "a.txt").write_text("a\n")
745
+ closed = []
746
+ # Held so the generator cannot be finalized by garbage collection during
747
+ # the test: `closed` then records only a close upload made itself.
748
+ bodies = []
749
+
750
+ async def _body():
751
+ try:
752
+ yield b"tar bytes"
753
+ finally:
754
+ closed.append(True)
755
+
756
+ def tracked_archive(root):
757
+ body = _body()
758
+ bodies.append(body)
759
+ return body
760
+
761
+ mocker.patch("render.experimental.sandbox.api.aiter_tar_gzip", tracked_archive)
762
+
763
+ async def proxy_handler(request: httpx.Request) -> httpx.Response:
764
+ # Take a chunk so the body is genuinely in flight, then fail the way a
765
+ # dropped connection does mid-upload.
766
+ stream = cast(httpx.AsyncByteStream, request.stream)
767
+ await stream.__aiter__().__anext__()
768
+ raise httpx.ConnectError("connection reset")
769
+
770
+ client = _sandbox_client(_noop_handler)
771
+ mocker.patch.object(
772
+ client.api,
773
+ "_mint_file_token",
774
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
775
+ )
776
+ _patch_proxy(mocker, proxy_handler)
777
+
778
+ with pytest.raises(RenderError):
779
+ await client.copy_to("sbx-123", tmp_path, "tree")
780
+
781
+ # A failed upload must not leave the archive producer parked on its body.
782
+ # httpx happens to close it here too, so this guards the property rather
783
+ # than upload's own close; it would catch httpx changing that.
784
+ assert closed == [True]
785
+
786
+
787
+ @pytest.mark.asyncio
788
+ async def test_copy_to_rejects_an_empty_remote_path(mocker, tmp_path):
789
+ source = tmp_path / "hello.txt"
790
+ source.write_text("hi\n")
791
+
792
+ client = _sandbox_client(_noop_handler)
793
+ mint = mocker.patch.object(
794
+ client.api,
795
+ "_mint_file_token",
796
+ new=mocker.AsyncMock(return_value=FILES_CONNECT_JSON),
797
+ )
798
+
799
+ # Normalizing "" would give ".", quietly targeting the home directory.
800
+ with pytest.raises(ValueError, match="remote_path is required"):
801
+ await client.copy_to("sbx-123", source, "")
802
+ mint.assert_not_called()