render_sdk 0.1.1__py3-none-any.whl → 0.1.3__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 +1 -1
- render_sdk/client/types.py +2 -1
- render_sdk/client/workflows.py +3 -1
- render_sdk/public_api/models/run_task.py +1 -1
- render_sdk/workflows/callback_api/models/__init__.py +2 -0
- render_sdk/workflows/callback_api/models/task.py +21 -0
- render_sdk/workflows/callback_api/models/task_parameter.py +88 -0
- render_sdk/workflows/callback_api/py.typed +1 -1
- render_sdk/workflows/client.py +6 -2
- render_sdk/workflows/executor.py +20 -8
- render_sdk/workflows/runner.py +40 -21
- render_sdk/workflows/task.py +75 -5
- render_sdk/workflows/tests/test_end_to_end.py +3 -1
- render_sdk/workflows/tests/test_registration.py +3 -1
- {render_sdk-0.1.1.dist-info → render_sdk-0.1.3.dist-info}/METADATA +4 -3
- {render_sdk-0.1.1.dist-info → render_sdk-0.1.3.dist-info}/RECORD +18 -17
- {render_sdk-0.1.1.dist-info → render_sdk-0.1.3.dist-info}/WHEEL +1 -1
- {render_sdk-0.1.1.dist-info → render_sdk-0.1.3.dist-info/licenses}/LICENSE +0 -0
render_sdk/__init__.py
CHANGED
render_sdk/client/types.py
CHANGED
|
@@ -11,7 +11,8 @@ from render_sdk.public_api.models.task_run_status import TaskRunStatus as _TaskR
|
|
|
11
11
|
|
|
12
12
|
# Type aliases to match Go client interface
|
|
13
13
|
TaskIdentifier = str
|
|
14
|
-
TaskData
|
|
14
|
+
# TaskData can be either positional (list) or named (dict) parameters
|
|
15
|
+
TaskData = list[Any] | dict[str, Any]
|
|
15
16
|
|
|
16
17
|
# Re-export model classes with cleaner names
|
|
17
18
|
TaskRun = _TaskRun
|
render_sdk/client/workflows.py
CHANGED
|
@@ -125,7 +125,9 @@ class WorkflowsService:
|
|
|
125
125
|
|
|
126
126
|
Args:
|
|
127
127
|
task_identifier: The identifier of the task to run
|
|
128
|
-
input_data: The input data for the task
|
|
128
|
+
input_data: The input data for the task. Can be either:
|
|
129
|
+
- A list for positional arguments: [arg1, arg2, arg3]
|
|
130
|
+
- A dict for named parameters: {"param1": value1, "param2": value2}
|
|
129
131
|
|
|
130
132
|
Returns:
|
|
131
133
|
AwaitableTaskRun: An awaitable task run object
|
|
@@ -11,6 +11,7 @@ from .task import Task
|
|
|
11
11
|
from .task_complete import TaskComplete
|
|
12
12
|
from .task_error import TaskError
|
|
13
13
|
from .task_options import TaskOptions
|
|
14
|
+
from .task_parameter import TaskParameter
|
|
14
15
|
from .tasks import Tasks
|
|
15
16
|
|
|
16
17
|
__all__ = (
|
|
@@ -25,5 +26,6 @@ __all__ = (
|
|
|
25
26
|
"TaskComplete",
|
|
26
27
|
"TaskError",
|
|
27
28
|
"TaskOptions",
|
|
29
|
+
"TaskParameter",
|
|
28
30
|
"Tasks",
|
|
29
31
|
)
|
|
@@ -8,6 +8,7 @@ from ..types import UNSET, Unset
|
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from ..models.task_options import TaskOptions
|
|
11
|
+
from ..models.task_parameter import TaskParameter
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
T = TypeVar("T", bound="Task")
|
|
@@ -19,10 +20,12 @@ class Task:
|
|
|
19
20
|
Attributes:
|
|
20
21
|
name (str):
|
|
21
22
|
options (Union[Unset, TaskOptions]):
|
|
23
|
+
parameters (Union[Unset, list['TaskParameter']]): Parameter schema extracted from the task function signature
|
|
22
24
|
"""
|
|
23
25
|
|
|
24
26
|
name: str
|
|
25
27
|
options: Union[Unset, "TaskOptions"] = UNSET
|
|
28
|
+
parameters: Union[Unset, list["TaskParameter"]] = UNSET
|
|
26
29
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
27
30
|
|
|
28
31
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -32,6 +35,13 @@ class Task:
|
|
|
32
35
|
if not isinstance(self.options, Unset):
|
|
33
36
|
options = self.options.to_dict()
|
|
34
37
|
|
|
38
|
+
parameters: Union[Unset, list[dict[str, Any]]] = UNSET
|
|
39
|
+
if not isinstance(self.parameters, Unset):
|
|
40
|
+
parameters = []
|
|
41
|
+
for parameters_item_data in self.parameters:
|
|
42
|
+
parameters_item = parameters_item_data.to_dict()
|
|
43
|
+
parameters.append(parameters_item)
|
|
44
|
+
|
|
35
45
|
field_dict: dict[str, Any] = {}
|
|
36
46
|
field_dict.update(self.additional_properties)
|
|
37
47
|
field_dict.update(
|
|
@@ -41,12 +51,15 @@ class Task:
|
|
|
41
51
|
)
|
|
42
52
|
if options is not UNSET:
|
|
43
53
|
field_dict["options"] = options
|
|
54
|
+
if parameters is not UNSET:
|
|
55
|
+
field_dict["parameters"] = parameters
|
|
44
56
|
|
|
45
57
|
return field_dict
|
|
46
58
|
|
|
47
59
|
@classmethod
|
|
48
60
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
49
61
|
from ..models.task_options import TaskOptions
|
|
62
|
+
from ..models.task_parameter import TaskParameter
|
|
50
63
|
|
|
51
64
|
d = dict(src_dict)
|
|
52
65
|
name = d.pop("name")
|
|
@@ -58,9 +71,17 @@ class Task:
|
|
|
58
71
|
else:
|
|
59
72
|
options = TaskOptions.from_dict(_options)
|
|
60
73
|
|
|
74
|
+
parameters = []
|
|
75
|
+
_parameters = d.pop("parameters", UNSET)
|
|
76
|
+
for parameters_item_data in _parameters or []:
|
|
77
|
+
parameters_item = TaskParameter.from_dict(parameters_item_data)
|
|
78
|
+
|
|
79
|
+
parameters.append(parameters_item)
|
|
80
|
+
|
|
61
81
|
task = cls(
|
|
62
82
|
name=name,
|
|
63
83
|
options=options,
|
|
84
|
+
parameters=parameters,
|
|
64
85
|
)
|
|
65
86
|
|
|
66
87
|
task.additional_properties = d
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Any, TypeVar, Union
|
|
3
|
+
|
|
4
|
+
from attrs import define as _attrs_define
|
|
5
|
+
from attrs import field as _attrs_field
|
|
6
|
+
|
|
7
|
+
from ..types import UNSET, Unset
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound="TaskParameter")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@_attrs_define
|
|
13
|
+
class TaskParameter:
|
|
14
|
+
"""Information about a task parameter extracted from function signature
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
name (str): Parameter name
|
|
18
|
+
has_default (bool): Whether the parameter has a default value
|
|
19
|
+
type_ (Union[Unset, str]): String representation of the parameter type hint
|
|
20
|
+
default_value (Union[Unset, str]): JSON-encoded default value (if has_default is true)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
name: str
|
|
24
|
+
has_default: bool
|
|
25
|
+
type_: Union[Unset, str] = UNSET
|
|
26
|
+
default_value: Union[Unset, str] = UNSET
|
|
27
|
+
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict[str, Any]:
|
|
30
|
+
name = self.name
|
|
31
|
+
|
|
32
|
+
has_default = self.has_default
|
|
33
|
+
|
|
34
|
+
type_ = self.type_
|
|
35
|
+
|
|
36
|
+
default_value = self.default_value
|
|
37
|
+
|
|
38
|
+
field_dict: dict[str, Any] = {}
|
|
39
|
+
field_dict.update(self.additional_properties)
|
|
40
|
+
field_dict.update(
|
|
41
|
+
{
|
|
42
|
+
"name": name,
|
|
43
|
+
"has_default": has_default,
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
if type_ is not UNSET:
|
|
47
|
+
field_dict["type"] = type_
|
|
48
|
+
if default_value is not UNSET:
|
|
49
|
+
field_dict["default_value"] = default_value
|
|
50
|
+
|
|
51
|
+
return field_dict
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
55
|
+
d = dict(src_dict)
|
|
56
|
+
name = d.pop("name")
|
|
57
|
+
|
|
58
|
+
has_default = d.pop("has_default")
|
|
59
|
+
|
|
60
|
+
type_ = d.pop("type", UNSET)
|
|
61
|
+
|
|
62
|
+
default_value = d.pop("default_value", UNSET)
|
|
63
|
+
|
|
64
|
+
task_parameter = cls(
|
|
65
|
+
name=name,
|
|
66
|
+
has_default=has_default,
|
|
67
|
+
type_=type_,
|
|
68
|
+
default_value=default_value,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
task_parameter.additional_properties = d
|
|
72
|
+
return task_parameter
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def additional_keys(self) -> list[str]:
|
|
76
|
+
return list(self.additional_properties.keys())
|
|
77
|
+
|
|
78
|
+
def __getitem__(self, key: str) -> Any:
|
|
79
|
+
return self.additional_properties[key]
|
|
80
|
+
|
|
81
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
82
|
+
self.additional_properties[key] = value
|
|
83
|
+
|
|
84
|
+
def __delitem__(self, key: str) -> None:
|
|
85
|
+
del self.additional_properties[key]
|
|
86
|
+
|
|
87
|
+
def __contains__(self, key: str) -> bool:
|
|
88
|
+
return key in self.additional_properties
|
|
@@ -1 +1 @@
|
|
|
1
|
-
# Marker file for PEP 561
|
|
1
|
+
# Marker file for PEP 561
|
render_sdk/workflows/client.py
CHANGED
|
@@ -134,13 +134,17 @@ class UDSClient:
|
|
|
134
134
|
async with self.get_client() as client:
|
|
135
135
|
return await post_callback.asyncio_detailed(client=client, body=data)
|
|
136
136
|
|
|
137
|
-
async def run_subtask(
|
|
137
|
+
async def run_subtask(
|
|
138
|
+
self, task_name: str, input_data: list[Any] | dict[str, Any] | None = None
|
|
139
|
+
) -> Any:
|
|
138
140
|
"""
|
|
139
141
|
Run a subtask and wait for its completion.
|
|
140
142
|
|
|
141
143
|
Args:
|
|
142
144
|
task_name: Name of the task to run
|
|
143
|
-
input_data: Input data to pass to the task
|
|
145
|
+
input_data: Input data to pass to the task. Can be either:
|
|
146
|
+
- A list for positional arguments: [arg1, arg2, arg3]
|
|
147
|
+
- A dict for named parameters: {"param1": value1, "param2": value2}
|
|
144
148
|
|
|
145
149
|
Returns:
|
|
146
150
|
The result of the subtask execution
|
render_sdk/workflows/executor.py
CHANGED
|
@@ -17,7 +17,9 @@ class TaskExecutor:
|
|
|
17
17
|
self.task_registry = task_registry
|
|
18
18
|
self.client = client
|
|
19
19
|
|
|
20
|
-
async def _execute_task(
|
|
20
|
+
async def _execute_task(
|
|
21
|
+
self, task_name: str, input_data: list[Any] | dict[str, Any]
|
|
22
|
+
) -> Any:
|
|
21
23
|
"""Execute a task by name with the given input."""
|
|
22
24
|
func = self.task_registry.get_function(task_name)
|
|
23
25
|
if not func:
|
|
@@ -28,11 +30,19 @@ class TaskExecutor:
|
|
|
28
30
|
context = _current_client.set(self.client)
|
|
29
31
|
|
|
30
32
|
try:
|
|
31
|
-
#
|
|
32
|
-
if
|
|
33
|
-
|
|
33
|
+
# Determine how to call the function based on input type
|
|
34
|
+
if isinstance(input_data, dict):
|
|
35
|
+
# Named parameters: pass as keyword arguments
|
|
36
|
+
if inspect.iscoroutinefunction(func):
|
|
37
|
+
result = await func(**input_data)
|
|
38
|
+
else:
|
|
39
|
+
result = func(**input_data)
|
|
34
40
|
else:
|
|
35
|
-
|
|
41
|
+
# Positional parameters: unpack list
|
|
42
|
+
if inspect.iscoroutinefunction(func):
|
|
43
|
+
result = await func(*input_data)
|
|
44
|
+
else:
|
|
45
|
+
result = func(*input_data)
|
|
36
46
|
|
|
37
47
|
return TaskResult(result=result)
|
|
38
48
|
finally:
|
|
@@ -42,15 +52,17 @@ class TaskExecutor:
|
|
|
42
52
|
except Exception as e:
|
|
43
53
|
return TaskResult(error=e)
|
|
44
54
|
|
|
45
|
-
async def execute(
|
|
55
|
+
async def execute(
|
|
56
|
+
self, task_name: str, input_data: list[Any] | dict[str, Any]
|
|
57
|
+
) -> Any:
|
|
46
58
|
"""Execute a task by name with the given input."""
|
|
47
|
-
logger.
|
|
59
|
+
logger.debug(f"Starting execution of task: {task_name}")
|
|
48
60
|
|
|
49
61
|
sent_error = False
|
|
50
62
|
|
|
51
63
|
try:
|
|
52
64
|
# Execute the task
|
|
53
|
-
result = await self._execute_task(task_name,
|
|
65
|
+
result = await self._execute_task(task_name, input_data)
|
|
54
66
|
if result.error:
|
|
55
67
|
# Send error callback and raise the error
|
|
56
68
|
await self._send_error_callback(task_name, result.error)
|
render_sdk/workflows/runner.py
CHANGED
|
@@ -10,11 +10,13 @@ from render_sdk.workflows.callback_api.models import (
|
|
|
10
10
|
RetryConfig,
|
|
11
11
|
Task,
|
|
12
12
|
TaskOptions,
|
|
13
|
+
TaskParameter,
|
|
13
14
|
Tasks,
|
|
14
15
|
)
|
|
16
|
+
from render_sdk.workflows.callback_api.types import UNSET, Unset
|
|
15
17
|
from render_sdk.workflows.client import UDSClient
|
|
16
18
|
from render_sdk.workflows.executor import TaskExecutor
|
|
17
|
-
from render_sdk.workflows.task import get_task_registry
|
|
19
|
+
from render_sdk.workflows.task import ParameterInfo, get_task_registry
|
|
18
20
|
|
|
19
21
|
logger = logging.getLogger(__name__)
|
|
20
22
|
|
|
@@ -25,18 +27,16 @@ async def run_async(socket_path: str) -> None:
|
|
|
25
27
|
|
|
26
28
|
It gets the input from the server, executes the task, and sends the result back.
|
|
27
29
|
"""
|
|
28
|
-
logger.
|
|
30
|
+
logger.debug("Starting task runner")
|
|
29
31
|
|
|
30
32
|
# Create client
|
|
31
33
|
client = UDSClient(socket_path)
|
|
32
34
|
|
|
33
35
|
try:
|
|
34
36
|
# Get input from server
|
|
35
|
-
logger.
|
|
37
|
+
logger.debug("Getting task input")
|
|
36
38
|
input_response = await client.get_input()
|
|
37
39
|
|
|
38
|
-
logger.info(f"Input response: {input_response}")
|
|
39
|
-
|
|
40
40
|
task_name = input_response.task_name
|
|
41
41
|
raw_input = input_response.input_
|
|
42
42
|
|
|
@@ -50,18 +50,13 @@ async def run_async(socket_path: str) -> None:
|
|
|
50
50
|
else:
|
|
51
51
|
input_data = []
|
|
52
52
|
|
|
53
|
-
logger.info(f"Received input - task: {task_name}, input: {input_data}")
|
|
54
|
-
|
|
55
53
|
# Create executor and execute task
|
|
56
54
|
task_registry = get_task_registry()
|
|
57
55
|
executor = TaskExecutor(task_registry, client)
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
except Exception as e:
|
|
64
|
-
logger.error(f"Task execution failed: {e}")
|
|
57
|
+
logger.debug(f"Executing task: {task_name}")
|
|
58
|
+
await executor.execute(task_name, input_data)
|
|
59
|
+
except Exception:
|
|
65
60
|
raise
|
|
66
61
|
|
|
67
62
|
|
|
@@ -76,7 +71,7 @@ async def register_async(socket_path: str) -> None:
|
|
|
76
71
|
"""
|
|
77
72
|
Register all tasks with the server asynchronously.
|
|
78
73
|
"""
|
|
79
|
-
logger.
|
|
74
|
+
logger.debug("Registering tasks")
|
|
80
75
|
|
|
81
76
|
# Create client
|
|
82
77
|
client = UDSClient(socket_path)
|
|
@@ -97,18 +92,25 @@ async def register_async(socket_path: str) -> None:
|
|
|
97
92
|
retry = task_info.options.retry
|
|
98
93
|
options.retry = RetryConfig(
|
|
99
94
|
max_retries=retry.max_retries,
|
|
100
|
-
wait_duration_ms=retry.
|
|
101
|
-
factor=retry.
|
|
95
|
+
wait_duration_ms=retry.wait_duration,
|
|
96
|
+
factor=retry.backoff_scaling,
|
|
102
97
|
)
|
|
103
98
|
|
|
104
|
-
|
|
99
|
+
parameters: list[TaskParameter] | Unset = UNSET
|
|
100
|
+
if task_info and task_info.parameters:
|
|
101
|
+
parameters = [
|
|
102
|
+
_convert_parameter_info_to_api_model(param)
|
|
103
|
+
for param in task_info.parameters
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
task_def = Task(name=name, options=options, parameters=parameters)
|
|
105
107
|
tasks.append(task_def)
|
|
106
108
|
|
|
107
109
|
# Register tasks with server
|
|
108
|
-
logger.
|
|
110
|
+
logger.debug(f"Registering {len(tasks)} tasks: {[t.name for t in tasks]}")
|
|
109
111
|
await client.register_tasks(Tasks(tasks=tasks))
|
|
110
112
|
|
|
111
|
-
logger.
|
|
113
|
+
logger.debug("Tasks registered successfully")
|
|
112
114
|
|
|
113
115
|
except Exception as e:
|
|
114
116
|
logger.error(f"Task registration failed: {e}")
|
|
@@ -139,11 +141,28 @@ def start() -> None:
|
|
|
139
141
|
if not socket_path:
|
|
140
142
|
raise ValueError("RENDER_SDK_SOCKET_PATH environment variable is required")
|
|
141
143
|
|
|
142
|
-
logger.info(f"Starting in mode: {mode}")
|
|
143
|
-
|
|
144
144
|
if mode == "run":
|
|
145
145
|
run(socket_path)
|
|
146
146
|
elif mode == "register":
|
|
147
147
|
register(socket_path)
|
|
148
148
|
else:
|
|
149
149
|
raise ValueError(f"Unknown mode: {mode}")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _convert_parameter_info_to_api_model(param_info: ParameterInfo) -> TaskParameter:
|
|
153
|
+
"""Convert internal ParameterInfo to API TaskParameter model."""
|
|
154
|
+
# JSON-encode the default value if it exists
|
|
155
|
+
default_value_str = None
|
|
156
|
+
if param_info.has_default and param_info.default_value is not None:
|
|
157
|
+
try:
|
|
158
|
+
default_value_str = json.dumps(param_info.default_value)
|
|
159
|
+
except (TypeError, ValueError):
|
|
160
|
+
# If the default can't be serialized, skip it
|
|
161
|
+
default_value_str = None
|
|
162
|
+
|
|
163
|
+
return TaskParameter(
|
|
164
|
+
name=param_info.name,
|
|
165
|
+
has_default=param_info.has_default,
|
|
166
|
+
type_=param_info.type_hint if param_info.type_hint is not None else UNSET,
|
|
167
|
+
default_value=default_value_str if default_value_str is not None else UNSET,
|
|
168
|
+
)
|
render_sdk/workflows/task.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextvars
|
|
5
5
|
import functools
|
|
6
|
+
import inspect
|
|
6
7
|
from abc import ABC, abstractmethod
|
|
7
8
|
from collections.abc import Callable
|
|
8
9
|
from dataclasses import dataclass
|
|
@@ -19,8 +20,8 @@ class Retry:
|
|
|
19
20
|
"""Retry configuration for a task."""
|
|
20
21
|
|
|
21
22
|
max_retries: int
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
wait_duration: int
|
|
24
|
+
backoff_scaling: float = 1.5
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@dataclass
|
|
@@ -30,6 +31,19 @@ class Options:
|
|
|
30
31
|
retry: Retry | None = None
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
@dataclass
|
|
35
|
+
class ParameterInfo:
|
|
36
|
+
"""
|
|
37
|
+
Information about a task parameter extracted from the task's function
|
|
38
|
+
signature.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
type_hint: str | None
|
|
43
|
+
has_default: bool
|
|
44
|
+
default_value: Any | None = None
|
|
45
|
+
|
|
46
|
+
|
|
33
47
|
class TaskResult:
|
|
34
48
|
"""Represents the result of a task execution."""
|
|
35
49
|
|
|
@@ -59,10 +73,17 @@ class TaskContext(ABC):
|
|
|
59
73
|
class TaskInfo:
|
|
60
74
|
"""Information about a registered task."""
|
|
61
75
|
|
|
62
|
-
def __init__(
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
func: Callable,
|
|
79
|
+
name: str,
|
|
80
|
+
options: Options | None = None,
|
|
81
|
+
parameters: list[ParameterInfo] | None = None,
|
|
82
|
+
):
|
|
63
83
|
self.func = func
|
|
64
84
|
self.name = name
|
|
65
85
|
self.options = options or Options()
|
|
86
|
+
self.parameters = parameters
|
|
66
87
|
|
|
67
88
|
|
|
68
89
|
class TaskRegistry:
|
|
@@ -71,6 +92,35 @@ class TaskRegistry:
|
|
|
71
92
|
def __init__(self) -> None:
|
|
72
93
|
self._tasks: dict[str, TaskInfo] = {}
|
|
73
94
|
|
|
95
|
+
def _extract_parameters(self, func: Callable) -> list[ParameterInfo]:
|
|
96
|
+
"""Extract parameter information from a function signature."""
|
|
97
|
+
sig = inspect.signature(func)
|
|
98
|
+
parameters: list[ParameterInfo] = []
|
|
99
|
+
|
|
100
|
+
for param_name, param in sig.parameters.items():
|
|
101
|
+
# Get type hint as string if available
|
|
102
|
+
type_hint: str | None = None
|
|
103
|
+
if param.annotation is not inspect.Parameter.empty:
|
|
104
|
+
if hasattr(param.annotation, "__name__"):
|
|
105
|
+
type_hint = param.annotation.__name__
|
|
106
|
+
else:
|
|
107
|
+
type_hint = str(param.annotation)
|
|
108
|
+
|
|
109
|
+
# Check if the parameter has a default value
|
|
110
|
+
has_default = param.default is not inspect.Parameter.empty
|
|
111
|
+
default_value = param.default if has_default else None
|
|
112
|
+
|
|
113
|
+
parameters.append(
|
|
114
|
+
ParameterInfo(
|
|
115
|
+
name=param_name,
|
|
116
|
+
type_hint=type_hint,
|
|
117
|
+
has_default=has_default,
|
|
118
|
+
default_value=default_value,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return parameters
|
|
123
|
+
|
|
74
124
|
def register(
|
|
75
125
|
self,
|
|
76
126
|
func: Callable,
|
|
@@ -80,7 +130,9 @@ class TaskRegistry:
|
|
|
80
130
|
"""Register a task function."""
|
|
81
131
|
task_name = name or func.__name__
|
|
82
132
|
|
|
83
|
-
|
|
133
|
+
parameters = self._extract_parameters(func)
|
|
134
|
+
|
|
135
|
+
task_info = TaskInfo(func, task_name, options, parameters)
|
|
84
136
|
|
|
85
137
|
if task_name in self._tasks:
|
|
86
138
|
raise ValueError(f"Task '{task_name}' already registered")
|
|
@@ -139,8 +191,26 @@ class TaskCallable:
|
|
|
139
191
|
def __call__(self, *args, **kwargs):
|
|
140
192
|
# Create a new TaskInstance for each call
|
|
141
193
|
client = _current_client.get()
|
|
194
|
+
|
|
195
|
+
# Error on mixed positional and kw args
|
|
196
|
+
if args and kwargs:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
"Cannot mix positional and keyword arguments when calling a task. "
|
|
199
|
+
"Use either positional arguments (e.g., task(arg1, arg2)) or "
|
|
200
|
+
"keyword arguments (e.g., task(param1=value1, param2=value2)), "
|
|
201
|
+
"but not both."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Determine input data type based on how the task was called
|
|
205
|
+
if kwargs:
|
|
206
|
+
# Named parameters: pass as dict
|
|
207
|
+
input_data: dict[str, Any] = kwargs
|
|
208
|
+
else:
|
|
209
|
+
# Positional parameters: pass as list
|
|
210
|
+
input_data = list(args)
|
|
211
|
+
|
|
142
212
|
# Start execution immediately
|
|
143
|
-
future = asyncio.create_task(client.run_subtask(self._name,
|
|
213
|
+
future = asyncio.create_task(client.run_subtask(self._name, input_data))
|
|
144
214
|
return TaskInstance(self._name, future)
|
|
145
215
|
|
|
146
216
|
|
|
@@ -70,7 +70,9 @@ def test_task_registration_network_payload(task_registry, task_decorator, mocker
|
|
|
70
70
|
return f"Hello {msg}"
|
|
71
71
|
|
|
72
72
|
@task_decorator(
|
|
73
|
-
options=Options(
|
|
73
|
+
options=Options(
|
|
74
|
+
retry=Retry(max_retries=3, wait_duration=1000, backoff_scaling=1.5)
|
|
75
|
+
),
|
|
74
76
|
)
|
|
75
77
|
def retry_task(data: str) -> str:
|
|
76
78
|
return data.upper()
|
|
@@ -93,7 +93,9 @@ def test_task_registration_with_options_object():
|
|
|
93
93
|
|
|
94
94
|
# Task with only retry options
|
|
95
95
|
@task_decorator(
|
|
96
|
-
options=Options(
|
|
96
|
+
options=Options(
|
|
97
|
+
retry=Retry(max_retries=1, wait_duration=500, backoff_scaling=1.0)
|
|
98
|
+
),
|
|
97
99
|
)
|
|
98
100
|
def task_with_retry_only(x: int) -> int:
|
|
99
101
|
return x
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: render_sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Python SDK for Render Workflows
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Author: Render
|
|
6
7
|
Author-email: support@render.com
|
|
7
8
|
Requires-Python: >=3.10,<4.0
|
|
@@ -15,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
-
Requires-Dist: aiohttp (>=3.
|
|
19
|
+
Requires-Dist: aiohttp (>=3.12.14,<4.0.0)
|
|
19
20
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
20
21
|
Requires-Dist: openapi-python-client (>=0.26.1,<0.27.0)
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
render_sdk/__init__.py,sha256=
|
|
1
|
+
render_sdk/__init__.py,sha256=fIELMyLq9HWX2veiJzLXZekFCIDc-aNuvGfO2pbks30,215
|
|
2
2
|
render_sdk/client/__init__.py,sha256=OtkFIaTnPhU6_1yveJwZFRzL3TemdEjZcRvtoYCVhTE,630
|
|
3
3
|
render_sdk/client/client.py,sha256=Relr4rc8qVbEkROBom6VvACKA8AalTZkYby3DPc2-vE,2486
|
|
4
4
|
render_sdk/client/errors.py,sha256=Y1WZV_AOpqvf0_HT5XBQ1JD5jNiEjHrgSiyq8wBfM5U,758
|
|
@@ -7,9 +7,9 @@ render_sdk/client/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
7
7
|
render_sdk/client/tests/test_client.py,sha256=6BmCSj0peE_CqYqYF5KaoCWIl5mLp7rvNEWvvMmiDgc,8144
|
|
8
8
|
render_sdk/client/tests/test_sse.py,sha256=-RpzXaQXgH3nTK2J73sZKrT0lqCP0q8qrx3IjJTgstw,1554
|
|
9
9
|
render_sdk/client/tests/test_util.py,sha256=Ap30o4XQd8VUzH6RS0AhfN5nAgud2SnfWAi4gvBuGOM,3801
|
|
10
|
-
render_sdk/client/types.py,sha256=
|
|
10
|
+
render_sdk/client/types.py,sha256=NfR25T0w8li0h6y-mN0ik62xY9Kpz79j9ENAX3pIuDQ,1339
|
|
11
11
|
render_sdk/client/util.py,sha256=CKi7pKc8JsVHaj_dDHJarm1amc5yCXd48z_MZmJE1nU,6108
|
|
12
|
-
render_sdk/client/workflows.py,sha256=
|
|
12
|
+
render_sdk/client/workflows.py,sha256=tb74nAHEjeZy9swx62DtbSyyW-ooYFQgX7jDjJ4PFqk,8636
|
|
13
13
|
render_sdk/public_api/__init__.py,sha256=nUgpPUbIzZHvvG1MhZppIBnRlI0gHQ0TRjKNrBKMELk,160
|
|
14
14
|
render_sdk/public_api/api/__init__.py,sha256=zTSiG_ujSjAqWPyc435YXaX9XTlpMjiJWBbV-f-YtdA,45
|
|
15
15
|
render_sdk/public_api/api/blueprints/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
@@ -470,7 +470,7 @@ render_sdk/public_api/models/route_post.py,sha256=gMHyooNM52XEEcnggG0L8OVr7A4UO-
|
|
|
470
470
|
render_sdk/public_api/models/route_put.py,sha256=pmistR0mLC5O5bOi811aRiHprgwtyCe1FPqgxaJ31zc,1920
|
|
471
471
|
render_sdk/public_api/models/route_type.py,sha256=j7vWAQkoYzQGuvjlIfKcFlr6DagyV7dD91AYRehIIIw,164
|
|
472
472
|
render_sdk/public_api/models/route_with_cursor.py,sha256=fV_yxMU_jZXz77HA1Iul5JzUpFSpZQeIqrugWjXGcLY,1780
|
|
473
|
-
render_sdk/public_api/models/run_task.py,sha256=
|
|
473
|
+
render_sdk/public_api/models/run_task.py,sha256=0aTb3DfzynAcVin4nMAEje8ekjgBGO_FF9dVHDwpzoo,1790
|
|
474
474
|
render_sdk/public_api/models/runtime.py,sha256=tKW8by0zb_JR_eOz3A3Al_S1wXCmY-wMK-XEqF91YlM,206
|
|
475
475
|
render_sdk/public_api/models/scale_service_body.py,sha256=G0HuTlbOAh9FcscEIS1qbyY8NO_7U6TQ8LZ6Aub6m48,1566
|
|
476
476
|
render_sdk/public_api/models/schemas_user.py,sha256=GkJ38jc0EkhUstnMfSZQljQsS9tA3z35LOl6rRDdh7s,1600
|
|
@@ -545,7 +545,7 @@ render_sdk/workflows/callback_api/api/default/post_register_tasks.py,sha256=k3ci
|
|
|
545
545
|
render_sdk/workflows/callback_api/api/default/post_run_subtask.py,sha256=i6epkJTyjynrFr0qiny63RIlJLlnt87HCbcRSVR5-3g,4180
|
|
546
546
|
render_sdk/workflows/callback_api/client.py,sha256=o_mdLqyBCQstu5tS1WZFwqIEbGwkvWQ7eQjuCJw_5VY,12419
|
|
547
547
|
render_sdk/workflows/callback_api/errors.py,sha256=gO8GBmKqmSNgAg-E5oT-oOyxztvp7V_6XG7OUTT15q0,546
|
|
548
|
-
render_sdk/workflows/callback_api/models/__init__.py,sha256=
|
|
548
|
+
render_sdk/workflows/callback_api/models/__init__.py,sha256=uRwBSHP4JKkVFPpK_W7x9_yzAIGKb4CjCl8M1E2pFw0,895
|
|
549
549
|
render_sdk/workflows/callback_api/models/callback_request.py,sha256=U2WnaGLWaIaeVrvEp4THtJoYhjgvYSH2-a6EPPt25nw,2680
|
|
550
550
|
render_sdk/workflows/callback_api/models/input_response.py,sha256=BduvQTvIr3Wo8P1gCaegn4i2p7I4SnRjlo6FvWDOLqY,1658
|
|
551
551
|
render_sdk/workflows/callback_api/models/retry_config.py,sha256=3cNk_kYWqpGZ7gJHzH_ExD0pvN14KpkYUk8XRu9Xo4U,2333
|
|
@@ -553,22 +553,23 @@ render_sdk/workflows/callback_api/models/run_subtask_request.py,sha256=Ln8FxEMzS
|
|
|
553
553
|
render_sdk/workflows/callback_api/models/run_subtask_response.py,sha256=ALjpEFczMcyNd-8O0hS_G0rbrDWPpSUME7xdiSCigwg,1545
|
|
554
554
|
render_sdk/workflows/callback_api/models/subtask_result_request.py,sha256=n9c4m5AwK96Nj6vSqKo_nCcdIv0yXgv1DzhtP5OR75I,1555
|
|
555
555
|
render_sdk/workflows/callback_api/models/subtask_result_response.py,sha256=yctQ4RcR66P0bHRmbku4y4FPgn14P6dwk_uX90WV_GE,2983
|
|
556
|
-
render_sdk/workflows/callback_api/models/task.py,sha256=
|
|
556
|
+
render_sdk/workflows/callback_api/models/task.py,sha256=XOKRkHnljQvkBKOuvXwMTABDWEInKVoMNBUtLZP3zfY,3131
|
|
557
557
|
render_sdk/workflows/callback_api/models/task_complete.py,sha256=Ndszhd7ymt7E-SEdtdLtKqraCh3gDnOq1qPOk03uIpE,1462
|
|
558
558
|
render_sdk/workflows/callback_api/models/task_error.py,sha256=8-AdDCq8_3FIemWBXLbpA-O_VNrI18tThZtwdDSqHuQ,1799
|
|
559
559
|
render_sdk/workflows/callback_api/models/task_options.py,sha256=FQLzMVNGDUHYhKWokV6FbD2RKit5rGIGlSun_ibbhv0,1975
|
|
560
|
+
render_sdk/workflows/callback_api/models/task_parameter.py,sha256=SNWODy1hTnPdK-kyIOJXsdVYlj_r9V-P72dXIadoOcA,2486
|
|
560
561
|
render_sdk/workflows/callback_api/models/tasks.py,sha256=mb3M6wKIb47y3tzl2uompmt3QvDe3qhaYstg1b9aKzc,1819
|
|
561
|
-
render_sdk/workflows/callback_api/py.typed,sha256=
|
|
562
|
+
render_sdk/workflows/callback_api/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
|
|
562
563
|
render_sdk/workflows/callback_api/types.py,sha256=AX4orxQZQJat3vZrgjJ-TYb2sNBL8kNo9yqYDT-n8y8,1391
|
|
563
|
-
render_sdk/workflows/client.py,sha256=
|
|
564
|
-
render_sdk/workflows/executor.py,sha256=
|
|
565
|
-
render_sdk/workflows/runner.py,sha256=
|
|
566
|
-
render_sdk/workflows/task.py,sha256=
|
|
564
|
+
render_sdk/workflows/client.py,sha256=a1bhCpdSAQ1M3JdmuN1ThOjIR2WX6xLzv5QzA69RaPQ,8662
|
|
565
|
+
render_sdk/workflows/executor.py,sha256=KralE5cwnVngzjxK6ovn1c2YshB1YfBnewPyHJUGftY,3518
|
|
566
|
+
render_sdk/workflows/runner.py,sha256=MhrU86Iru-SC-ybSpMAXekist_tSy-yiUVh2-ORpE_k,5161
|
|
567
|
+
render_sdk/workflows/task.py,sha256=ivT35hkP_c9Xyg1O3hrZty9H4Gsduh4raJgH6hOLkEA,7576
|
|
567
568
|
render_sdk/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
568
|
-
render_sdk/workflows/tests/test_end_to_end.py,sha256=
|
|
569
|
+
render_sdk/workflows/tests/test_end_to_end.py,sha256=QFsbcdAJHP9rnuoSfSIZxPJ8sPk49G4cNKbJbpgnyg0,5211
|
|
569
570
|
render_sdk/workflows/tests/test_executor.py,sha256=8l5RtrAJKB3ZiWUr7aE6tVWJgCWWEHfadPvtqc3uvzs,4685
|
|
570
|
-
render_sdk/workflows/tests/test_registration.py,sha256=
|
|
571
|
-
render_sdk-0.1.
|
|
572
|
-
render_sdk-0.1.
|
|
573
|
-
render_sdk-0.1.
|
|
574
|
-
render_sdk-0.1.
|
|
571
|
+
render_sdk/workflows/tests/test_registration.py,sha256=mc3VIu1uvx3QvhBjJheolCxuBW9u4lQdygOKzQOYOlA,4032
|
|
572
|
+
render_sdk-0.1.3.dist-info/METADATA,sha256=6NqlMZDfcmVdZSaPVj-sOCxXC3ADvT27kbha-6RNKtc,2731
|
|
573
|
+
render_sdk-0.1.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
574
|
+
render_sdk-0.1.3.dist-info/licenses/LICENSE,sha256=YQ01omJ4YGRAeEslBDXIx9tgO1VbIFYsusdCjQwjwW4,11336
|
|
575
|
+
render_sdk-0.1.3.dist-info/RECORD,,
|
|
File without changes
|