Skip to content

Commit 93d1813

Browse files
ctruedenclaude
andcommitted
Replace invoke_method with get_attribute for language-agnostic proxy attribute access
Removes the now-unused invoke_method from ScriptSyntax and introduces get_attribute to handle attribute access in a language-specific way. Key changes: - Added ScriptSyntax.get_attribute(obj, attr) method - PythonSyntax: Returns "obj.attr" (gets field or bound method) - GroovySyntax: Returns try/catch block that attempts field access first, falls back to method reference (&) on MissingPropertyException - ProxyObject.__getattr__: Now uses syntax.get_attribute() instead of hardcoded string formatting - Removed invoke_method from both PythonSyntax and GroovySyntax (dead code) Groovy attribute access behavior: - obj.field (field only) → returns field value - obj.method (method only) → returns MethodClosure proxy - obj.foo (both field and method) → returns field (method inaccessible) - Workaround for conflicts: service.call("obj.method", args) This design maintains the elegant chaining behavior for Python while properly handling Groovy's different semantics for fields vs methods. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 1f2207f commit 93d1813

File tree

2 files changed

+24
-22
lines changed

2 files changed

+24
-22
lines changed

src/appose/syntax.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,20 @@ def call(self, function: str, arg_var_names: list[str]) -> str:
8989
...
9090

9191
@abstractmethod
92-
def invoke_method(
93-
self, object_var_name: str, method_name: str, arg_var_names: list[str]
94-
) -> str:
92+
def get_attribute(self, object_var_name: str, attribute_name: str) -> str:
9593
"""
96-
Generate a script expression to invoke a method on an object.
94+
Generate a script expression to retrieve a single attribute from an object.
9795
98-
The object must have been previously exported using task.export().
99-
This is used by the proxy mechanism to forward method calls to remote objects.
96+
This is used by proxy objects to implement __getattr__() for attribute access.
97+
The implementation should handle both fields and methods appropriately for
98+
the target language.
10099
101100
Args:
102101
object_var_name: The name of the variable referencing the object.
103-
method_name: The name of the method to invoke.
104-
arg_var_names: The names of input variables containing the arguments.
102+
attribute_name: The name of the attribute to retrieve.
105103
106104
Returns:
107-
A script expression that invokes the method and evaluates to its result.
105+
A script expression that evaluates to the attribute value or method reference.
108106
"""
109107
...
110108

@@ -148,11 +146,10 @@ def call(self, function: str, arg_var_names: list[str]) -> str:
148146
# Python function call syntax: function(arg0, arg1, ...)
149147
return f"{function}({', '.join(arg_var_names)})"
150148

151-
def invoke_method(
152-
self, object_var_name: str, method_name: str, arg_var_names: list[str]
153-
) -> str:
154-
# Python method invocation: object.method(arg0, arg1, ...)
155-
return f"{object_var_name}.{method_name}({', '.join(arg_var_names)})"
149+
def get_attribute(self, object_var_name: str, attribute_name: str) -> str:
150+
# Python attribute access: object.attribute
151+
# This returns either the field value or a bound method object.
152+
return f"{object_var_name}.{attribute_name}"
156153

157154
def get_attributes(self, object_var_name: str) -> str:
158155
# Return all attributes from dir(), including private ones.
@@ -185,11 +182,14 @@ def call(self, function: str, arg_var_names: list[str]) -> str:
185182
# Groovy function call syntax: function(arg0, arg1, ...)
186183
return f"{function}({', '.join(arg_var_names)})"
187184

188-
def invoke_method(
189-
self, object_var_name: str, method_name: str, arg_var_names: list[str]
190-
) -> str:
191-
# Groovy method invocation: object.method(arg0, arg1, ...)
192-
return f"{object_var_name}.{method_name}({', '.join(arg_var_names)})"
185+
def get_attribute(self, object_var_name: str, attribute_name: str) -> str:
186+
# Groovy attribute access: try field first, then method reference.
187+
# This handles the case where both field and method exist with same name:
188+
# field access takes precedence (Groovy semantics).
189+
return (
190+
f"try {{ {object_var_name}.{attribute_name} }} "
191+
f"catch (groovy.lang.MissingPropertyException e) {{ {object_var_name}.&{attribute_name} }}"
192+
)
193193

194194
def get_attributes(self, object_var_name: str) -> str:
195195
# Return all method names and property names from the object's metaclass.

src/appose/util/proxy.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,13 @@ def __init__(self, service: Service, var: str, queue: str | None):
9393
self._queue = queue
9494

9595
def __getattr__(self, name: str):
96-
# Immediately evaluate the attribute access on the worker.
97-
attr_expr = f"{self._var}.{name}"
96+
# Use the service's ScriptSyntax to generate the attribute access script.
97+
# This allows language-specific handling (e.g., Groovy's field vs method).
98+
syntax.validate(self._service)
99+
script = self._service._syntax.get_attribute(self._var, name)
98100

99101
try:
100-
task = self._service.task(attr_expr, queue=self._queue)
102+
task = self._service.task(script, queue=self._queue)
101103
task.wait_for()
102104
result = task.result()
103105
# If result is a worker_object, it will already be a ProxyObject

0 commit comments

Comments
 (0)