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
render_sdk/__init__.py CHANGED
@@ -1,8 +1,45 @@
1
1
  """Render Python SDK
2
2
 
3
- This package provides:
4
- 1. Workflow SDK (render_sdk.workflows) for defining and running tasks
5
- 2. REST API Client (render_sdk.client) for interacting with Render's API
3
+ Task definition (for workers):
4
+
5
+ from render_sdk import Workflows, Retry
6
+
7
+ app = Workflows(auto_start=True)
8
+
9
+ @app.task
10
+ def my_task(x: int) -> int:
11
+ return x * 2
12
+
13
+ REST API access (for clients):
14
+
15
+ from render_sdk import Render
16
+
17
+ render = Render()
18
+ result = await render.workflows.run_task("my-workflow/my_task", [5])
6
19
  """
7
20
 
8
- __version__ = "0.1.3"
21
+ __version__ = "0.2.0"
22
+
23
+ # Primary user-facing APIs
24
+ from render_sdk.render import Render
25
+ from render_sdk.workflows import Options, Retry, Workflows, start, task
26
+
27
+ __all__ = [
28
+ "__version__",
29
+ # Primary APIs
30
+ "Render", # REST API client
31
+ "Workflows", # Task definition
32
+ # Configuration
33
+ "Options",
34
+ "Retry",
35
+ # Deprecated: use Workflows.task and Workflows.start() instead
36
+ "start",
37
+ "task",
38
+ ]
39
+
40
+ # Direct client access available via:
41
+ # render.client # Access from existing Render instance
42
+ # from render_sdk.client import Client # Or import directly
43
+ #
44
+ # Raw API access available via:
45
+ # from render_sdk.public_api import AuthenticatedClient
@@ -16,6 +16,19 @@ from render_sdk.client.types import (
16
16
  TaskRunStatus,
17
17
  )
18
18
  from render_sdk.client.workflows import WorkflowsService
19
+ from render_sdk.experimental import (
20
+ DownloadResponse,
21
+ ExperimentalService,
22
+ ObjectApi,
23
+ ObjectClient,
24
+ ObjectData,
25
+ OwnerID,
26
+ PutObjectResult,
27
+ Region,
28
+ ScopedObjectClient,
29
+ StorageService,
30
+ UploadResponse,
31
+ )
19
32
 
20
33
  __all__ = [
21
34
  "Client",
@@ -29,4 +42,16 @@ __all__ = [
29
42
  "LimitParam",
30
43
  "CursorParam",
31
44
  "OwnerIDParam",
45
+ # Experimental exports
46
+ "ExperimentalService",
47
+ "StorageService",
48
+ "ObjectApi",
49
+ "ObjectClient",
50
+ "ScopedObjectClient",
51
+ "DownloadResponse",
52
+ "ObjectData",
53
+ "OwnerID",
54
+ "PutObjectResult",
55
+ "Region",
56
+ "UploadResponse",
32
57
  ]
@@ -7,7 +7,9 @@ import os
7
7
 
8
8
  from render_sdk.client.sse import SSEClient
9
9
  from render_sdk.client.workflows import WorkflowsService
10
+ from render_sdk.experimental.experimental import ExperimentalService
10
11
  from render_sdk.public_api.client import AuthenticatedClient
12
+ from render_sdk.version import get_user_agent
11
13
 
12
14
 
13
15
  class Client:
@@ -21,6 +23,7 @@ class Client:
21
23
  token: The authentication token
22
24
  base_url: The API base URL
23
25
  workflows: Service client for workflow operations
26
+ experimental: Service client for experimental features
24
27
  """
25
28
 
26
29
  token: str
@@ -71,8 +74,10 @@ class Client:
71
74
  self.internal = AuthenticatedClient(
72
75
  base_url=api_base,
73
76
  token=self.token,
77
+ headers={"User-Agent": get_user_agent()},
74
78
  )
75
79
 
76
80
  # Initialize service clients
77
81
  self.workflows = WorkflowsService(self)
78
82
  self.sse = SSEClient(self)
83
+ self.experimental = ExperimentalService(self)
render_sdk/client/sse.py CHANGED
@@ -12,7 +12,10 @@ import httpx
12
12
 
13
13
  from render_sdk.client.types import TaskRunDetails
14
14
  from render_sdk.client.util import handle_http_error, handle_httpx_exception
15
- from render_sdk.public_api.api.workflows.stream_task_runs_events import _get_kwargs
15
+ from render_sdk.public_api.api.workflow_tasks_ea.stream_task_runs_events import (
16
+ _get_kwargs,
17
+ )
18
+ from render_sdk.version import get_user_agent
16
19
 
17
20
  if TYPE_CHECKING:
18
21
  from render_sdk.client.client import Client
@@ -58,6 +61,7 @@ class SSEClient:
58
61
  "Accept": "text/event-stream",
59
62
  "Cache-Control": "no-cache",
60
63
  "Authorization": f"Bearer {self.client.token}",
64
+ "User-Agent": get_user_agent(),
61
65
  }
62
66
  )
63
67
 
@@ -25,6 +25,7 @@ def mock_task_run(mocker):
25
25
  parent_task_run_id=None,
26
26
  root_task_run_id=None,
27
27
  retries=0,
28
+ attempts=[],
28
29
  started_at=None,
29
30
  task_id=None,
30
31
  )
@@ -42,6 +43,7 @@ def mock_task_run_details(mocker):
42
43
  parent_task_run_id=None,
43
44
  root_task_run_id=None,
44
45
  retries=0,
46
+ attempts=[],
45
47
  started_at=None,
46
48
  task_id=None,
47
49
  )
@@ -74,28 +76,28 @@ def workflows_service(client):
74
76
  @pytest.fixture
75
77
  def mock_cancel_task_run_asyncio(mocker):
76
78
  return mocker.patch(
77
- "render_sdk.public_api.api.workflows.cancel_task_run.asyncio_detailed"
79
+ "render_sdk.public_api.api.workflow_tasks_ea.cancel_task_run.asyncio_detailed"
78
80
  )
79
81
 
80
82
 
81
83
  @pytest.fixture
82
84
  def mock_list_task_runs_asyncio(mocker):
83
85
  return mocker.patch(
84
- "render_sdk.public_api.api.workflows.list_task_runs.asyncio_detailed"
86
+ "render_sdk.public_api.api.workflow_tasks_ea.list_task_runs.asyncio_detailed"
85
87
  )
86
88
 
87
89
 
88
90
  @pytest.fixture
89
91
  def mock_create_task_asyncio(mocker):
90
92
  return mocker.patch(
91
- "render_sdk.public_api.api.workflows.create_task.asyncio_detailed"
93
+ "render_sdk.public_api.api.workflow_tasks_ea.create_task.asyncio_detailed"
92
94
  )
93
95
 
94
96
 
95
97
  @pytest.fixture
96
98
  def mock_get_task_run_asyncio(mocker):
97
99
  return mocker.patch(
98
- "render_sdk.public_api.api.workflows.get_task_run.asyncio_detailed"
100
+ "render_sdk.public_api.api.workflow_tasks_ea.get_task_run.asyncio_detailed"
99
101
  )
100
102
 
101
103
 
@@ -38,6 +38,7 @@ async def test_parse_stream_completed():
38
38
  parent_task_run_id="trn-test123",
39
39
  root_task_run_id="trn-test123",
40
40
  retries=0,
41
+ attempts=[],
41
42
  )
42
43
  sse_data = [
43
44
  b"event: task.completed\ndata: "
@@ -15,7 +15,7 @@ from render_sdk.client.types import (
15
15
  TaskRunStatusValues,
16
16
  )
17
17
  from render_sdk.client.util import handle_http_errors, retry_with_backoff
18
- from render_sdk.public_api.api.workflows import (
18
+ from render_sdk.public_api.api.workflow_tasks_ea import (
19
19
  cancel_task_run,
20
20
  create_task,
21
21
  get_task_run,
@@ -23,6 +23,7 @@ from render_sdk.public_api.api.workflows import (
23
23
  )
24
24
  from render_sdk.public_api.models.error import Error
25
25
  from render_sdk.public_api.models.run_task import RunTask
26
+ from render_sdk.public_api.models.task_data_type_1 import TaskDataType1
26
27
  from render_sdk.public_api.types import UNSET, Response
27
28
 
28
29
  if TYPE_CHECKING:
@@ -149,10 +150,17 @@ class WorkflowsService:
149
150
  self, task_identifier: TaskIdentifier, input_data: TaskData
150
151
  ) -> Response[Error | TaskRun]:
151
152
  """Internal method to make the create task API call."""
153
+ # Convert dict to TaskDataType1 for named parameters
154
+ task_data_input: TaskDataType1 | list[Any]
155
+ if isinstance(input_data, dict):
156
+ task_data_input = TaskDataType1.from_dict(input_data)
157
+ else:
158
+ task_data_input = input_data
159
+
152
160
  # Create the request body
153
161
  run_task = RunTask(
154
162
  task=task_identifier,
155
- input_=input_data,
163
+ input_=task_data_input,
156
164
  )
157
165
 
158
166
  # Make the API call
@@ -0,0 +1,31 @@
1
+ """Experimental API exports."""
2
+
3
+ from render_sdk.experimental.experimental import ExperimentalService, StorageService
4
+ from render_sdk.experimental.object import (
5
+ DownloadResponse,
6
+ ObjectApi,
7
+ ObjectClient,
8
+ ObjectData,
9
+ OwnerID,
10
+ PutObjectResult,
11
+ Region,
12
+ ScopedObjectClient,
13
+ UploadResponse,
14
+ )
15
+
16
+ __all__ = [
17
+ # Experimental Service
18
+ "ExperimentalService",
19
+ "StorageService",
20
+ # Object API classes
21
+ "ObjectApi",
22
+ "ObjectClient",
23
+ "ScopedObjectClient",
24
+ # Object types
25
+ "DownloadResponse",
26
+ "ObjectData",
27
+ "OwnerID",
28
+ "PutObjectResult",
29
+ "Region",
30
+ "UploadResponse",
31
+ ]
@@ -0,0 +1,71 @@
1
+ """Experimental Service
2
+
3
+ This module provides the ExperimentalService class that exposes experimental features
4
+ from the Render SDK.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ from render_sdk.experimental.object.client import ObjectClient
10
+
11
+ if TYPE_CHECKING:
12
+ from render_sdk.client.client import Client
13
+
14
+
15
+ class StorageService:
16
+ """Storage Service
17
+
18
+ Provides access to experimental storage features.
19
+
20
+ Example:
21
+ ```python
22
+ # Access object storage
23
+ await client.experimental.storage.objects.put(
24
+ owner_id="tea-xxxxx",
25
+ region="oregon",
26
+ key="file.png",
27
+ data=b"content"
28
+ )
29
+ ```
30
+ """
31
+
32
+ def __init__(self, client: "Client"):
33
+ """Initialize the storage service.
34
+
35
+ Args:
36
+ client: The Render client instance
37
+ """
38
+ self.objects = ObjectClient(client.internal)
39
+
40
+
41
+ class ExperimentalService:
42
+ """Experimental Service
43
+
44
+ Provides access to experimental Render SDK features.
45
+
46
+ Features in this namespace may change or be removed without a migration plan.
47
+ When a feature stabilizes, it will be promoted to the main SDK namespace.
48
+
49
+ Example:
50
+ ```python
51
+ from render_sdk.client import Client
52
+
53
+ client = Client()
54
+
55
+ # Access experimental object storage
56
+ await client.experimental.storage.objects.put(
57
+ owner_id="tea-xxxxx",
58
+ region="oregon",
59
+ key="file.png",
60
+ data=b"content"
61
+ )
62
+ ```
63
+ """
64
+
65
+ def __init__(self, client: "Client"):
66
+ """Initialize the experimental service.
67
+
68
+ Args:
69
+ client: The Render client instance
70
+ """
71
+ self.storage = StorageService(client)
@@ -0,0 +1,30 @@
1
+ """Object storage API exports."""
2
+
3
+ from render_sdk.experimental.object.api import ObjectApi
4
+ from render_sdk.experimental.object.client import ObjectClient, ScopedObjectClient
5
+ from render_sdk.experimental.object.types import (
6
+ DownloadResponse,
7
+ ListObjectsResponse,
8
+ ObjectData,
9
+ ObjectMetadata,
10
+ OwnerID,
11
+ PutObjectResult,
12
+ Region,
13
+ UploadResponse,
14
+ )
15
+
16
+ __all__ = [
17
+ # API classes
18
+ "ObjectApi",
19
+ "ObjectClient",
20
+ "ScopedObjectClient",
21
+ # Types
22
+ "DownloadResponse",
23
+ "ListObjectsResponse",
24
+ "ObjectData",
25
+ "ObjectMetadata",
26
+ "OwnerID",
27
+ "PutObjectResult",
28
+ "Region",
29
+ "UploadResponse",
30
+ ]
@@ -0,0 +1,260 @@
1
+ """Layer 2: Typed Object API Client
2
+
3
+ Provides idiomatic Python wrapper around the raw OpenAPI client.
4
+ Handles presigned URL flow but still exposes the two-step nature
5
+ (get URL, then upload/download). Useful for advanced use cases
6
+ requiring fine-grained control.
7
+ """
8
+
9
+ import builtins
10
+ from typing import TYPE_CHECKING
11
+
12
+ from render_sdk.client.errors import RenderError
13
+ from render_sdk.client.util import handle_http_errors
14
+ from render_sdk.experimental.object.types import (
15
+ DownloadResponse,
16
+ ListObjectsResponse,
17
+ ObjectMetadata,
18
+ UploadResponse,
19
+ )
20
+ from render_sdk.public_api.api.blob_storage import (
21
+ delete_blob,
22
+ get_blob,
23
+ list_blobs,
24
+ put_blob,
25
+ )
26
+ from render_sdk.public_api.models.blob_with_cursor import BlobWithCursor
27
+ from render_sdk.public_api.models.error import Error
28
+ from render_sdk.public_api.models.get_blob_output import GetBlobOutput
29
+ from render_sdk.public_api.models.put_blob_input import (
30
+ PutBlobInput as PutBlobInputModel,
31
+ )
32
+ from render_sdk.public_api.models.put_blob_output import PutBlobOutput
33
+ from render_sdk.public_api.models.region import Region
34
+ from render_sdk.public_api.types import UNSET, Response
35
+
36
+ if TYPE_CHECKING:
37
+ from render_sdk.public_api.client import AuthenticatedClient
38
+
39
+
40
+ class ObjectApi:
41
+ """Layer 2: Typed Object API Client
42
+
43
+ Provides idiomatic Python wrapper around the raw OpenAPI client.
44
+ Handles presigned URL flow but still exposes the two-step nature.
45
+ """
46
+
47
+ def __init__(self, client: "AuthenticatedClient"):
48
+ self.client = client
49
+
50
+ async def get_upload_url(
51
+ self,
52
+ owner_id: str,
53
+ region: Region | str,
54
+ key: str,
55
+ size_bytes: int,
56
+ ) -> UploadResponse:
57
+ """Get a presigned URL for uploading an object.
58
+
59
+ Args:
60
+ owner_id: Owner ID (workspace team ID)
61
+ region: Storage region
62
+ key: Object key (path)
63
+ size_bytes: Size of the object in bytes
64
+
65
+ Returns:
66
+ UploadResponse: Upload URL with expiration and size limit
67
+
68
+ Raises:
69
+ ClientError: For 4xx client errors
70
+ ServerError: For 5xx server errors
71
+ TimeoutError: If the request times out
72
+ """
73
+ response = await self._get_upload_url_api_call(
74
+ owner_id, region, key, size_bytes
75
+ )
76
+
77
+ if not isinstance(response.parsed, PutBlobOutput):
78
+ raise RenderError("Failed to get upload URL: unexpected response type")
79
+
80
+ return UploadResponse(
81
+ url=response.parsed.url,
82
+ expires_at=response.parsed.expires_at,
83
+ max_size_bytes=response.parsed.max_size_bytes,
84
+ )
85
+
86
+ @handle_http_errors("get upload URL")
87
+ async def _get_upload_url_api_call(
88
+ self,
89
+ owner_id: str,
90
+ region: Region | str,
91
+ key: str,
92
+ size_bytes: int,
93
+ ) -> Response[Error | PutBlobOutput]:
94
+ """Internal method to make the get upload URL API call."""
95
+ # Convert region to Region enum if it's a string
96
+ if isinstance(region, str):
97
+ region = Region(region)
98
+
99
+ body = PutBlobInputModel(size_bytes=size_bytes)
100
+
101
+ return await put_blob.asyncio_detailed(
102
+ owner_id=owner_id,
103
+ region=region,
104
+ key=key,
105
+ client=self.client,
106
+ body=body,
107
+ )
108
+
109
+ async def get_download_url(
110
+ self,
111
+ owner_id: str,
112
+ region: Region | str,
113
+ key: str,
114
+ ) -> DownloadResponse:
115
+ """Get a presigned URL for downloading an object.
116
+
117
+ Args:
118
+ owner_id: Owner ID (workspace team ID)
119
+ region: Storage region
120
+ key: Object key (path)
121
+
122
+ Returns:
123
+ DownloadResponse: Download URL with expiration
124
+
125
+ Raises:
126
+ ClientError: For 4xx client errors
127
+ ServerError: For 5xx server errors
128
+ TimeoutError: If the request times out
129
+ """
130
+ response = await self._get_download_url_api_call(owner_id, region, key)
131
+
132
+ if not isinstance(response.parsed, GetBlobOutput):
133
+ raise RenderError("Failed to get download URL: unexpected response type")
134
+
135
+ return DownloadResponse(
136
+ url=response.parsed.url,
137
+ expires_at=response.parsed.expires_at,
138
+ )
139
+
140
+ @handle_http_errors("get download URL")
141
+ async def _get_download_url_api_call(
142
+ self,
143
+ owner_id: str,
144
+ region: Region | str,
145
+ key: str,
146
+ ) -> Response[Error | GetBlobOutput]:
147
+ """Internal method to make the get download URL API call."""
148
+ # Convert region to Region enum if it's a string
149
+ if isinstance(region, str):
150
+ region = Region(region)
151
+
152
+ return await get_blob.asyncio_detailed(
153
+ owner_id=owner_id,
154
+ region=region,
155
+ key=key,
156
+ client=self.client,
157
+ )
158
+
159
+ async def delete(
160
+ self,
161
+ owner_id: str,
162
+ region: Region | str,
163
+ key: str,
164
+ ) -> None:
165
+ """Delete an object.
166
+
167
+ Args:
168
+ owner_id: Owner ID (workspace team ID)
169
+ region: Storage region
170
+ key: Object key (path)
171
+
172
+ Raises:
173
+ ClientError: For 4xx client errors
174
+ ServerError: For 5xx server errors
175
+ TimeoutError: If the request times out
176
+ """
177
+ await self._delete_api_call(owner_id, region, key)
178
+
179
+ @handle_http_errors("delete object")
180
+ async def _delete_api_call(
181
+ self,
182
+ owner_id: str,
183
+ region: Region | str,
184
+ key: str,
185
+ ) -> Response[Error | None]:
186
+ """Internal method to make the delete object API call."""
187
+ # Convert region to Region enum if it's a string
188
+ if isinstance(region, str):
189
+ region = Region(region)
190
+
191
+ return await delete_blob.asyncio_detailed(
192
+ owner_id=owner_id,
193
+ region=region,
194
+ key=key,
195
+ client=self.client,
196
+ )
197
+
198
+ async def list_objects(
199
+ self,
200
+ owner_id: str,
201
+ region: Region | str,
202
+ cursor: str | None = None,
203
+ limit: int | None = None,
204
+ ) -> ListObjectsResponse:
205
+ """List objects in storage.
206
+
207
+ Args:
208
+ owner_id: Owner ID (workspace team ID)
209
+ region: Storage region
210
+ cursor: Pagination cursor from previous response
211
+ limit: Maximum number of objects to return (default 20)
212
+
213
+ Returns:
214
+ ListObjectsResponse: List of object metadata with optional next cursor
215
+
216
+ Raises:
217
+ ClientError: For 4xx client errors
218
+ ServerError: For 5xx server errors
219
+ TimeoutError: If the request times out
220
+ """
221
+ response = await self._list_objects_api_call(owner_id, region, cursor, limit)
222
+
223
+ if not isinstance(response.parsed, builtins.list):
224
+ raise RenderError("Failed to list objects: unexpected response type")
225
+
226
+ objects = [
227
+ ObjectMetadata(
228
+ key=item.blob.key,
229
+ size=item.blob.size_bytes,
230
+ last_modified=item.blob.last_modified,
231
+ content_type=item.blob.content_type,
232
+ )
233
+ for item in response.parsed
234
+ ]
235
+
236
+ # The cursor for the next page is the cursor of the last item
237
+ next_cursor = response.parsed[-1].cursor if response.parsed else None
238
+
239
+ return ListObjectsResponse(objects=objects, next_cursor=next_cursor)
240
+
241
+ @handle_http_errors("list objects")
242
+ async def _list_objects_api_call(
243
+ self,
244
+ owner_id: str,
245
+ region: Region | str,
246
+ cursor: str | None,
247
+ limit: int | None,
248
+ ) -> Response[Error | builtins.list[BlobWithCursor]]:
249
+ """Internal method to make the list objects API call."""
250
+ # Convert region to Region enum if it's a string
251
+ if isinstance(region, str):
252
+ region = Region(region)
253
+
254
+ return await list_blobs.asyncio_detailed(
255
+ owner_id=owner_id,
256
+ region=region,
257
+ cursor=cursor if cursor is not None else UNSET,
258
+ limit=limit if limit is not None else UNSET,
259
+ client=self.client,
260
+ )