Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ee3a7e2
remote-connection server API changed.
franknli Aug 7, 2025
83c2a1d
chore: adding changelog file 237.miscellaneous.md [dependabot-skip]
pyansys-ci-bot Aug 7, 2025
b5e11dd
internal_wbexit became available at 25.2
franknli Aug 8, 2025
5b0f1b4
Merge remote-tracking branch 'refs/remotes/origin/fli/handle_api_chan…
franknli Aug 8, 2025
3f2a3e0
auto exiting server
franknli Aug 8, 2025
ede2107
fix code style
franknli Aug 8, 2025
2ecfe5a
fix code style
franknli Aug 8, 2025
401ea66
feat: ask user to allow remote host
jorgepiloto Aug 12, 2025
2c26e98
merge main
franknli Sep 12, 2025
4713800
merge main
franknli Sep 14, 2025
b74da83
correct previous commits
franknli Sep 14, 2025
3204efb
security options
franknli Sep 16, 2025
33353be
chore: adding changelog file 254.miscellaneous.md [dependabot-skip]
pyansys-ci-bot Sep 16, 2025
94f7c37
fix code style
franknli Sep 16, 2025
82a4da9
Merge remote-tracking branch 'refs/remotes/origin/fli/handle_api_chan…
franknli Sep 16, 2025
94b2522
code style
franknli Sep 16, 2025
d128dc3
style
franknli Sep 16, 2025
60168f7
handle older install
franknli Sep 17, 2025
3d10b89
code style
franknli Sep 17, 2025
00c5b80
fix long line
franknli Sep 17, 2025
f6f7cc0
fix unit tests
franknli Sep 17, 2025
dc4db24
update comment
franknli Sep 17, 2025
c5501c8
fix unit test
franknli Sep 17, 2025
02ce3f5
code style
franknli Sep 17, 2025
22a226b
typo
franknli Oct 1, 2025
bcbc7d6
merge main
franknli Oct 1, 2025
b26883f
fix command line quotes
franknli Oct 3, 2025
31549b2
chore: adding changelog file 260.miscellaneous.md [dependabot-skip]
pyansys-ci-bot Oct 3, 2025
93f10f2
fix code style
franknli Oct 3, 2025
c456f5a
merge pull
franknli Oct 3, 2025
d4e5b2e
merge main
franknli Oct 3, 2025
d490159
fix Linux workflow
franknli Oct 21, 2025
f7e3bfc
merge main
franknli Oct 21, 2025
8160f90
testing using cyberchannel package
franknli Nov 5, 2025
1918997
chore: adding changelog file 269.miscellaneous.md [dependabot-skip]
pyansys-ci-bot Nov 5, 2025
f7f666e
fix code style
franknli Nov 5, 2025
63283ac
git merge
franknli Nov 5, 2025
af97714
merge main
franknli Nov 5, 2025
2cfcc5b
update code style
franknli Nov 5, 2025
fde061c
add copyright
franknli Nov 5, 2025
35320e9
use ansys-common-tools
franknli Nov 25, 2025
38cefd2
chore: adding changelog file 277.maintenance.md [dependabot-skip]
pyansys-ci-bot Nov 25, 2025
c2c1259
merge main
franknli Nov 25, 2025
28b4b43
correct namaspace
franknli Nov 25, 2025
98ff988
fix unit tests
franknli Nov 25, 2025
f345a77
fix code style
franknli Nov 25, 2025
6340ef9
fix code style
franknli Nov 25, 2025
f5fb1a5
fix unit test
franknli Nov 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/237.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
remote-connection server API changed.
1 change: 1 addition & 0 deletions doc/changelog.d/254.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fli/handle api change for remote connection
1 change: 1 addition & 0 deletions doc/changelog.d/260.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fli/handle api change for remote connection
1 change: 1 addition & 0 deletions doc/changelog.d/269.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fli/handle api change for remote connection
1 change: 1 addition & 0 deletions doc/changelog.d/277.maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fli/handle api change for remote connection
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies = [
"ansys-platform-instancemanagement>=1.0.1",
"ansys-pythonnet>=3.1.0rc1",
"ansys-tools-path>=0.3.1",
"ansys-tools-common>=0.3.0",
"tqdm>=4.65.0",
"WMI>=1.4.9; platform_system=='Windows'",
]
Expand Down
42 changes: 35 additions & 7 deletions src/ansys/workbench/core/public_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

"""Module for public API on PyWorkbench."""

import atexit
import logging
import tempfile

Expand All @@ -40,15 +41,18 @@ class ClientWrapper(WorkbenchClient):
Path to a writable directory on the client computer.
host : str, default: None
Server computer's name or IP address.
security : str, default: 'mtls'
Transport mode used for connection security.
Options are: "insecure", "uds", "wnua", "mtls"
"""

def __init__(self, port, client_workdir=None, host=None):
def __init__(self, port, client_workdir=None, host=None, security="mtls"):
"""Create a PyWorkbench client that connects to a Workbench server."""
if host is None:
host = "localhost"
if client_workdir is None:
client_workdir = tempfile.gettempdir()
super().__init__(client_workdir, host, port)
super().__init__(client_workdir, host, port, security)
super()._connect()

def exit(self):
Expand All @@ -73,6 +77,8 @@ class LaunchWorkbench(ClientWrapper):
server_workdir : str, None
Path to a writable directory on the server computer. The default is ``None``,
in which case the user preference for the Workbench temporary file folder is used.
use_insecure_connection : bool, default: False
whether to use insecure connection between the server and clients
host : str, None
Server computer's name or IP address. The default is ``None`` for launching on the
local computer.
Expand Down Expand Up @@ -102,6 +108,7 @@ def __init__(
version=None,
client_workdir=None,
server_workdir=None,
use_insecure_connection=False,
host=None,
username=None,
password=None,
Expand All @@ -110,10 +117,19 @@ def __init__(
version = "252"

self._launcher = Launcher()
port = self._launcher.launch(version, show_gui, server_workdir, host, username, password)
port, security = self._launcher.launch(
version, show_gui, server_workdir, use_insecure_connection, host, username, password
)
if port is None or port <= 0:
raise Exception("Failed to launch Ansys Workbench service.")
super().__init__(port, client_workdir, host)
if use_insecure_connection:
print(
"Using insecure connection is not recommended. "
"Please see the documentation for your installed "
"product for additional information."
)
super().__init__(port, client_workdir, host, security)
atexit.register(self.exit)
self._exited = False

def exit(self):
Expand All @@ -134,6 +150,7 @@ def launch_workbench(
version=None,
client_workdir=None,
server_workdir=None,
use_insecure_connection=False,
host=None,
username=None,
password=None,
Expand All @@ -155,6 +172,8 @@ def launch_workbench(
server_workdir : str, None
Path to a writable directory on the server computer. The default is ``None``,
in which case the user preference for the Workbench temporary file folder is used.
use_insecure_connection : bool, default: False
whether to use insecure connection between the server and clients
host : str, None
Server computer's name or IP address. The default is ``None`` for launching on the
local computer.
Expand All @@ -179,11 +198,18 @@ def launch_workbench(

"""
return LaunchWorkbench(
show_gui, version, client_workdir, server_workdir, host, username, password
show_gui,
version,
client_workdir,
server_workdir,
use_insecure_connection,
host,
username,
password,
)


def connect_workbench(port, client_workdir=None, host=None):
def connect_workbench(port, client_workdir=None, host=None, security="mtls"):
"""Create a PyWorkbench client that connects to an already running Workbench server.

Parameters
Expand All @@ -195,6 +221,8 @@ def connect_workbench(port, client_workdir=None, host=None):
in which case the system temp directory is used.
host : str, default: None
Server computer's name or IP address. The default is ``None`` for the local computer.
security : str among 'mtls', 'wnua', 'insecure', default: 'mtls'
Transport mode used for connection security. The default is `mtls`.

Returns
-------
Expand All @@ -209,7 +237,7 @@ def connect_workbench(port, client_workdir=None, host=None):
>>> from ansys.workbench.core import connect_workbench
>>> wb = connect_workbench(port = 32588)
"""
return ClientWrapper(port, client_workdir, host)
return ClientWrapper(port, client_workdir, host, security)


__all__ = ["launch_workbench", "connect_workbench"]
22 changes: 17 additions & 5 deletions src/ansys/workbench/core/workbench_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
import re
import time

import grpc
import tqdm

from ansys.api.workbench.v0 import workbench_pb2 as wb
from ansys.api.workbench.v0.workbench_pb2_grpc import WorkbenchServiceStub
from ansys.tools.common.cyberchannel import create_channel
from ansys.workbench.core.example_data import ExampleData


Expand All @@ -50,13 +50,17 @@ class WorkbenchClient:
Hostname or IP address of the server.
server_port : int
Port number of the server.
server_security : string
Security mode of the server.
Options are: "insecure", "uds", "wnua", "mtls"
"""

def __init__(self, local_workdir, server_host, server_port):
def __init__(self, local_workdir, server_host, server_port, server_security):
"""Create a Workbench client."""
self.workdir = local_workdir
self._server_host = server_host
self._server_port = server_port
self._server_security = server_security
self._server_version = -1
self.__init_logging()

Expand All @@ -81,10 +85,18 @@ def __exit__(self, exc_type, exc_value, traceback):

def _connect(self):
"""Connect to the server."""
hnp = self._server_host + ":" + str(self._server_port)
self.channel = grpc.insecure_channel(hnp)
self.channel = create_channel(
host=self._server_host,
port=self._server_port,
transport_mode=self._server_security,
certs_dir=None,
grpc_options=None,
)
self.stub = WorkbenchServiceStub(self.channel)
logging.info(f"connected to the WB server at {hnp}")
logging.info(
f"connected to the WB server at {self._server_host}:{self._server_port} "
"using {self._server_security} connection"
)

def _disconnect(self):
"""Disconnect from the server."""
Expand Down
89 changes: 65 additions & 24 deletions src/ansys/workbench/core/workbench_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def launch(
version,
show_gui=True,
server_workdir=None,
use_insecure_connection=False,
host=None,
username=None,
password=None,
Expand All @@ -91,6 +92,8 @@ def launch(
server_workdir : str, default: None
Path to a writable directory on the server. The default is ``None``,
in which case the user preference for the Workbench temporary file folder is used.
use_insecure_connection : bool, default: False
whether to use insecure connection between the server and clients
host : str, default: None
Name or IP address of the server. The default is ``None``, which launches Workbench
on the local computer.
Expand Down Expand Up @@ -125,12 +128,21 @@ def launch(
"Launching PyWorkbench on a remote machine from Linux is not supported."
)

if not host and not self._wmi and int(version) < 252:
raise Exception("Launching PyWorkbench 25.1 or below on Linux is not supported.")

if host and (not username or not password):
raise Exception(
"Username and passwork must be specified "
"Username and password must be specified "
"to launch PyWorkbench on a remote machine."
)

security = "mtls"
if use_insecure_connection:
security = "insecure"
elif not host and self._wmi:
security = "wnua"

if self._wmi:
try:
if not host:
Expand Down Expand Up @@ -167,17 +179,44 @@ def launch(
args.append("--start-and-wait")
args.append("-nowindow")
args.append("-E")

# create command string
prefix = uuid.uuid4().hex
cmd = "StartServer(EnvironmentPrefix='"
cmd += prefix + "'"
cmd1 = "StartServer(EnvironmentPrefix='"
cmd1 += prefix + "'"
if server_workdir is not None:
# use forward slash only to avoid escaping as command line argument
server_workdir = server_workdir.replace("\\", "/")
cmd += ",WorkingDirectory='" + server_workdir + "'"
cmd += ")"
cmd1 += ",WorkingDirectory='" + server_workdir + "'"
cmd2 = str(cmd1)
cmd1 += ")"
cmd2 += ",Security='" + security + "'"
if host is not None:
cmd2 += ",AllowRemoteConnection=True"
cmd2 += ")"

if self._wmi:
quote_or_not = '"' # quotes needed when constructing command line
else:
quote_or_not = ""
cmd = (
quote_or_not
+ cmd2
+ """ if __scriptingEngine__.CommandContext.AddinManager.GetAddin("""
+ """'Ansys.RemoteWB.Addin').Version.Major > 1 else """
+ cmd1
+ quote_or_not
)
args.append(cmd)

command_line = " ".join(args)

# security precaution statement
if host is not None:
print("""The server started will allow remote access connections to be
established, possibly permitting control of the machine and any data which resides on it.
It is highly recommended to only utilize these features on a trusted, secure network.""")

successful = False
process = None
if self._wmi:
Expand Down Expand Up @@ -206,35 +245,37 @@ def launch(
logging.info(f"Workbench is launched successfully with process ID {self._process_id}.")
else:
logging.error("Workbench failed to launch on the host.")
return 0
return 0, security

# retrieve server port once WB is fully up running
port = None
timeout = 60 * 8 # set 8 minutes as upper limit for WB startup
start_time = time.time()
while True:
if self._wmi:
if self._wmi:
while True:
port = self.__getenv("ANSYS_FRAMEWORK_SERVER_PORT")
else:
for line in process.stdout:
line = line.rstrip()
if line.startswith("ANSYS_FRAMEWORK_SERVER_PORT="):
port = line[28:]
if port and port.startswith(prefix):
port = port[len(prefix) :]
break
else:
port = None
if time.time() - start_time > timeout:
logging.error("Failed to start Workbench service within reasonable timeout.")
break
time.sleep(10)
else:
for line in process.stdout:
line = line.rstrip()
if line.startswith("ANSYS_FRAMEWORK_SERVER_PORT="):
port = line[28:]
if port.startswith(prefix):
port = port[len(prefix) :]
break
if port and port.startswith(prefix):
port = port[len(prefix) :]
break
else:
port = None
if time.time() - start_time > timeout:
logging.error("Failed to start Workbench service within reasonable timeout.")
break
time.sleep(10)
if not port or int(port) <= 0:
logging.error("Failed to retrieve the port used by Workbench service.")
return 0
return 0, security
logging.info("Workbench service uses port: " + port)
return int(port)
return int(port), security

def __getenv(self, key):
value = None
Expand Down
Loading