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,262 @@
1
+ """Workflows application for defining durable tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import atexit
6
+ import os
7
+ import warnings
8
+ from collections.abc import Callable
9
+ from typing import Any, TypeVar
10
+
11
+ from render_sdk.workflows.runner import register, run
12
+ from render_sdk.workflows.task import (
13
+ Options,
14
+ Retry,
15
+ TaskCallable,
16
+ TaskRegistry,
17
+ create_task_decorator,
18
+ )
19
+
20
+ F = TypeVar("F", bound=Callable[..., Any])
21
+
22
+ # Track whether auto_start has been registered
23
+ _auto_start_app_registered = False
24
+
25
+
26
+ class Workflows:
27
+ """
28
+ Task definition app for Render Durable Workflows.
29
+
30
+ This is the primary entry point for defining tasks that run on Render.
31
+ For calling tasks via REST API, use the Render class instead.
32
+
33
+ Example:
34
+ app = Workflows(auto_start=True)
35
+
36
+ @app.task
37
+ def my_task(x: int) -> int:
38
+ return x * 2
39
+
40
+ With configuration:
41
+ app = Workflows(
42
+ default_retry=Retry(max_retries=3, wait_duration_ms=1000),
43
+ default_timeout=300,
44
+ auto_start=True,
45
+ )
46
+
47
+ @app.task
48
+ def my_task(x: int) -> int:
49
+ return x * 2
50
+
51
+ Combining multiple modules:
52
+ from tasks_a import app as app_a
53
+ from tasks_b import app as app_b
54
+
55
+ combined = Workflows.from_workflows(app_a, app_b, auto_start=True)
56
+ """
57
+
58
+ _registry: TaskRegistry
59
+ _default_retry: Retry | None
60
+ _default_timeout: int | None
61
+ _default_plan: str | None
62
+ _auto_start: bool
63
+ _started: bool
64
+
65
+ def __init__(
66
+ self,
67
+ *,
68
+ default_retry: Retry | None = None,
69
+ default_timeout: int | None = None,
70
+ default_plan: str | None = None,
71
+ auto_start: bool = False,
72
+ ) -> None:
73
+ """
74
+ Initialize a Workflows application.
75
+
76
+ Args:
77
+ default_retry: Default retry configuration for all tasks.
78
+ default_timeout: Default timeout in seconds for all tasks.
79
+ default_plan: Default resource plan for all tasks.
80
+ auto_start: If True, automatically starts the worker on program exit
81
+ when RENDER_SDK_MODE is set.
82
+ """
83
+ global _auto_start_app_registered
84
+
85
+ self._registry = TaskRegistry()
86
+ self._default_retry = default_retry
87
+ self._default_timeout = default_timeout
88
+ self._default_plan = default_plan
89
+ self._auto_start = auto_start
90
+ self._started = False
91
+
92
+ if auto_start:
93
+ if _auto_start_app_registered:
94
+ warnings.warn(
95
+ "Multiple Workflows instances with auto_start=True detected. "
96
+ "Only one instance's tasks will be executed per invocation.",
97
+ UserWarning,
98
+ stacklevel=2,
99
+ )
100
+ _auto_start_app_registered = True
101
+ atexit.register(self._maybe_start)
102
+
103
+ def __repr__(self) -> str:
104
+ task_count = len(self._registry.get_task_names())
105
+ return f"Workflows(tasks={task_count}, auto_start={self._auto_start})"
106
+
107
+ def _maybe_start(self) -> None:
108
+ """Start the worker if not already started and environment is configured."""
109
+
110
+ if self._started:
111
+ return
112
+
113
+ mode = os.environ.get("RENDER_SDK_MODE")
114
+ socket_path = os.environ.get("RENDER_SDK_SOCKET_PATH")
115
+
116
+ # Only start if both environment variables are set
117
+ if mode and socket_path:
118
+ self.start()
119
+
120
+ def task(
121
+ self,
122
+ func: F | None = None,
123
+ *,
124
+ name: str | None = None,
125
+ retry: Retry | None = None,
126
+ timeout: int | None = None,
127
+ plan: str | None = None,
128
+ ) -> F | Callable[[F], TaskCallable]:
129
+ """
130
+ Decorator to register a function as a task.
131
+
132
+ Args:
133
+ func: The function to decorate (when used without parentheses).
134
+ name: Optional custom name for the task (defaults to function name).
135
+ retry: Retry configuration (overrides default_retry).
136
+ timeout: Timeout in seconds (overrides default_timeout).
137
+ plan: Resource plan (overrides default_plan).
138
+
139
+ Returns:
140
+ The decorated function as a TaskCallable.
141
+
142
+ Example:
143
+ @app.task
144
+ def simple_task(x: int) -> int:
145
+ return x * 2
146
+
147
+ @app.task(timeout=60, plan="starter")
148
+ def quick_task(x: int) -> int:
149
+ return x + 1
150
+ """
151
+ # Build options from defaults and overrides
152
+ effective_retry = retry if retry is not None else self._default_retry
153
+ effective_timeout = timeout if timeout is not None else self._default_timeout
154
+ effective_plan = plan if plan is not None else self._default_plan
155
+
156
+ options = Options(
157
+ retry=effective_retry,
158
+ timeout_seconds=effective_timeout,
159
+ plan=effective_plan,
160
+ )
161
+
162
+ # Create the task decorator bound to this app's registry
163
+ task_decorator = create_task_decorator(self._registry)
164
+
165
+ def decorator(f: F) -> TaskCallable:
166
+ return task_decorator(f, name=name, options=options)
167
+
168
+ if func is None:
169
+ # Called with arguments: @app.task(name="...", timeout=30)
170
+ return decorator
171
+ # Called without arguments: @app.task
172
+ return decorator(func)
173
+
174
+ def start(self) -> None:
175
+ """
176
+ Start the workflow worker.
177
+
178
+ Reads RENDER_SDK_MODE and RENDER_SDK_SOCKET_PATH environment variables
179
+ to determine whether to run tasks or register them.
180
+
181
+ In most cases, use auto_start=True instead of calling this manually.
182
+
183
+ Raises:
184
+ ValueError: If required environment variables are not set.
185
+ """
186
+ import os
187
+
188
+ mode = os.environ.get("RENDER_SDK_MODE")
189
+ socket_path = os.environ.get("RENDER_SDK_SOCKET_PATH")
190
+
191
+ if not mode:
192
+ raise ValueError("RENDER_SDK_MODE environment variable is required")
193
+
194
+ if not socket_path:
195
+ raise ValueError("RENDER_SDK_SOCKET_PATH environment variable is required")
196
+
197
+ self._started = True
198
+
199
+ # Copy tasks to global registry for the runner to use
200
+ from render_sdk.workflows.task import _global_registry
201
+
202
+ for task_name in self._registry.get_task_names():
203
+ task_info = self._registry.get_task(task_name)
204
+ if task_info and task_name not in _global_registry._tasks:
205
+ _global_registry._tasks[task_name] = task_info
206
+
207
+ if mode == "run":
208
+ run(socket_path)
209
+ elif mode == "register":
210
+ register(socket_path)
211
+ else:
212
+ raise ValueError(f"Unknown mode: {mode}")
213
+
214
+ @classmethod
215
+ def from_workflows(
216
+ cls,
217
+ *apps: Workflows,
218
+ default_retry: Retry | None = None,
219
+ default_timeout: int | None = None,
220
+ default_plan: str | None = None,
221
+ auto_start: bool = False,
222
+ ) -> Workflows:
223
+ """
224
+ Combine multiple Workflows apps into one.
225
+
226
+ This is useful for organizing tasks across multiple modules.
227
+
228
+ Args:
229
+ *apps: Workflows instances to combine.
230
+ default_retry: Default retry for new tasks on combined app.
231
+ default_timeout: Default timeout for new tasks on combined app.
232
+ default_plan: Default plan for new tasks on combined app.
233
+ auto_start: If True, auto-start the combined app.
234
+
235
+ Returns:
236
+ A new Workflows instance with all tasks from the input apps.
237
+
238
+ Example:
239
+ from tasks_a import app as app_a
240
+ from tasks_b import app as app_b
241
+
242
+ combined = Workflows.from_workflows(app_a, app_b, auto_start=True)
243
+ """
244
+ combined = cls(
245
+ default_retry=default_retry,
246
+ default_timeout=default_timeout,
247
+ default_plan=default_plan,
248
+ auto_start=auto_start,
249
+ )
250
+
251
+ # Copy tasks from all apps
252
+ for app in apps:
253
+ for task_name in app._registry.get_task_names():
254
+ task_info = app._registry.get_task(task_name)
255
+ if task_info:
256
+ if task_name in combined._registry._tasks:
257
+ raise ValueError(
258
+ f"Task '{task_name}' is defined in multiple apps"
259
+ )
260
+ combined._registry._tasks[task_name] = task_info
261
+
262
+ return combined
@@ -18,9 +18,13 @@ class TaskOptions:
18
18
  """
19
19
  Attributes:
20
20
  retry (Union[Unset, RetryConfig]):
21
+ timeout_seconds (Union[Unset, int]): Task execution timeout in seconds (30-86400)
22
+ plan (Union[Unset, str]): Resource plan for task execution
21
23
  """
22
24
 
23
25
  retry: Union[Unset, "RetryConfig"] = UNSET
26
+ timeout_seconds: Union[Unset, int] = UNSET
27
+ plan: Union[Unset, str] = UNSET
24
28
  additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
25
29
 
26
30
  def to_dict(self) -> dict[str, Any]:
@@ -28,11 +32,19 @@ class TaskOptions:
28
32
  if not isinstance(self.retry, Unset):
29
33
  retry = self.retry.to_dict()
30
34
 
35
+ timeout_seconds = self.timeout_seconds
36
+
37
+ plan = self.plan
38
+
31
39
  field_dict: dict[str, Any] = {}
32
40
  field_dict.update(self.additional_properties)
33
41
  field_dict.update({})
34
42
  if retry is not UNSET:
35
43
  field_dict["retry"] = retry
44
+ if timeout_seconds is not UNSET:
45
+ field_dict["timeout_seconds"] = timeout_seconds
46
+ if plan is not UNSET:
47
+ field_dict["plan"] = plan
36
48
 
37
49
  return field_dict
38
50
 
@@ -48,8 +60,14 @@ class TaskOptions:
48
60
  else:
49
61
  retry = RetryConfig.from_dict(_retry)
50
62
 
63
+ timeout_seconds = d.pop("timeout_seconds", UNSET)
64
+
65
+ plan = d.pop("plan", UNSET)
66
+
51
67
  task_options = cls(
52
68
  retry=retry,
69
+ timeout_seconds=timeout_seconds,
70
+ plan=plan,
53
71
  )
54
72
 
55
73
  task_options.additional_properties = d
@@ -0,0 +1,58 @@
1
+ """CLI entrypoint for running Render Workflows.
2
+
3
+ Usage:
4
+ render-workflows myapp:app
5
+ render-workflows myapp.tasks:app
6
+
7
+ This is an alternative to auto_start=True. It provides explicit control
8
+ over when and how the worker starts.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib
14
+ import sys
15
+
16
+
17
+ def main() -> None:
18
+ """Main CLI entrypoint."""
19
+ if len(sys.argv) < 2:
20
+ print("Usage: render-workflows <module:app>", file=sys.stderr)
21
+ print("Example: render-workflows myapp:app", file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+ app_path = sys.argv[1]
25
+
26
+ if ":" not in app_path:
27
+ print(f"Error: Invalid app path '{app_path}'", file=sys.stderr)
28
+ print("Expected format: <module>:<app_variable>", file=sys.stderr)
29
+ print("Example: render-workflows myapp:app", file=sys.stderr)
30
+ sys.exit(1)
31
+
32
+ module_path, app_name = app_path.rsplit(":", 1)
33
+
34
+ try:
35
+ module = importlib.import_module(module_path)
36
+ except ImportError as e:
37
+ print(f"Error: Could not import module '{module_path}': {e}", file=sys.stderr)
38
+ sys.exit(1)
39
+
40
+ try:
41
+ app = getattr(module, app_name)
42
+ except AttributeError:
43
+ print(
44
+ f"Error: Module '{module_path}' has no attribute '{app_name}'",
45
+ file=sys.stderr,
46
+ )
47
+ sys.exit(1)
48
+
49
+ if not hasattr(app, "start"):
50
+ print(f"Error: '{app_name}' does not have a start() method", file=sys.stderr)
51
+ print("Expected a Workflows instance", file=sys.stderr)
52
+ sys.exit(1)
53
+
54
+ app.start()
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()
@@ -2,7 +2,6 @@
2
2
 
3
3
  import asyncio
4
4
  import base64
5
- import importlib.metadata
6
5
  import json
7
6
  import time
8
7
  from dataclasses import dataclass
@@ -13,6 +12,7 @@ import httpx
13
12
 
14
13
  from render_sdk.client.errors import RenderError, TaskRunError
15
14
  from render_sdk.client.util import handle_http_errors
15
+ from render_sdk.version import get_user_agent
16
16
  from render_sdk.workflows.callback_api.api.default import (
17
17
  get_input,
18
18
  post_callback,
@@ -65,11 +65,6 @@ POLLING_INTERVAL = 1.0
65
65
  POLLING_TIMEOUT = 24 * 60 * 60 # 24 hours
66
66
  QUERY_TIMEOUT = 15 # 15 seconds
67
67
 
68
- try:
69
- version = importlib.metadata.version("render")
70
- except importlib.metadata.PackageNotFoundError:
71
- version = "unknown" # fallback version
72
-
73
68
 
74
69
  class UDSClient:
75
70
  """Client for communicating with the SDK server over Unix Domain Socket."""
@@ -80,7 +75,7 @@ class UDSClient:
80
75
  def get_client(self) -> Client:
81
76
  return Client(
82
77
  base_url="http://localhost",
83
- headers={"User-Agent": f"render-workflows-python-sdk/{version}"},
78
+ headers={"User-Agent": get_user_agent()},
84
79
  httpx_args={
85
80
  "transport": httpx.AsyncHTTPTransport(uds=self.socket_path),
86
81
  },
@@ -88,13 +88,18 @@ async def register_async(socket_path: str) -> None:
88
88
 
89
89
  options = TaskOptions()
90
90
  # Add options if present
91
- if task_info and task_info.options and task_info.options.retry:
92
- retry = task_info.options.retry
93
- options.retry = RetryConfig(
94
- max_retries=retry.max_retries,
95
- wait_duration_ms=retry.wait_duration,
96
- factor=retry.backoff_scaling,
97
- )
91
+ if task_info and task_info.options:
92
+ if task_info.options.retry:
93
+ retry = task_info.options.retry
94
+ options.retry = RetryConfig(
95
+ max_retries=retry.max_retries,
96
+ wait_duration_ms=retry.wait_duration_ms,
97
+ factor=retry.backoff_scaling,
98
+ )
99
+ if task_info.options.timeout_seconds:
100
+ options.timeout_seconds = task_info.options.timeout_seconds
101
+ if task_info.options.plan:
102
+ options.plan = task_info.options.plan
98
103
 
99
104
  parameters: list[TaskParameter] | Unset = UNSET
100
105
  if task_info and task_info.parameters:
@@ -20,15 +20,24 @@ class Retry:
20
20
  """Retry configuration for a task."""
21
21
 
22
22
  max_retries: int
23
- wait_duration: int
23
+ wait_duration_ms: int
24
24
  backoff_scaling: float = 1.5
25
25
 
26
26
 
27
27
  @dataclass
28
28
  class Options:
29
- """Configuration options for a task."""
29
+ """Configuration options for a task.
30
+
31
+ Attributes:
32
+ retry: Retry configuration for automatic task retries.
33
+ timeout_seconds: Task execution timeout in seconds (30-86400).
34
+ plan: Resource plan for task execution. Options: "starter" (0.5CPU/512MB),
35
+ "standard" (1CPU/2GB), "pro" (2CPU/4GB). Defaults to "standard".
36
+ """
30
37
 
31
38
  retry: Retry | None = None
39
+ timeout_seconds: int | None = None
40
+ plan: str | None = None
32
41
 
33
42
 
34
43
  @dataclass