Skip to content

Commit dbe41bf

Browse files
committed
reflect feedbacks
1 parent 28436c7 commit dbe41bf

File tree

6 files changed

+44
-17
lines changed

6 files changed

+44
-17
lines changed

src/designer_plugin/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ async def d3_api_aregister_module(
201201
timeout=aiohttp.ClientTimeout(timeout_sec) if timeout_sec else None,
202202
)
203203
except Exception as e:
204-
raise Exception(f"Failed to register module '{payload.moduleName}") from e
204+
raise Exception(f"Failed to register module: '{payload.moduleName}'") from e
205205

206206
plugin_response: PluginRegisterResponse = PluginRegisterResponse.model_validate(
207207
response

src/designer_plugin/d3sdk/ast_utils.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,13 @@ def filter_base_classes(class_node: ast.ClassDef) -> None:
5959

6060

6161
def filter_init_args(class_node: ast.ClassDef) -> list[str]:
62-
"""Remove excluded arguments from __init__ method and extract parameter names.
63-
64-
This function modifies the class_node in-place by:
65-
1. Removing excluded parameters from __init__ signature
66-
2. Removing excluded arguments from super().__init__() calls
67-
3. Returning the list of remaining parameter names (excluding 'self')
62+
"""Extract parameter names from the __init__ method of a class.
6863
6964
Args:
7065
class_node: The class definition node to process
7166
7267
Returns:
73-
List of parameter names that remain after filtering (excluding 'self')
68+
List of parameter names from __init__ (excluding 'self'), or empty list if no __init__ found
7469
"""
7570
for node in class_node.body:
7671
if not isinstance(node, ast.FunctionDef):

src/designer_plugin/d3sdk/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> t
231231
attrs["source_code_py27"] = f"{ast.unparse(class_node)}"
232232

233233
# Wrap all user-defined public methods to execute remotely via D3 API
234-
# Skip private methods (_*) and internal framework methods
234+
# Skip internal framework methods
235235
for attr_name, attr_value in attrs.items():
236236
if callable(attr_value) and not attr_name.startswith("__"):
237237
attrs[attr_name] = create_d3_plugin_method_wrapper(
@@ -318,7 +318,7 @@ def get_surface_uid(self, surface_name: str) -> dict[str, str]:
318318
319319
# Use as sync context manager
320320
with plugin.session("localhost", 80):
321-
result = await plugin.get_surface_uid("surface 1")
321+
result = plugin.get_surface_uid("surface 1")
322322
```
323323
Attributes:
324324
instance_code: The code used to instantiate the plugin remotely (set on init)

src/designer_plugin/d3sdk/function.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ def _args_to_assign(self, *args: Any, **kwargs: Any) -> str:
189189
String containing variable assignment statements, one per line.
190190
"""
191191
args_parts = [
192-
f"{self._function_info.args[i]}={repr(arg)}" for i, arg in enumerate(args)
192+
f"{param}={repr(arg)}"
193+
for param, arg in zip(self._function_info.args, args, strict=False)
193194
]
194195
kwargs_parts = [f"{name}={repr(value)}" for name, value in kwargs.items()]
195196
return "\n".join(args_parts + kwargs_parts)

src/designer_plugin/models.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ class PluginResponse(BaseModel, Generic[RetType]):
5959
@field_validator("returnValue", mode="before")
6060
@classmethod
6161
def parse_returnValue(cls, v: Any) -> Any:
62-
if isinstance(v, str):
63-
if v == "null":
64-
return None
65-
else:
66-
return json.loads(v)
67-
return v
62+
if not isinstance(v, str):
63+
return v
64+
if v == "null":
65+
return None
66+
try:
67+
return json.loads(v)
68+
except json.JSONDecodeError:
69+
return v
6870

6971
def returnCastValue(self, castType: type[RetCastType]) -> RetCastType:
7072
"""Validate and cast the return value to the specified type.

tests/test_core.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
Copyright (c) 2025 Disguise Technologies ltd
44
"""
55

6+
import pytest
7+
68
from designer_plugin.d3sdk.function import (
79
D3Function,
810
D3PythonScript,
@@ -290,3 +292,30 @@ def test_func(a: int, b: int = 10) -> int:
290292
assert "a=5" in payload.script
291293
assert "b=15" in payload.script
292294
assert "return a + b" in payload.script
295+
296+
def test_d3pythonscript_args_to_assign_with_extra_args(self):
297+
"""Test that _args_to_assign handles extra arguments gracefully.
298+
299+
This test will fail with the enumerate/indexing implementation:
300+
args_parts = [f"{self._function_info.args[i]}={repr(arg)}" for i, arg in enumerate(args)]
301+
302+
But will pass with the zip implementation:
303+
args_parts = [f"{param}={repr(arg)}" for param, arg in zip(self._function_info.args, args)]
304+
305+
The enumerate version causes IndexError when len(args) > len(function parameters).
306+
The zip version stops at the shorter sequence length.
307+
"""
308+
@d3pythonscript
309+
def test_func(a: int, b: int) -> int:
310+
return a + b
311+
312+
# This function has 2 parameters but we're passing 3 arguments
313+
# The first implementation will crash with IndexError
314+
# The second implementation will silently ignore the extra argument
315+
result = test_func._args_to_assign(1, 2, 3)
316+
317+
# Should only contain assignments for the defined parameters
318+
assert "a=1" in result
319+
assert "b=2" in result
320+
# The extra argument should be silently ignored by the zip implementation
321+
assert result.count("=") == 2 # Only 2 assignments

0 commit comments

Comments
 (0)