Extending edgetest through plugins
==================================
``edgetest`` uses `pluggy `_ to implement a simple plugin
framework that allows for additional functionality. We expose the following hooks:
+---------------------------------------------------------+----------------------------------------------+
| Hook | Description |
| | |
+=========================================================+==============================================+
| :py:meth:`edgetest.hookspecs.addoption` | | This hook allows the user to add global or |
| | | environment-level hooks to the |
| | | configuration schema. |
+---------------------------------------------------------+----------------------------------------------+
| :py:meth:`edgetest.hookspecs.pre_run_hook` | | This hook allows the user to execute any |
| | | necessary set up code before creating the |
| | | virtual environment(s). |
+---------------------------------------------------------+----------------------------------------------+
| :py:meth:`edgetest.hookspecs.path_to_python` [#f1]_ | | This hook returns the path to the python |
| | | executable. |
+---------------------------------------------------------+----------------------------------------------+
| :py:meth:`edgetest.hookspecs.create_environment` [#f1]_ | This hook creates the virtual environment. |
+---------------------------------------------------------+----------------------------------------------+
| :py:meth:`edgetest.hookspecs.run_update` [#f1]_ | | This hook runs the environment update. |
| | | As an example, you could implement updates |
| | | via `pip`, `conda`, or some other package |
| | | manager. |
+---------------------------------------------------------+----------------------------------------------+
| :py:meth:`edgetest.hookspecs.post_run_hook` | | This hook executes code after the testing |
| | | has completed. Commonly used for creating |
| | | notifications. |
+---------------------------------------------------------+----------------------------------------------+
.. rubric:: Footnotes
.. [#f1]
Only one plugin can be used at a time.
Example
-------
Suppose you want to create a plugin that uses ``conda`` to create your virtual
environments. The typical naming convention for a plugin is ``-``,
so let's name our package ``edgetest-conda``. First, let's create the hooks:
``edgetest-conda/edgetest_conda/plugin.py``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let's create the hooks. First, we want to create a hook for :py:meth:`edgetest.hookspecs.addoption`
so we can add two sections to our environment configuration: ``conda_install``
and ``python_version``.
.. code-block:: python
import pluggy
from edgetest.schema import Schema
hookimpl = pluggy.HookimplMarker("edgetest")
@hookimpl
def addoption(schema: Schema):
"""Add an environment-level variable for conda installation options.
Parameters
----------
schema : Schema
The schema class.
"""
schema.add_envoption(
"conda_install",
{
"type": "list",
"schema": {"type": "string"},
"coerce": "listify",
"default": None,
"nullable": True,
},
)
schema.add_envoption(
"python_version", {"type": "string", "default": "3.7", "coerce": str}
)
Here, we're using :py:meth:`edgetest.schema.Schema.add_envoption`` to create a new
Cerberus validated section to the configuration file. Next, we'll create a hook
for :py:meth:`edgetest.hookspecs.create_environment` to let conda handle environment
creation.
.. code-block:: python
from pathlib import Path
from typing import Dict
from edgetest.logger import get_logger
from edgetest.utils import _run_command
LOG = get_logger(__name__)
@hookimpl
def create_environment(basedir: Path, envname: str, conf: Dict):
"""Create the conda environment.
Parameters
----------
basedir : Path
The base directory location for the environment.
envname : str
The name of the virtual environment.
conf : dict
The configuration dictionary for the environment. We will look for ``conda_install``.
Raises
------
RuntimeError
Error raised if the environment cannot be created.
"""
_run_command(
"conda",
"create",
"-p",
str(basedir / envname),
f"python={conf['python_version']}",
"--yes",
)
# Install any conda packages
if conf.get("conda_install"):
LOG.info(f"Installing conda packages for {envname}")
_run_command(
"conda",
"install",
"-p",
str(basedir / envname),
*conf["conda_install"],
"--yes",
)
LOG.info(f"Successfully installed conda packages for {envname}")
``edgetest-conda/setup.cfg``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: ini
:emphasize-lines: 11-13
[metadata]
name = edgetest-conda
...
[options]
packages = find:
...
install_requires =
edgetest>=2021.11.0
[options.entry_points]
edgetest =
conda = edgetest_conda.plugin
The ``options.entry_points`` section here creates the link between ``edgetest`` and your plugin.