tox is a generic virtualenv management and test command line tool
Hook-based extensibility system using pluggy that allows plugins to extend CLI options, configuration, environment types, and execution hooks. Tox's plugin system supports both entry-point plugins and toxfile.py plugins for maximum flexibility.
The core decorator for marking plugin hook implementations.
impl: Callable[[_F], _F]
"""
Decorator to mark tox plugin hooks.
Usage:
@impl
def hook_function(...): ...
"""
NAME: str = "tox"
"""The name of the tox hook system."""Usage example:
from tox.plugin import impl
@impl
def tox_add_option(parser):
parser.add_argument("--my-option", help="Custom option")The complete set of hooks available for plugin development.
class ToxHookSpecs:
"""Hook specification definitions for tox plugins."""
def tox_add_option(self, parser: ToxParser) -> None:
"""
Add command line options.
Args:
parser: Command line parser to extend
"""
def tox_add_core_config(self, core_conf: CoreConfigSet) -> None:
"""
Add core configuration options.
Args:
core_conf: Core configuration set to extend
"""
def tox_add_env_config(self, env_conf: EnvConfigSet) -> None:
"""
Add environment-specific configuration options.
Args:
env_conf: Environment configuration set to extend
"""
def tox_register_tox_env(self, register) -> None:
"""
Register new environment types.
Args:
register: Environment registry to extend
"""
def tox_extend_envs(self) -> list[str]:
"""
Extend the list of available environments.
Returns:
list[str]: Additional environment names
"""
def tox_before_run_commands(self, tox_env: ToxEnv, run_conf, opts) -> None:
"""
Hook called before running commands in environment.
Args:
tox_env: Environment where commands will run
run_conf: Run configuration
opts: Additional options
"""
def tox_after_run_commands(self, tox_env: ToxEnv, run_conf, opts, outcome: Outcome) -> None:
"""
Hook called after running commands in environment.
Args:
tox_env: Environment where commands ran
run_conf: Run configuration
opts: Additional options
outcome: Execution outcome
"""The plugin manager coordinates plugin discovery and hook execution.
MANAGER: PluginManager
"""Global plugin manager instance."""Create a plugin by implementing hook functions:
# my_tox_plugin.py
from tox.plugin import impl
from tox.config.cli.parser import ToxParser
@impl
def tox_add_option(parser: ToxParser) -> None:
"""Add custom CLI option."""
parser.add_argument(
"--coverage-report",
action="store_true",
help="Generate coverage report after tests"
)
@impl
def tox_add_core_config(core_conf) -> None:
"""Add core configuration option."""
core_conf.add_config(
keys="coverage_threshold",
desc="Minimum coverage threshold",
of_type=float,
default=80.0
)
@impl
def tox_after_run_commands(tox_env, run_conf, opts, outcome) -> None:
"""Generate coverage report if requested."""
if getattr(opts, 'coverage_report', False):
print(f"Generating coverage report for {tox_env.name}")
# Coverage report generation logicRegister custom environment types:
from tox.plugin import impl
from tox.tox_env.api import ToxEnv
class DockerToxEnv(ToxEnv):
"""Docker-based tox environment."""
def create(self) -> None:
"""Create Docker container."""
print(f"Creating Docker container for {self.name}")
def execute(self, request):
"""Execute command in Docker container."""
# Docker execution logic
return ExecuteStatus(0, "success", "")
@impl
def tox_register_tox_env(register) -> None:
"""Register Docker environment type."""
register.add_env_type(
name="docker",
factory=DockerToxEnv,
description="Docker container environment"
)Extend tox configuration with plugin-specific options:
@impl
def tox_add_env_config(env_conf) -> None:
"""Add environment configuration options."""
env_conf.add_config(
keys="docker_image",
desc="Docker image to use",
of_type=str,
default="python:3.11"
)
env_conf.add_config(
keys="docker_volumes",
desc="Docker volumes to mount",
of_type=list,
default=[]
)Register plugins via setuptools entry points:
# setup.py or pyproject.toml
[project.entry-points.tox]
my_plugin = "my_tox_plugin"
docker_plugin = "tox_docker.plugin"Create toxfile.py in your project root:
# toxfile.py
from tox.plugin import impl
@impl
def tox_add_option(parser):
"""Project-specific plugin hook."""
parser.add_argument("--project-flag", help="Project specific option")Plugins can implement multiple hooks:
from tox.plugin import impl
class MyToxPlugin:
"""Comprehensive tox plugin."""
@impl
def tox_add_option(self, parser):
"""Add CLI options."""
parser.add_argument("--verbose-timing", action="store_true")
@impl
def tox_before_run_commands(self, tox_env, run_conf, opts):
"""Pre-execution hook."""
if getattr(opts, 'verbose_timing', False):
print(f"Starting {tox_env.name} at {time.time()}")
@impl
def tox_after_run_commands(self, tox_env, run_conf, opts, outcome):
"""Post-execution hook."""
if getattr(opts, 'verbose_timing', False):
print(f"Finished {tox_env.name} at {time.time()}")
# Register plugin instance
plugin = MyToxPlugin()Implement conditional logic in plugins:
@impl
def tox_before_run_commands(tox_env, run_conf, opts):
"""Conditional pre-execution logic."""
# Only run for Python environments
if hasattr(tox_env, 'python_executable'):
print(f"Python environment: {tox_env.name}")
# Only run in CI environment
if os.getenv('CI'):
print("Running in CI environment")
# Environment-specific logic
if tox_env.name.startswith('py3'):
print("Python 3 environment detected")Plugins can define their own configuration:
@impl
def tox_add_core_config(core_conf):
"""Add plugin configuration."""
core_conf.add_config(
keys="my_plugin_enabled",
desc="Enable my plugin functionality",
of_type=bool,
default=True
)
@impl
def tox_before_run_commands(tox_env, run_conf, opts):
"""Check plugin configuration."""
if tox_env.core["my_plugin_enabled"]:
print("Plugin is enabled")
else:
print("Plugin is disabled")Test plugins using tox's testing utilities:
# test_my_plugin.py
import pytest
from tox.plugin import impl
from tox.run import main
def test_plugin_option():
"""Test custom CLI option."""
result = main(['--my-option', '--help'])
# Assert option is present in help output
def test_plugin_environment():
"""Test custom environment type."""
result = main(['-e', 'docker'])
# Assert Docker environment runs correctlyHandle errors gracefully in plugins:
@impl
def tox_register_tox_env(register):
"""Register with error handling."""
try:
# Check if Docker is available
import docker
register.add_env_type(
name="docker",
factory=DockerToxEnv,
description="Docker environment"
)
except ImportError:
print("Docker not available, skipping Docker environment")Tox includes several built-in plugins that demonstrate best practices:
These serve as reference implementations for developing custom plugins with similar functionality.
Install with Tessl CLI
npx tessl i tessl/pypi-tox