diff --git a/src/ansys/rocky/prepost/__init__.py b/src/ansys/rocky/prepost/__init__.py index fd197471..5c9e7c03 100644 --- a/src/ansys/rocky/prepost/__init__.py +++ b/src/ansys/rocky/prepost/__init__.py @@ -1,4 +1,5 @@ from .client import connect_to_rocky +from .launcher import launch_rocky # TODO: Set __version__ # try: @@ -9,4 +10,7 @@ # __version__ = importlib_metadata.version(__name__.replace(".", "-")) -__all__ = ["connect_to_rocky"] +__all__ = [ + "connect_to_rocky", + "launch_rocky", +] diff --git a/src/ansys/rocky/prepost/client.py b/src/ansys/rocky/prepost/client.py index ef8b0e12..baa0210e 100644 --- a/src/ansys/rocky/prepost/client.py +++ b/src/ansys/rocky/prepost/client.py @@ -29,6 +29,9 @@ def __init__(self, rocky_api): def api(self): return self._api_adapter + def close(self): + self._api.Exit() + class _ApiElementProxy: def __init__(self, pyro_api, pool_id): @@ -38,7 +41,7 @@ def __init__(self, pyro_api, pool_id): def GetNumpyCurve(self, curve_name, unit=None): api = self._pyro_api serpent_dicts = api.SendToApiElement( - self._pool_id, "GetNumpyCurve", (curve_name, unit) + self._pool_id, "GetNumpyCurve", curve_name, unit ) numpy_data = [serpent.tobytes(d) for d in serpent_dicts] return tuple(pickle.loads(b) for b in numpy_data) @@ -55,5 +58,10 @@ def CallProxy(*args, **kwargs): def deserialize(cls, classname, serialized): return cls(_ROCKY_API, serialized["_api_element_id"]) + @classmethod + def serialize(cls, obj) -> dict: + return {"__class__": "_ApiElementProxy", "_api_element_id": obj._pool_id} + Pyro5.api.register_dict_to_class("ApiElementProxy", _ApiElementProxy.deserialize) +Pyro5.api.register_class_to_dict(_ApiElementProxy, _ApiElementProxy.serialize) diff --git a/src/ansys/rocky/prepost/exceptions.py b/src/ansys/rocky/prepost/exceptions.py new file mode 100644 index 00000000..61317475 --- /dev/null +++ b/src/ansys/rocky/prepost/exceptions.py @@ -0,0 +1,2 @@ +class PyRockyError(Exception): + """Generic exception for PyRocky API""" diff --git a/src/ansys/rocky/prepost/launcher.py b/src/ansys/rocky/prepost/launcher.py new file mode 100644 index 00000000..ca5edeb8 --- /dev/null +++ b/src/ansys/rocky/prepost/launcher.py @@ -0,0 +1,63 @@ +import contextlib +import os +from pathlib import Path +import subprocess +import time +from typing import Optional + +from Pyro5.errors import CommunicationError + +from ansys.rocky.prepost.client import RockyClient, connect_to_rocky +from ansys.rocky.prepost.exceptions import PyRockyError + + +def launch_rocky(rocky_exe: Optional[Path] = None) -> RockyClient: + """ + Launch Rocky executable with PyRocky server enabled, wait Rocky to start up and + return a `RockyClient` instance. + """ + if rocky_exe is None: + for awp_root in ["AWP_ROOT241", "AWP_ROOT232"]: + if awp_root not in os.environ: + continue + + rocky_exe = Path(os.environ[awp_root]) / "Rocky/bin/Rocky.exe" + if rocky_exe.is_file(): + break + else: + raise FileNotFoundError("Rocky executable not found") + else: + if not rocky_exe.is_file(): + raise FileNotFoundError(f"Rocky executable not found at {rocky_exe}") + + cmd = [rocky_exe, "--pyrocky"] + with contextlib.suppress(subprocess.TimeoutExpired): + rocky_process = subprocess.Popen(cmd) + rocky_process.wait(timeout=3) + + # Rocky.exe call returned to soon, something happen + if rocky_process.returncode is not None: + raise RockyLaunchError(f"Error launching Rocky:\n {' '.join(cmd)}") + + client = connect_to_rocky() + + # TODO: A more elegant way to find out that Rocky Pyro server started. + for _ in range(_WAIT_ROCKY_START): + try: + client.api.GetProject() + except CommunicationError: + time.sleep(1) + else: + break + else: + raise RockyLaunchError("Could not connect Rocky remote server: timed out") + + client._process = rocky_process + return client + + +_WAIT_ROCKY_START = 60 + + +class RockyLaunchError(PyRockyError): + """Raised for errors occurred during Rocky application launch"""