Define the HTTP Server launcher plugin#
This example shows the launcher plugin that is used to start the Python HTTP server. The full source is available in the examples/example_httpserver_plugin directory on GitHub.
While this example explains some aspects of the code, for information on how to create a plugin for the Local Product Launcher, see Launcher plugin creation.
Launcher code#
The LauncherConfig class determines which options are available to the user when configuring the launcher. It exposes
a single option directory, which determines which directory the server is to serve files from.
@dataclass
class LauncherConfig:
"""Defines the configuration options for the HTTP server launcher."""
directory: str = field(default=os.getcwd())
The Launcher class actually starts the server. It needs to fulfill the interface defined by the
LauncherProtocol class. Namely, this interface consists of these endpoints:
The
startandstopmethods for starting and stopping the server.The
checkmethod for checking if the server is running.The
urlsproperty for getting the URLs that the the server is serving requests on.
class Launcher(LauncherProtocol[LauncherConfig]):
"""Implements launching an HTTP server."""
CONFIG_MODEL = LauncherConfig
SERVER_SPEC = {"main": ServerType.GENERIC}
def __init__(self, *, config: LauncherConfig):
"""Instantiate the HTTP server launcher."""
self._config = config
def start(self) -> None:
"""Start the HTTP server."""
port = find_free_ports()[0]
self._url = f"localhost:{port}"
self._process = subprocess.Popen(
[
sys.executable,
"-m",
"http.server",
"--directory",
self._config.directory,
str(port),
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
text=True,
)
def stop(self, *, timeout: float | None = None) -> None:
"""Stop the HTTP server."""
self._process.terminate()
try:
self._process.wait(timeout=timeout)
except subprocess.TimeoutExpired:
self._process.kill()
self._process.wait()
def check(self, timeout: float | None = None) -> bool:
"""Check if the server is running."""
try:
# As a simple check, we try to get the main page from the
# server. If it is accessible, we the server is running.
# If not, we assume it is not running.
requests.get(f"http://{self._url}")
return True
except requests.RequestException:
return False
@property
def urls(self) -> dict[str, str]:
"""Addresses on which the server is serving content."""
return {"main": self._url}
The local product launcher uses this minimal interface to construct a ProductInstance class,
which adds some additional functionality on top. For example, the ProductInstance class automatically
stops the server when the Python process terminates.
Entrypoint configuration#
Besides the launcher code, the plugin must be registered by adding an entrypoint to
the pyproject.toml file, as described in Register entrypoint. In this example,
flit is used as a build tool. Thus, the pyproject.toml file looks like this:
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "example_httpserver_plugin"
authors = [{name = "ANSYS, Inc.", email = "pyansys.core@ansys.com"}]
dynamic = ["version", "description"]
dependencies = ["ansys-tools-local-product-launcher", "requests"]
[project.entry-points."ansys.tools.local_product_launcher.launcher"]
"example_httpserver.direct" = "example_httpserver_plugin:Launcher"
"example_httpserver.__fallback__" = "example_httpserver_plugin:Launcher"
Two entrypoints for the local product launcher are defined:
direct: A launch mode that users can configure.__fallback__: A fallback mode that is used if no configuration is available.
Both entrypoints use the same launcher class.