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.
- render_sdk/__init__.py +41 -4
- render_sdk/client/__init__.py +25 -0
- render_sdk/client/client.py +5 -0
- render_sdk/client/sse.py +5 -1
- render_sdk/client/tests/test_client.py +6 -4
- render_sdk/client/tests/test_sse.py +1 -0
- render_sdk/client/workflows.py +10 -2
- render_sdk/experimental/__init__.py +31 -0
- render_sdk/experimental/experimental.py +71 -0
- render_sdk/experimental/object/__init__.py +30 -0
- render_sdk/experimental/object/api.py +260 -0
- render_sdk/experimental/object/client.py +475 -0
- render_sdk/experimental/object/types.py +87 -0
- render_sdk/public_api/api/audit_logs/list_organization_audit_logs.py +303 -0
- render_sdk/public_api/api/audit_logs/list_owner_audit_logs.py +303 -0
- render_sdk/public_api/api/blob_storage/delete_blob.py +215 -0
- render_sdk/public_api/api/blob_storage/get_blob.py +221 -0
- render_sdk/public_api/api/{workflows/list_workflow_versions.py → blob_storage/list_blobs.py} +52 -30
- render_sdk/public_api/api/blob_storage/put_blob.py +248 -0
- render_sdk/public_api/api/blueprints/validate_blueprint.py +212 -0
- render_sdk/public_api/api/key_value/resume_key_value.py +203 -0
- render_sdk/public_api/api/key_value/suspend_key_value.py +203 -0
- render_sdk/public_api/api/metrics/get_bandwidth_sources.py +251 -0
- render_sdk/public_api/api/postgres/create_postgres_user.py +229 -0
- render_sdk/public_api/api/postgres/delete_postgres_user.py +201 -0
- render_sdk/public_api/api/postgres/list_postgres_users.py +195 -0
- render_sdk/public_api/api/redis_deprecated/__init__.py +1 -0
- render_sdk/public_api/api/{redis → redis_deprecated}/create_redis.py +4 -4
- render_sdk/public_api/api/{redis → redis_deprecated}/delete_redis.py +4 -4
- render_sdk/public_api/api/{redis → redis_deprecated}/list_redis.py +4 -0
- render_sdk/public_api/api/{redis → redis_deprecated}/retrieve_redis.py +4 -4
- render_sdk/public_api/api/{redis → redis_deprecated}/retrieve_redis_connection_info.py +4 -0
- render_sdk/public_api/api/{redis → redis_deprecated}/update_redis.py +4 -4
- render_sdk/public_api/api/services/create_service.py +4 -4
- render_sdk/public_api/api/workflow_tasks_ea/__init__.py +1 -0
- render_sdk/public_api/api/{workflows → workflow_tasks_ea}/cancel_task_run.py +12 -4
- render_sdk/public_api/api/{workflows → workflow_tasks_ea}/create_task.py +12 -4
- render_sdk/public_api/api/{workflows → workflow_tasks_ea}/get_task.py +12 -4
- render_sdk/public_api/api/{workflows → workflow_tasks_ea}/get_task_run.py +12 -4
- render_sdk/public_api/api/{workflows → workflow_tasks_ea}/list_task_runs.py +12 -0
- render_sdk/public_api/api/{workflows → workflow_tasks_ea}/list_tasks.py +24 -12
- render_sdk/public_api/api/workflows_ea/__init__.py +1 -0
- render_sdk/public_api/api/workflows_ea/create_workflow.py +199 -0
- render_sdk/public_api/api/{workflows/deploy_workflow.py → workflows_ea/create_workflow_version.py} +31 -14
- render_sdk/public_api/api/{workflows → workflows_ea}/delete_workflow.py +12 -4
- render_sdk/public_api/api/{workflows → workflows_ea}/get_workflow.py +32 -14
- render_sdk/public_api/api/{workflows → workflows_ea}/get_workflow_version.py +12 -4
- render_sdk/public_api/api/workflows_ea/list_workflow_versions.py +275 -0
- render_sdk/public_api/api/{workflows → workflows_ea}/list_workflows.py +41 -14
- render_sdk/public_api/api/workflows_ea/update_workflow.py +212 -0
- render_sdk/public_api/api/workspaces/remove_workspace_member.py +206 -0
- render_sdk/public_api/api/workspaces/update_workspace_member.py +235 -0
- render_sdk/public_api/models/__init__.py +82 -4
- render_sdk/public_api/models/audit_log.py +113 -0
- render_sdk/public_api/models/audit_log_actor.py +80 -0
- render_sdk/public_api/models/audit_log_actor_type.py +10 -0
- render_sdk/public_api/models/audit_log_event.py +80 -0
- render_sdk/public_api/models/audit_log_metadata.py +49 -0
- render_sdk/public_api/models/audit_log_status.py +9 -0
- render_sdk/public_api/models/audit_log_with_cursor.py +73 -0
- render_sdk/public_api/models/background_worker_details.py +2 -2
- render_sdk/public_api/models/background_worker_details_patch.py +1 -1
- render_sdk/public_api/models/background_worker_details_post.py +1 -1
- render_sdk/public_api/models/blob_metadata.py +85 -0
- render_sdk/public_api/models/blob_with_cursor.py +73 -0
- render_sdk/public_api/models/cache.py +6 -4
- render_sdk/public_api/models/cache_profile.py +10 -0
- render_sdk/public_api/models/create_deploy_body.py +23 -0
- render_sdk/public_api/models/create_version.py +70 -0
- render_sdk/public_api/models/credential_create_input.py +59 -0
- render_sdk/public_api/models/cron_job_details.py +2 -2
- render_sdk/public_api/models/cron_job_details_patch.py +1 -1
- render_sdk/public_api/models/cron_job_details_post.py +1 -1
- render_sdk/public_api/models/deploy_mode.py +9 -0
- render_sdk/public_api/models/event.py +11 -27
- render_sdk/public_api/models/event_type.py +1 -1
- render_sdk/public_api/models/get_bandwidth_sources_response_200.py +75 -0
- render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item.py +101 -0
- render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item_labels.py +78 -0
- render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item_labels_traffic_source.py +12 -0
- render_sdk/public_api/models/get_bandwidth_sources_response_200_data_item_values_item.py +68 -0
- render_sdk/public_api/models/{server_unhealthy.py → get_bandwidth_sources_response_400.py} +12 -12
- render_sdk/public_api/models/get_blob_output.py +71 -0
- render_sdk/public_api/models/list_postgres_users_response_200_item.py +86 -0
- render_sdk/public_api/models/otel_provider_type.py +2 -0
- render_sdk/public_api/models/postgres.py +8 -0
- render_sdk/public_api/models/postgres_detail.py +26 -0
- render_sdk/public_api/models/postgres_parameter_overrides.py +44 -0
- render_sdk/public_api/models/postgres_patch_input.py +27 -0
- render_sdk/public_api/models/postgres_post_input.py +27 -0
- render_sdk/public_api/models/postgres_version.py +1 -0
- render_sdk/public_api/models/preview_input.py +2 -2
- render_sdk/public_api/models/private_service_details.py +2 -2
- render_sdk/public_api/models/private_service_details_patch.py +1 -1
- render_sdk/public_api/models/private_service_details_post.py +1 -1
- render_sdk/public_api/models/project_post_environment_input.py +26 -1
- render_sdk/public_api/models/put_blob_input.py +59 -0
- render_sdk/public_api/models/put_blob_output.py +79 -0
- render_sdk/public_api/models/read_replica.py +25 -1
- render_sdk/public_api/models/read_replica_input.py +25 -1
- render_sdk/public_api/models/run_task.py +35 -7
- render_sdk/public_api/models/service_event.py +12 -27
- render_sdk/public_api/models/service_event_type.py +0 -1
- render_sdk/public_api/models/service_post.py +9 -6
- render_sdk/public_api/models/task_attempt.py +88 -0
- render_sdk/public_api/models/task_attempt_details.py +108 -0
- render_sdk/public_api/models/task_data_type_1.py +44 -0
- render_sdk/public_api/models/task_run.py +23 -1
- render_sdk/public_api/models/task_run_details.py +50 -5
- render_sdk/public_api/models/task_run_status.py +1 -0
- render_sdk/public_api/models/task_with_cursor.py +73 -0
- render_sdk/public_api/models/team_member.py +5 -4
- render_sdk/public_api/models/team_member_role.py +12 -0
- render_sdk/public_api/models/update_workspace_member_body.py +61 -0
- render_sdk/public_api/models/validate_blueprint_request.py +84 -0
- render_sdk/public_api/models/validate_blueprint_response.py +105 -0
- render_sdk/public_api/models/validation_error.py +88 -0
- render_sdk/public_api/models/validation_plan_summary.py +107 -0
- render_sdk/public_api/models/web_service_details.py +2 -2
- render_sdk/public_api/models/web_service_details_patch.py +6 -5
- render_sdk/public_api/models/web_service_details_post.py +6 -5
- render_sdk/public_api/models/workflow.py +144 -0
- render_sdk/public_api/models/workflow_create.py +99 -0
- render_sdk/public_api/models/workflow_update.py +90 -0
- render_sdk/public_api/models/workflow_version.py +10 -14
- render_sdk/public_api/models/workflow_version_status.py +13 -0
- render_sdk/public_api/models/workflow_version_with_cursor.py +73 -0
- render_sdk/public_api/models/workflow_with_cursor.py +73 -0
- render_sdk/render.py +65 -0
- render_sdk/version.py +27 -0
- render_sdk/workflows/__init__.py +5 -1
- render_sdk/workflows/app.py +262 -0
- render_sdk/workflows/callback_api/models/task_options.py +18 -0
- render_sdk/workflows/cli.py +58 -0
- render_sdk/workflows/client.py +2 -7
- render_sdk/workflows/runner.py +12 -7
- render_sdk/workflows/task.py +11 -2
- render_sdk/workflows/tests/test_app.py +412 -0
- render_sdk/workflows/tests/test_cli.py +134 -0
- render_sdk/workflows/tests/test_end_to_end.py +69 -1
- render_sdk/workflows/tests/test_registration.py +56 -1
- {render_sdk-0.1.3.dist-info → render_sdk-0.2.0.dist-info}/METADATA +1 -1
- {render_sdk-0.1.3.dist-info → render_sdk-0.2.0.dist-info}/RECORD +149 -78
- render_sdk-0.2.0.dist-info/entry_points.txt +3 -0
- render_sdk/public_api/models/image_version.py +0 -79
- /render_sdk/public_api/api/{redis → audit_logs}/__init__.py +0 -0
- /render_sdk/public_api/api/{workflows → blob_storage}/__init__.py +0 -0
- /render_sdk/public_api/api/{workflows → workflow_tasks_ea}/stream_task_runs_events.py +0 -0
- {render_sdk-0.1.3.dist-info → render_sdk-0.2.0.dist-info}/WHEEL +0 -0
- {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()
|
render_sdk/workflows/client.py
CHANGED
|
@@ -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":
|
|
78
|
+
headers={"User-Agent": get_user_agent()},
|
|
84
79
|
httpx_args={
|
|
85
80
|
"transport": httpx.AsyncHTTPTransport(uds=self.socket_path),
|
|
86
81
|
},
|
render_sdk/workflows/runner.py
CHANGED
|
@@ -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
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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:
|
render_sdk/workflows/task.py
CHANGED
|
@@ -20,15 +20,24 @@ class Retry:
|
|
|
20
20
|
"""Retry configuration for a task."""
|
|
21
21
|
|
|
22
22
|
max_retries: int
|
|
23
|
-
|
|
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
|