diff --git a/src/ansys/mechanical/core/embedding/rpc/client.py b/src/ansys/mechanical/core/embedding/rpc/client.py index afa61d447..a7f1e7d91 100644 --- a/src/ansys/mechanical/core/embedding/rpc/client.py +++ b/src/ansys/mechanical/core/embedding/rpc/client.py @@ -25,8 +25,6 @@ import time import rpyc -from rpyc.utils.classic import upload - class Client: """Client for connecting to Mechanical services.""" @@ -57,8 +55,20 @@ def __init__(self, host: str, port: int, timeout: float = 60.0): def __getattr__(self, attr): if hasattr(self.root, attr): return getattr(self.root, attr) - # if hasattr(self.root, attr+"__property__"): - # return getattr(self.root) + propget_name = f"propget_{attr}" + if hasattr(self.root, propget_name): + exposed_fget = getattr(self.root, propget_name) + return exposed_fget() + return self.__dict__.items[attr] + """ + def __setattr__(self, attr, value): + if hasattr(self.root, attr): + inner_prop = getattr(self.root.__class__, attr) + if isinstance(inner_prop, property): + inner_prop.fset(self.root, value) + else: + super().__setattr__(attr, value) + """ def _connect(self): self._wait_until_ready() @@ -158,6 +168,6 @@ def _download_file(self, remote_file_path, local_file_path, chunk_size=1024, ove print(f"File {remote_file_path} downloaded to {local_file_path}") - @property - def project_directory(self): - return self.root.exposed_run_python_script("ExtAPI.DataModel.Project.ProjectDirectory") + # @property + # def project_directory(self): + # return self.root.exposed_run_python_script("ExtAPI.DataModel.Project.ProjectDirectory") diff --git a/src/ansys/mechanical/core/embedding/rpc/server.py b/src/ansys/mechanical/core/embedding/rpc/server.py index 99dd6ce75..9d0e428c7 100644 --- a/src/ansys/mechanical/core/embedding/rpc/server.py +++ b/src/ansys/mechanical/core/embedding/rpc/server.py @@ -67,7 +67,7 @@ def _install_class(self, impl): if methodtype == MethodType.METHOD: self._install_method(method) elif methodtype == MethodType.PROP: - self._install_property(method) + self._install_property(method, methodname) def on_connect(self, conn): """Handle client connection.""" @@ -78,13 +78,27 @@ def on_disconnect(self, conn): """Handle client disconnection.""" print("Client disconnected") + def _curry_property(self, prop, propname, get: bool): + """Curry the given property.""" + + def posted(*arg): + def curried(): + if get: + return getattr(prop._owner, propname) + else: + setattr(prop._owner, propname, *arg) + + return self._poster.post(curried) + + return posted + def _curry_method(self, method, realmethodname): """Curry the given method.""" - def posted(*args): + def posted(*args, **kwargs): def curried(): original_method = getattr(method._owner, realmethodname) - result = original_method(*args) + result = original_method(*args, **kwargs) return result return self._poster.post(curried) @@ -104,28 +118,47 @@ def curried(): return posted - def _install_property(self, property: property): + def _install_property(self, prop: property, propname: str): """Install property with inner and exposed pairs.""" # TODO: check how rpyc has property - print("installing property") + # print("installing property") + + # install exposed_fget and fset + # exposed fget gets the inner attr ? + + # get value using poster here + + if prop.fget: + exposed_get_name = f"exposed_propget_{propname}" + def exposed_propget(): + """Convert to exposed getter.""" + f = self._curry_property(prop.fget, propname, True) + result = f() + return result + setattr(self, exposed_get_name, exposed_propget) + if prop.fset: + exposed_set_name = f"exposed_propset_{propname}" + def exposed_propset(arg): + """Convert to exposed getter.""" + f = self._curry_property(prop.fset, propname, True) + result = f(arg) + return result + setattr(self, exposed_set_name, exposed_propset) def _install_method(self, method): - """Install methods of impl with inner and exposed pairs.""" - exposed_name = f"exposed_{method.__name__}" - inner_name = f"inner_{method.__name__}" + methodname = method.__name__ + self._install_method_with_name(methodname, methodname) - def inner_method(*args): - """Convert to inner method.""" - result = method(*args) - return result + def _install_method_with_name(self, method, methodname, innername): + """Install methods of impl with inner and exposed pairs.""" + exposed_name = f"exposed_{methodname}" - def exposed_method(*args): + def exposed_method(*args, **kwargs): """Convert to exposed method.""" - f = self._curry_method(method, method.__name__) - result = f(*args) + f = self._curry_method(method, innername) + result = f(*args, **kwargs) return result - setattr(self, inner_name, inner_method) setattr(self, exposed_name, exposed_method) def _install_function(self, function): @@ -271,9 +304,9 @@ def __init__(self, app): def __repr__(self): return '"ServiceMethods instance"' - # @property + @property @remote_method - def get_project_name(self): + def project_name(self): return self.helper_func() def helper_func(self): diff --git a/src/ansys/mechanical/core/embedding/rpc/utils.py b/src/ansys/mechanical/core/embedding/rpc/utils.py index 36baeaf20..038e7c43a 100644 --- a/src/ansys/mechanical/core/embedding/rpc/utils.py +++ b/src/ansys/mechanical/core/embedding/rpc/utils.py @@ -80,9 +80,11 @@ def try_get_remote_property(attrname: str, obj: typing.Any) -> typing.Tuple[str, if class_attribute.fget: if isinstance(class_attribute.fget, remote_method): getmethod = class_attribute.fget + getmethod._owner = obj if class_attribute.fset: if isinstance(class_attribute.fset, remote_method): setmethod = class_attribute.fset + setmethod._owner = obj return (attrname, property(getmethod, setmethod)) diff --git a/test.py b/test.py index 44c208e3d..35b964dac 100644 --- a/test.py +++ b/test.py @@ -31,6 +31,7 @@ def __setattr__(self, attr, value): inner_prop = getattr(self._inner.__class__, attr) if isinstance(inner_prop, property): inner_prop.fset(self._inner, value) + print(inner_prop.fset.__name__) else: super().__setattr__(attr, value) diff --git a/test_client.py b/test_client.py index d021ba289..e2091a53c 100644 --- a/test_client.py +++ b/test_client.py @@ -33,19 +33,20 @@ def launch_mechanical(port: int, version: int) -> Client: # c2: DefaultServiceMethods = launch_server() => creates client, returns client.roo - # print(client.get_project_name()) + client.project_name = "hello" + print(client.project_name) # client.change_project_name("lol") # print(client.get_project_name()) # print(client.run_python_script("ExtAPI.DataModel.Project", False, "WARNING", 2000)) - print( - client.run_python_script( - "ExtAPI.DataModel.Project", - enable_logging=False, - log_level="WARNING", - progress_interval=2000, - ) - ) + # print( + # client.run_python_script( + # "ExtAPI.DataModel.Project", + # enable_logging=False, + # log_level="WARNING", + # progress_interval=2000, + # ) + # ) # print(client.project_directory) # will this work ? since file is not in server diff --git a/testcopy.py b/testcopy.py new file mode 100644 index 000000000..5181f8195 --- /dev/null +++ b/testcopy.py @@ -0,0 +1,67 @@ +G_stuff = 1 + +class Foo: + @property + def bar(self): + return G_stuff + @bar.setter + def bar(self, value): + global G_stuff + G_stuff = value + + def propget_bar(self): + return self.bar + def propset_bar(self, value): + self.bar = value + + +class Baz: + def __init__(self, typ): + print("callinginit") + super().__setattr__('_inner', typ()) + super().__setattr__('x', 100) + # here install the property + + def __getattr__(self, attr): + print(f"calling gettr {attr}") + if hasattr(self._inner, attr): + return getattr(self._inner, attr) + return self.__dict__.items[attr] + + def __setattr__(self, attr, value): + if hasattr(self._inner, attr): + inner_prop = getattr(self._inner.__class__, attr) + if isinstance(inner_prop, property): + inner_prop.fset(self._inner, value) + else: + super().__setattr__(attr, value) + +import sys + +if __name__ == "__main__": + foo=Foo() + print(foo.__class__.__dict__) + barprop: property =getattr(Foo, "bar") + print(barprop.fget(foo)) + print(barprop.fset(foo, 3)) + print(foo.bar) + baz = Baz(Foo) + sys.exit(0) + + # baz = Baz(Foo) + # assert baz.bar == 1 + # print(baz.bar) + # print(baz.x) + # baz.x=200 + # assert baz.x ==200 + # baz.bar = 2 + # #setattr(baz, "bar", 2) + # assert G_stuff == 2 + # assert baz.bar == 2 + + + +# x = "some string" +# x.capitalize() + +# getattr(getattr(x, "capitalize"), "__call__")() \ No newline at end of file diff --git a/tests/embedding/test_rpc.py b/tests/embedding/test_rpc.py deleted file mode 100644 index 92133d942..000000000 --- a/tests/embedding/test_rpc.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import threading -import time - -import pytest -import rpyc - -from ansys.mechanical.core.embedding.rpc import Client, MechanicalEmbeddedServer - - -def get_project_name(app: "ansys.mechanical.core.embedding.App"): - return app.DataModel.Project.Name - - -@pytest.fixture(scope="module") -def start_server(): - server = MechanicalEmbeddedServer( - port=18861, - version=242, - methods=[get_project_name], - # impl=mod.ServiceMethods, - ) - server_thread = threading.Thread(target=server.start) - server_thread.daemon = True - server_thread.start() - yield - server.stop() - server_thread.join() - - -def test_client(start_server, printer): - c1 = Client("localhost", 18861) - server = c1.root - - project_name = server.get_project_name() - printer(project_name) - assert project_name == "Project" - c1.close()