Integration#
While PyPIM can be used by an app developer, it can also be used behind the scene by other PyAnsys libraries to manage remote instances of the products that they interact with. This enables an app developer to write code that works both in an environment configured with PyPIM and an environment without such a configuration.
For example, an app developer can write the following code:
from ansys.mapdl.core import launch_mapdl
mapdl = launch_mapdl()
The preceding code replaces this much longer code:
import ansys.platform.instancemanagement as pypim
from ansys.mapdl.core import launch_mapdl
if pypim.is_configured():
pim = pypim.connect()
instance = pim.create_instance(product_name="mapdl")
channel = instance.build_grpc_channel(
options=[("grpc.max_receive_message_length", 8 * 1024**2)]
)
mapdl = Mapdl(channel=channel)
else:
mapdl = launch_mapdl()
This page provides guidelines for implementing the launch_*
method that
takes PyPIM into account. Just like the entire PIM API, this page is targeted
only toward products that are stateful and require explicit lifecycle
management.
Usage#
Dependency#
PyPIM
uses semantic versioning. To depend on PyPIM, a library
must include the following require
string:
"ansys-platform-instancemanagement~=1.0"
Condition for PyPIM usage#
The condition for using PyPIM transparently is that you must be able to launch the product in an environment configured with PyPIM without specifying launch information. In other words, PyPIM must be the default startup method in an environment that is configured with PyPIM.
To integrate PyPIM correctly, you should use either a generic method such as
the launch_my_product()
method or a constructor that usually starts a local process.
For example, with PyMAPDL in an environment configured with PyPIM, this code uses PyPIM:
from ansys.mapdl.core import launch_mapdl
mapdl = launch_mapdl()
However, this code does not use PyPIM:
from ansys.mapdl.core import launch_mapdl
mapdl = launch_mapdl(exec_file="/usr/bin/mapdl")
Start a gRPC product#
To use PyPIM, the code flow should first verify if PyPIM is configured to use
the is_configured()
method. Then, ensure that the caller of the
launch_my_product()
method has not specified how to launch it.
If both conditions are met:
Connect to PyPIM with the
connect()
method.Create an instance with
Client.create_instance()
method.Wait for the instance to be ready with the
Instance.wait_for_ready()
method.Build a gRPC channel with the
Instance.build_grpc_channel()
method.
Typically, the resulting code looks like this:
import ansys.platform.instancemanagement as pypim
def launch_my_product():
if pypim.is_configured():
pim = pypim.connect()
self.instance = pim.create_instance("my_product_name")
self.instance.wait_for_ready()
channel = self.instance.build_grpc_channel()
else:
# usual start-up
self.process = subprocess.run(...)
channel = grpc.insecure_chanel(...)
When stopping the product, use this code to ensure that the remote instance is deleted:
def stop(self):
if self.instance is not None:
self.instance.delete()
Note
While it is PyPIM’s responsibility to clean up any resource and process associated with the product, relevant product-specific cleanup can still be performed.
Start a non-gRPC product#
While the code flow for a non-gRPC product is the same, connection information is more specific.
For a REST-ful product, the base uniform resource identifier (URI) must be found under
instance.services["http"].uri
and all requests must include the headers ininstance.services["http"].headers
.For other protocols, an agreement between the PIM implementation and the client code determines how to pass the required information in a dedicated entry in
.services
.
Testing#
When testing the PyPIM integration, you should not rely on an actual PIM implementation. Instead, you should mock the interaction with PyPIM. Verifying that a specific PIM implementation is able to start and provide an endpoint to the product in a specific environment is the responsibility of the team managing this environment.
This test approach mocks PyPIM behavior, resulting in a verbose test with no additional dependencies. It is also not subject to bugs in PyPIM or in a PIM implementation.
The initial setup of such a mock can look like this:
from unittest.mock import create_autospec
import grpc
import ansys.platform.instancemanagement as pypim
def test_pim(monkeypatch):
# Start the product
product = launch_my_product(port=50052)
# Create a mock PyPIM instance object representing the running product
mock_instance = pypim.Instance(
definition_name="definitions/fake-product",
name="instances/fake-product",
ready=True,
status_message=None,
services={"grpc": pypim.Service(uri="localhost:50052", headers={})},
)
# Create a working gRPC channel to this product
pim_channel = grpc.insecure_channel("localhost:50052")
# Mock the wait_for_ready method so that it immediately returns
mock_instance.wait_for_ready = create_autospec(mock_instance.wait_for_ready)
# Mock the `build_grpc_channel` to return the working channel
mock_instance.build_grpc_channel = create_autospec(
mock_instance.build_grpc_channel, return_value=pim_channel
)
# Mock the deletion method
mock_instance.delete = create_autospec(mock_instance.delete)
# Mock the PyPIM client so that on the "create_instance" call it returns the mock instance
# Note: the host and port here will not be used.
mock_client = pypim.Client(channel=grpc.insecure_channel("localhost:12345"))
mock_client.create_instance = create_autospec(
mock_client.create_instance, return_value=mock_instance
)
# Mock the general PyPIM connection and configuration check method to expose the mock client.
mock_connect = create_autospec(pypim.connect, return_value=mock_client)
mock_is_configured = create_autospec(pypim.is_configured, return_value=True)
monkeypatch.setattr(pypim, "connect", mock_connect)
monkeypatch.setattr(pypim, "is_configured", mock_is_configured)
This initial setup is faking all the necessary parts of PyPIM. From here,
calling the launch_my_product()
method with no parameter is expected
to call only the mocks, which the test should now do:
my_product = launch_my_product()
After this call, the test is ready to make all the assertions verifying that the PyPIM workflow was applied:
# The launch method checked if it was in a PyPIM environment
assert mock_is_configured.called
# It connected to PyPIM
assert mock_connect.called
# It created a remote instance through PyPIM
mock_client.create_instance.assert_called_with(
product_name="my_product_name", product_version=None
)
# It waited for this instance to be ready
assert mock_instance.wait_for_ready.called
# It created an gRPC channel from this instance
assert mock_instance.build_grpc_channel.called
# It connected using the channel created by PyPIM
assert my_product._channel == pim_channel
When stopping the product, the test should also verify that the remote instance is deleted:
# Stop the product
my_product.stop()
assert mock_instance.delete.called
Example#
An example of such an integration can be seen in this PyMAPDL pull request.