render_sdk 0.1.3__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. render_sdk/__init__.py +41 -4
  2. render_sdk/client/__init__.py +25 -0
  3. render_sdk/client/client.py +5 -0
  4. render_sdk/client/sse.py +5 -1
  5. render_sdk/client/tests/test_client.py +6 -4
  6. render_sdk/client/tests/test_sse.py +1 -0
  7. render_sdk/client/workflows.py +10 -2
  8. render_sdk/experimental/__init__.py +31 -0
  9. render_sdk/experimental/experimental.py +71 -0
  10. render_sdk/experimental/object/__init__.py +30 -0
  11. render_sdk/experimental/object/api.py +260 -0
  12. render_sdk/experimental/object/client.py +475 -0
  13. render_sdk/experimental/object/types.py +87 -0
  14. render_sdk/public_api/api/audit_logs/list_organization_audit_logs.py +303 -0
  15. render_sdk/public_api/api/audit_logs/list_owner_audit_logs.py +303 -0
  16. render_sdk/public_api/api/blob_storage/delete_blob.py +215 -0
  17. render_sdk/public_api/api/blob_storage/get_blob.py +221 -0
  18. render_sdk/public_api/api/{workflows/list_workflow_versions.py → blob_storage/list_blobs.py} +52 -30
  19. render_sdk/public_api/api/blob_storage/put_blob.py +248 -0
  20. render_sdk/public_api/api/blueprints/validate_blueprint.py +212 -0
  21. render_sdk/public_api/api/key_value/resume_key_value.py +203 -0
  22. render_sdk/public_api/api/key_value/suspend_key_value.py +203 -0
  23. render_sdk/public_api/api/metrics/get_bandwidth_sources.py +251 -0
  24. render_sdk/public_api/api/postgres/create_postgres_user.py +229 -0
  25. render_sdk/public_api/api/postgres/delete_postgres_user.py +201 -0
  26. render_sdk/public_api/api/postgres/list_postgres_users.py +195 -0
  27. render_sdk/public_api/api/redis_deprecated/__init__.py +1 -0
  28. render_sdk/public_api/api/{redis → redis_deprecated}/create_redis.py +4 -4
  29. render_sdk/public_api/api/{redis → redis_deprecated}/delete_redis.py +4 -4
  30. render_sdk/public_api/api/{redis → redis_deprecated}/list_redis.py +4 -0
  31. render_sdk/public_api/api/{redis → redis_deprecated}/retrieve_redis.py +4 -4
  32. render_sdk/public_api/api/{redis → redis_deprecated}/retrieve_redis_connection_info.py +4 -0
  33. render_sdk/public_api/api/{redis → redis_deprecated}/update_redis.py +4 -4
  34. render_sdk/public_api/api/services/create_service.py +4 -4
  35. render_sdk/public_api/api/workflow_tasks_ea/__init__.py +1 -0
  36. render_sdk/public_api/api/{workflows → workflow_tasks_ea}/cancel_task_run.py +12 -4
  37. render_sdk/public_api/api/{workflows → workflow_tasks_ea}/create_task.py +12 -4
  38. render_sdk/public_api/api/{workflows → workflow_tasks_ea}/get_task.py +12 -4
  39. render_sdk/public_api/api/{workflows → workflow_tasks_ea}/get_task_run.py +12 -4
  40. render_sdk/public_api/api/{workflows → workflow_tasks_ea}/list_task_runs.py +12 -0
  41. render_sdk/public_api/api/{workflows → workflow_tasks_ea}/list_tasks.py +24 -12
  42. render_sdk/public_api/api/workflows_ea/__init__.py +1 -0
  43. render_sdk/public_api/api/workflows_ea/create_workflow.py +199 -0
  44. render_sdk/public_api/api/{workflows/deploy_workflow.py → workflows_ea/create_workflow_version.py} +31 -14
  45. render_sdk/public_api/api/{workflows → workflows_ea}/delete_workflow.py +12 -4
  46. render_sdk/public_api/api/{workflows → workflows_ea}/get_workflow.py +32 -14
  47. render_sdk/public_api/api/{workflows → workflows_ea}/get_workflow_version.py +12 -4
  48. render_sdk/public_api/api/workflows_ea/list_workflow_versions.py +275 -0
  49. render_sdk/public_api/api/{workflows → workflows_ea}/list_workflows.py +41 -14
  50. render_sdk/public_api/api/workflows_ea/update_workflow.py +212 -0
  51. render_sdk/public_api/api/workspaces/remove_workspace_member.py +206 -0
  52. render_sdk/public_api/api/workspaces/update_workspace_member.py +235 -0
  53. render_sdk/public_api/models/__init__.py +82 -4
  54. render_sdk/public_api/models/audit_log.py +113 -0
  55. render_sdk/public_api/models/audit_log_actor.py +80 -0
  56. render_sdk/public_api/models/audit_log_actor_type.py +10 -0
  57. render_sdk/public_api/models/audit_log_event.py +80 -0
  58. render_sdk/public_api/models/audit_log_metadata.py +49 -0
  59. render_sdk/public_api/models/audit_log_status.py +9 -0
  60. render_sdk/public_api/models/audit_log_with_cursor.py +73 -0
  61. render_sdk/public_api/models/background_worker_details.py +2 -2
  62. render_sdk/public_api/models/background_worker_details_patch.py +1 -1
  63. render_sdk/public_api/models/background_worker_details_post.py +1 -1
  64. render_sdk/public_api/models/blob_metadata.py +85 -0
  65. render_sdk/public_api/models/blob_with_cursor.py +73 -0
  66. render_sdk/public_api/models/cache.py +6 -4
  67. render_sdk/public_api/models/cache_profile.py +10 -0
  68. render_sdk/public_api/models/create_deploy_body.py +23 -0
  69. render_sdk/public_api/models/create_version.py +70 -0
  70. render_sdk/public_api/models/credential_create_input.py +59 -0
  71. render_sdk/public_api/models/cron_job_details.py +2 -2
  72. render_sdk/public_api/models/cron_job_details_patch.py +1 -1
  73. render_sdk/public_api/models/cron_job_details_post.py +1 -1
  74. render_sdk/public_api/models/deploy_mode.py +9 -0
  75. render_sdk/public_api/models/event.py +11 -27
  76. render_sdk/public_api/models/event_type.py +1 -1
  77. render_sdk/public_api/models/get_bandwidth_sources_response_200.py +75 -0
  78. render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item.py +101 -0
  79. render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item_labels.py +78 -0
  80. render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item_labels_traffic_source.py +12 -0
  81. render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item_values_item.py +68 -0
  82. render_sdk/public_api/models/{server_unhealthy.py → get_bandwidth_sources_response_400.py} +12 -12
  83. render_sdk/public_api/models/get_blob_output.py +71 -0
  84. render_sdk/public_api/models/list_postgres_users_response_200_item.py +86 -0
  85. render_sdk/public_api/models/otel_provider_type.py +2 -0
  86. render_sdk/public_api/models/postgres.py +8 -0
  87. render_sdk/public_api/models/postgres_detail.py +26 -0
  88. render_sdk/public_api/models/postgres_parameter_overrides.py +44 -0
  89. render_sdk/public_api/models/postgres_patch_input.py +27 -0
  90. render_sdk/public_api/models/postgres_post_input.py +27 -0
  91. render_sdk/public_api/models/postgres_version.py +1 -0
  92. render_sdk/public_api/models/preview_input.py +2 -2
  93. render_sdk/public_api/models/private_service_details.py +2 -2
  94. render_sdk/public_api/models/private_service_details_patch.py +1 -1
  95. render_sdk/public_api/models/private_service_details_post.py +1 -1
  96. render_sdk/public_api/models/project_post_environment_input.py +26 -1
  97. render_sdk/public_api/models/put_blob_input.py +59 -0
  98. render_sdk/public_api/models/put_blob_output.py +79 -0
  99. render_sdk/public_api/models/read_replica.py +25 -1
  100. render_sdk/public_api/models/read_replica_input.py +25 -1
  101. render_sdk/public_api/models/run_task.py +35 -7
  102. render_sdk/public_api/models/service_event.py +12 -27
  103. render_sdk/public_api/models/service_event_type.py +0 -1
  104. render_sdk/public_api/models/service_post.py +9 -6
  105. render_sdk/public_api/models/task_attempt.py +88 -0
  106. render_sdk/public_api/models/task_attempt_details.py +108 -0
  107. render_sdk/public_api/models/task_data_type_1.py +44 -0
  108. render_sdk/public_api/models/task_run.py +23 -1
  109. render_sdk/public_api/models/task_run_details.py +50 -5
  110. render_sdk/public_api/models/task_run_status.py +1 -0
  111. render_sdk/public_api/models/task_with_cursor.py +73 -0
  112. render_sdk/public_api/models/team_member.py +5 -4
  113. render_sdk/public_api/models/team_member_role.py +12 -0
  114. render_sdk/public_api/models/update_workspace_member_body.py +61 -0
  115. render_sdk/public_api/models/validate_blueprint_request.py +84 -0
  116. render_sdk/public_api/models/validate_blueprint_response.py +105 -0
  117. render_sdk/public_api/models/validation_error.py +88 -0
  118. render_sdk/public_api/models/validation_plan_summary.py +107 -0
  119. render_sdk/public_api/models/web_service_details.py +2 -2
  120. render_sdk/public_api/models/web_service_details_patch.py +6 -5
  121. render_sdk/public_api/models/web_service_details_post.py +6 -5
  122. render_sdk/public_api/models/workflow.py +144 -0
  123. render_sdk/public_api/models/workflow_create.py +99 -0
  124. render_sdk/public_api/models/workflow_update.py +90 -0
  125. render_sdk/public_api/models/workflow_version.py +10 -14
  126. render_sdk/public_api/models/workflow_version_status.py +13 -0
  127. render_sdk/public_api/models/workflow_version_with_cursor.py +73 -0
  128. render_sdk/public_api/models/workflow_with_cursor.py +73 -0
  129. render_sdk/render.py +65 -0
  130. render_sdk/version.py +27 -0
  131. render_sdk/workflows/__init__.py +5 -1
  132. render_sdk/workflows/app.py +262 -0
  133. render_sdk/workflows/callback_api/models/task_options.py +18 -0
  134. render_sdk/workflows/cli.py +58 -0
  135. render_sdk/workflows/client.py +2 -7
  136. render_sdk/workflows/runner.py +12 -7
  137. render_sdk/workflows/task.py +11 -2
  138. render_sdk/workflows/tests/test_app.py +412 -0
  139. render_sdk/workflows/tests/test_cli.py +134 -0
  140. render_sdk/workflows/tests/test_end_to_end.py +69 -1
  141. render_sdk/workflows/tests/test_registration.py +56 -1
  142. {render_sdk-0.1.3.dist-info → render_sdk-0.2.0.dist-info}/METADATA +1 -1
  143. {render_sdk-0.1.3.dist-info → render_sdk-0.2.0.dist-info}/RECORD +149 -78
  144. render_sdk-0.2.0.dist-info/entry_points.txt +3 -0
  145. render_sdk/public_api/models/image_version.py +0 -79
  146. /render_sdk/public_api/api/{redis → audit_logs}/__init__.py +0 -0
  147. /render_sdk/public_api/api/{workflows → blob_storage}/__init__.py +0 -0
  148. /render_sdk/public_api/api/{workflows → workflow_tasks_ea}/stream_task_runs_events.py +0 -0
  149. {render_sdk-0.1.3.dist-info → render_sdk-0.2.0.dist-info}/WHEEL +0 -0
  150. {render_sdk-0.1.3.dist-info → render_sdk-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,475 @@
1
+ """High-level object storage client.
2
+
3
+ Provides simple put/get/delete operations for object storage.
4
+ """
5
+
6
+ from typing import TYPE_CHECKING, BinaryIO
7
+
8
+ import httpx
9
+
10
+ from render_sdk.client.errors import RenderError
11
+ from render_sdk.client.util import handle_http_error
12
+ from render_sdk.experimental.object.api import ObjectApi
13
+ from render_sdk.experimental.object.types import (
14
+ ListObjectsResponse,
15
+ ObjectData,
16
+ OwnerID,
17
+ PutObjectResult,
18
+ )
19
+ from render_sdk.public_api.models.region import Region
20
+
21
+ if TYPE_CHECKING:
22
+ from render_sdk.public_api.client import AuthenticatedClient
23
+
24
+
25
+ class ObjectClient:
26
+ """ObjectClient is a high level client for interacting with object storage.
27
+
28
+ It exposes methods to put/get/delete objects.
29
+ """
30
+
31
+ def __init__(self, client: "AuthenticatedClient"):
32
+ self.client = client
33
+ self.api = ObjectApi(client)
34
+
35
+ async def put(
36
+ self,
37
+ *,
38
+ owner_id: OwnerID,
39
+ region: Region | str,
40
+ key: str,
41
+ data: bytes | BinaryIO,
42
+ size: int | None = None,
43
+ content_type: str | None = None,
44
+ ) -> PutObjectResult:
45
+ """Upload an object to storage.
46
+
47
+ Args:
48
+ owner_id: Owner ID (workspace team ID) in format tea-xxxxx
49
+ region: Storage region
50
+ key: Object key (path) for the object
51
+ data: Binary data as bytes or a file-like stream
52
+ size: Size in bytes (optional for bytes, required for streams)
53
+ content_type: MIME type of the content (optional)
54
+
55
+ Returns:
56
+ PutObjectResult: Result with optional ETag
57
+
58
+ Raises:
59
+ RenderError: If size validation fails or upload fails
60
+ ClientError: For 4xx client errors
61
+ ServerError: For 5xx server errors
62
+ TimeoutError: If the request times out
63
+
64
+ Example:
65
+ ```python
66
+ # Upload bytes
67
+ await object_client.put(
68
+ owner_id="tea-xxxxx",
69
+ region="oregon",
70
+ key="path/to/file.png",
71
+ data=b"binary content",
72
+ content_type="image/png"
73
+ )
74
+
75
+ # Upload from file stream
76
+ with open("/path/to/file.zip", "rb") as f:
77
+ import os
78
+ size = os.path.getsize("/path/to/file.zip")
79
+ await object_client.put(
80
+ owner_id="tea-xxxxx",
81
+ region="oregon",
82
+ key="file.zip",
83
+ data=f,
84
+ size=size
85
+ )
86
+ ```
87
+ """
88
+ # Resolve and validate size
89
+ resolved_size = self._resolve_size(data, size)
90
+
91
+ # Convert region to Region enum if it's a string
92
+ region_enum = Region(region) if isinstance(region, str) else region
93
+
94
+ # Step 1: Get presigned upload URL from Render API
95
+ presigned = await self.api.get_upload_url(
96
+ owner_id=owner_id,
97
+ region=region_enum,
98
+ key=key,
99
+ size_bytes=resolved_size,
100
+ )
101
+
102
+ # Step 2: Upload to storage via presigned URL
103
+ headers = {
104
+ "Content-Length": str(resolved_size),
105
+ }
106
+
107
+ if content_type:
108
+ headers["Content-Type"] = content_type
109
+
110
+ async with httpx.AsyncClient() as http_client:
111
+ response = await http_client.put(
112
+ presigned.url,
113
+ headers=headers,
114
+ content=data,
115
+ )
116
+
117
+ handle_http_error(response, "upload object")
118
+
119
+ return PutObjectResult(
120
+ etag=response.headers.get("ETag"),
121
+ )
122
+
123
+ async def get(
124
+ self, *, owner_id: OwnerID, region: Region | str, key: str
125
+ ) -> ObjectData:
126
+ """Download an object from storage.
127
+
128
+ Args:
129
+ owner_id: Owner ID (workspace team ID) in format tea-xxxxx
130
+ region: Storage region
131
+ key: Object key (path) for the object
132
+
133
+ Returns:
134
+ ObjectData: Object data with content
135
+
136
+ Raises:
137
+ ClientError: For 4xx client errors
138
+ ServerError: For 5xx server errors
139
+ TimeoutError: If the request times out
140
+
141
+ Example:
142
+ ```python
143
+ obj = await object_client.get(
144
+ owner_id="tea-xxxxx",
145
+ region="oregon",
146
+ key="path/to/file.png"
147
+ )
148
+
149
+ print(obj.size) # Size in bytes
150
+ print(obj.content_type) # MIME type if available
151
+ # obj.data is bytes
152
+ ```
153
+ """
154
+ # Convert region to Region enum if it's a string
155
+ region_enum = Region(region) if isinstance(region, str) else region
156
+
157
+ # Step 1: Get presigned download URL from Render API
158
+ presigned = await self.api.get_download_url(
159
+ owner_id=owner_id,
160
+ region=region_enum,
161
+ key=key,
162
+ )
163
+
164
+ # Step 2: Download from storage via presigned URL
165
+ async with httpx.AsyncClient() as http_client:
166
+ response = await http_client.get(presigned.url)
167
+
168
+ handle_http_error(response, "download object")
169
+
170
+ data = response.content
171
+
172
+ return ObjectData(
173
+ data=data,
174
+ size=len(data),
175
+ content_type=response.headers.get("Content-Type"),
176
+ )
177
+
178
+ async def delete(
179
+ self, *, owner_id: OwnerID, region: Region | str, key: str
180
+ ) -> None:
181
+ """Delete an object from storage.
182
+
183
+ Args:
184
+ owner_id: Owner ID (workspace team ID) in format tea-xxxxx
185
+ region: Storage region
186
+ key: Object key (path) for the object
187
+
188
+ Raises:
189
+ ClientError: For 4xx client errors
190
+ ServerError: For 5xx server errors
191
+ TimeoutError: If the request times out
192
+
193
+ Example:
194
+ ```python
195
+ await object_client.delete(
196
+ owner_id="tea-xxxxx",
197
+ region="oregon",
198
+ key="path/to/file.png"
199
+ )
200
+ ```
201
+ """
202
+ # Convert region to Region enum if it's a string
203
+ region_enum = Region(region) if isinstance(region, str) else region
204
+
205
+ # DELETE goes directly to Render API (no presigned URL)
206
+ await self.api.delete(
207
+ owner_id=owner_id,
208
+ region=region_enum,
209
+ key=key,
210
+ )
211
+
212
+ async def list(
213
+ self,
214
+ *,
215
+ owner_id: OwnerID,
216
+ region: Region | str,
217
+ cursor: str | None = None,
218
+ limit: int | None = None,
219
+ ) -> ListObjectsResponse:
220
+ """List objects in storage.
221
+
222
+ Args:
223
+ owner_id: Owner ID (workspace team ID) in format tea-xxxxx
224
+ region: Storage region
225
+ cursor: Pagination cursor from previous response
226
+ limit: Maximum number of objects to return (default 20)
227
+
228
+ Returns:
229
+ ListObjectsResponse: List of object metadata with optional next cursor
230
+
231
+ Raises:
232
+ ClientError: For 4xx client errors
233
+ ServerError: For 5xx server errors
234
+ TimeoutError: If the request times out
235
+
236
+ Example:
237
+ ```python
238
+ # List first page
239
+ response = await object_client.list(
240
+ owner_id="tea-xxxxx",
241
+ region="oregon"
242
+ )
243
+
244
+ for obj in response.objects:
245
+ print(f"{obj.key}: {obj.size} bytes")
246
+
247
+ # Get next page if available
248
+ if response.next_cursor:
249
+ next_page = await object_client.list(
250
+ owner_id="tea-xxxxx",
251
+ region="oregon",
252
+ cursor=response.next_cursor
253
+ )
254
+ ```
255
+ """
256
+ # Convert region to Region enum if it's a string
257
+ region_enum = Region(region) if isinstance(region, str) else region
258
+
259
+ return await self.api.list_objects(
260
+ owner_id=owner_id,
261
+ region=region_enum,
262
+ cursor=cursor,
263
+ limit=limit,
264
+ )
265
+
266
+ def scoped(
267
+ self, *, owner_id: OwnerID, region: Region | str
268
+ ) -> "ScopedObjectClient":
269
+ """Create a scoped object client for a specific owner and region.
270
+
271
+ Args:
272
+ owner_id: Owner ID (workspace team ID) in format tea-xxxxx
273
+ region: Storage region
274
+
275
+ Returns:
276
+ ScopedObjectClient: Scoped object client that doesn't require
277
+ owner_id/region on each call
278
+
279
+ Example:
280
+ ```python
281
+ scoped = object_client.scoped(
282
+ owner_id="tea-xxxxx",
283
+ region="oregon"
284
+ )
285
+
286
+ # Subsequent calls only need the key
287
+ await scoped.put(key="file.png", data=buffer)
288
+ await scoped.get(key="file.png")
289
+ await scoped.delete(key="file.png")
290
+ ```
291
+ """
292
+ return ScopedObjectClient(self, owner_id, region)
293
+
294
+ def _resolve_size(self, data: bytes | BinaryIO, size: int | None) -> int:
295
+ """Resolve and validate the size for a put operation.
296
+
297
+ - For bytes: auto-calculate size, validate if provided
298
+ - For streams: require explicit size
299
+
300
+ Args:
301
+ data: Binary data (bytes or stream)
302
+ size: Optional size in bytes
303
+
304
+ Returns:
305
+ int: The size in bytes
306
+
307
+ Raises:
308
+ RenderError: If size validation fails
309
+ """
310
+ if isinstance(data, bytes):
311
+ # Auto-calculate for bytes
312
+ actual_size = len(data)
313
+
314
+ if size is not None and size != actual_size:
315
+ raise RenderError(
316
+ f"Size mismatch: provided size {size} does not match "
317
+ f"actual size {actual_size}"
318
+ )
319
+
320
+ return actual_size
321
+ else:
322
+ # Require explicit size for streams
323
+ if size is None:
324
+ raise RenderError("size is required for stream uploads")
325
+ if size <= 0:
326
+ raise RenderError("size must be a positive integer")
327
+
328
+ return size
329
+
330
+
331
+ class ScopedObjectClient:
332
+ """Scoped Object Client
333
+
334
+ Pre-configured client for a specific owner and region.
335
+ Eliminates the need to specify owner_id and region on every operation.
336
+
337
+ Example:
338
+ ```python
339
+ scoped = object_client.scoped(
340
+ owner_id="tea-xxxxx",
341
+ region="oregon"
342
+ )
343
+
344
+ # Methods have the same signature as ObjectClient but without owner_id/region
345
+ await scoped.put(key="file.png", data=b"content")
346
+ obj = await scoped.get(key="file.png")
347
+ await scoped.delete(key="file.png")
348
+ ```
349
+ """
350
+
351
+ def __init__(
352
+ self, object_client: ObjectClient, owner_id: OwnerID, region: Region | str
353
+ ):
354
+ self._object_client = object_client
355
+ self._owner_id = owner_id
356
+ self._region = region
357
+
358
+ async def put(
359
+ self,
360
+ *,
361
+ key: str,
362
+ data: bytes | BinaryIO,
363
+ size: int | None = None,
364
+ content_type: str | None = None,
365
+ ) -> PutObjectResult:
366
+ """Upload an object to storage using scoped owner and region.
367
+
368
+ Args:
369
+ key: Object key (path) for the object
370
+ data: Binary data as bytes or a file-like stream
371
+ size: Size in bytes (optional for bytes, required for streams)
372
+ content_type: MIME type of the content (optional)
373
+
374
+ Returns:
375
+ PutObjectResult: Result with optional ETag
376
+
377
+ Example:
378
+ ```python
379
+ scoped = object_client.scoped(
380
+ owner_id="tea-xxxxx",
381
+ region="oregon"
382
+ )
383
+ await scoped.put(
384
+ key="file.png",
385
+ data=b"content",
386
+ content_type="image/png"
387
+ )
388
+ ```
389
+ """
390
+ return await self._object_client.put(
391
+ owner_id=self._owner_id,
392
+ region=self._region,
393
+ key=key,
394
+ data=data,
395
+ size=size,
396
+ content_type=content_type,
397
+ )
398
+
399
+ async def get(self, *, key: str) -> ObjectData:
400
+ """Download an object from storage using scoped owner and region.
401
+
402
+ Args:
403
+ key: Object key (path) for the object
404
+
405
+ Returns:
406
+ ObjectData: Object data with content
407
+
408
+ Example:
409
+ ```python
410
+ scoped = object_client.scoped(
411
+ owner_id="tea-xxxxx",
412
+ region="oregon"
413
+ )
414
+ obj = await scoped.get(key="file.png")
415
+ ```
416
+ """
417
+ return await self._object_client.get(
418
+ owner_id=self._owner_id,
419
+ region=self._region,
420
+ key=key,
421
+ )
422
+
423
+ async def delete(self, *, key: str) -> None:
424
+ """Delete an object from storage using scoped owner and region.
425
+
426
+ Args:
427
+ key: Object key (path) for the object
428
+
429
+ Example:
430
+ ```python
431
+ scoped = object_client.scoped(
432
+ owner_id="tea-xxxxx",
433
+ region="oregon"
434
+ )
435
+ await scoped.delete(key="file.png")
436
+ ```
437
+ """
438
+ await self._object_client.delete(
439
+ owner_id=self._owner_id,
440
+ region=self._region,
441
+ key=key,
442
+ )
443
+
444
+ async def list(
445
+ self,
446
+ *,
447
+ cursor: str | None = None,
448
+ limit: int | None = None,
449
+ ) -> ListObjectsResponse:
450
+ """List objects in storage using scoped owner and region.
451
+
452
+ Args:
453
+ cursor: Pagination cursor from previous response
454
+ limit: Maximum number of objects to return (default 20)
455
+
456
+ Returns:
457
+ ListObjectsResponse: List of object metadata with optional next cursor
458
+
459
+ Example:
460
+ ```python
461
+ scoped = object_client.scoped(
462
+ owner_id="tea-xxxxx",
463
+ region="oregon"
464
+ )
465
+ response = await scoped.list()
466
+ for obj in response.objects:
467
+ print(f"{obj.key}: {obj.size} bytes")
468
+ ```
469
+ """
470
+ return await self._object_client.list(
471
+ owner_id=self._owner_id,
472
+ region=self._region,
473
+ cursor=cursor,
474
+ limit=limit,
475
+ )
@@ -0,0 +1,87 @@
1
+ """Type definitions for the experimental object storage API."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+
6
+ # Re-export Region from the generated models for user convenience.
7
+ # While region accepts strings, exporting Region allows users to see
8
+ # available values via IDE autocomplete.
9
+ from render_sdk.public_api.models.region import Region # noqa: F401
10
+
11
+ # Type alias for owner ID format
12
+ OwnerID = str # Should match pattern tea-xxxxx
13
+
14
+
15
+ @dataclass
16
+ class UploadResponse:
17
+ """Response containing upload URL and metadata."""
18
+
19
+ url: str
20
+ """Presigned upload URL"""
21
+
22
+ expires_at: datetime
23
+ """Expiration timestamp"""
24
+
25
+ max_size_bytes: int
26
+ """Maximum size allowed for upload"""
27
+
28
+
29
+ @dataclass
30
+ class DownloadResponse:
31
+ """Response containing download URL and metadata."""
32
+
33
+ url: str
34
+ """Presigned download URL"""
35
+
36
+ expires_at: datetime
37
+ """Expiration timestamp"""
38
+
39
+
40
+ @dataclass
41
+ class ObjectData:
42
+ """Downloaded object data."""
43
+
44
+ data: bytes
45
+ """Binary content"""
46
+
47
+ size: int
48
+ """Size in bytes"""
49
+
50
+ content_type: str | None = None
51
+ """MIME type if available"""
52
+
53
+
54
+ @dataclass
55
+ class PutObjectResult:
56
+ """Result from uploading an object."""
57
+
58
+ etag: str | None = None
59
+ """ETag from storage provider"""
60
+
61
+
62
+ @dataclass
63
+ class ObjectMetadata:
64
+ """Metadata for a stored object."""
65
+
66
+ key: str
67
+ """Object key (path)"""
68
+
69
+ size: int
70
+ """Size in bytes"""
71
+
72
+ last_modified: datetime
73
+ """When the object was last modified"""
74
+
75
+ content_type: str
76
+ """MIME type of the object"""
77
+
78
+
79
+ @dataclass
80
+ class ListObjectsResponse:
81
+ """Response from listing objects."""
82
+
83
+ objects: list[ObjectMetadata]
84
+ """List of object metadata"""
85
+
86
+ next_cursor: str | None = None
87
+ """Cursor for next page, None if no more results"""