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
start
andstop
methods for starting and stopping the server.The
check
method for checking if the server is running.The
urls
property 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.