Plugin how-to guides

This page provides task-oriented guides for creating each type of virtualenv plugin.

Create a discovery plugin

Discovery plugins locate Python interpreters. Register your plugin under the virtualenv.discovery entry point group.

Implement the Discover interface:

from virtualenv.discovery.discover import Discover
from virtualenv.discovery.py_info import PythonInfo


class CustomDiscovery(Discover):
    @classmethod
    def add_parser_arguments(cls, parser):
        parser.add_argument("--custom-opt", help="custom discovery option")

    def __init__(self, options):
        super().__init__(options)
        self.custom_opt = options.custom_opt

    def run(self):
        # Locate Python interpreter and return PythonInfo
        python_exe = self._find_python()
        return PythonInfo.from_exe(str(python_exe))

    def _find_python(self):
        # Implementation-specific logic
        pass

Register the entry point:

[virtualenv.discovery]
custom = your_package.discovery:CustomDiscovery

Create a creator plugin

Creator plugins build the virtual environment structure. Register under virtualenv.create.

Implement the Creator interface:

from virtualenv.create.creator import Creator


class CustomCreator(Creator):
    @classmethod
    def add_parser_arguments(cls, parser, interpreter):
        parser.add_argument("--custom-creator-opt", help="custom creator option")

    def __init__(self, options, interpreter):
        super().__init__(options, interpreter)
        self.custom_opt = options.custom_creator_opt

    def create(self):
        # Create directory structure
        self.bin_dir.mkdir(parents=True, exist_ok=True)
        # Copy or symlink Python executable
        self.install_python()
        # Set up site-packages
        self.install_site_packages()
        # Write pyvenv.cfg
        self.set_pyenv_cfg()

Register the entry point using a naming pattern that matches platform and Python version:

[virtualenv.create]
cpython3-posix = virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Posix
cpython3-win = virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Windows

Create a seeder plugin

Seeder plugins install initial packages into the virtual environment. Register under virtualenv.seed.

Implement the Seeder interface:

from virtualenv.seed.seeder import Seeder


class CustomSeeder(Seeder):
    @classmethod
    def add_parser_arguments(cls, parser, interpreter, app_data):
        parser.add_argument("--custom-seed-opt", help="custom seeder option")

    def __init__(self, options, enabled, app_data):
        super().__init__(options, enabled, app_data)
        self.custom_opt = options.custom_seed_opt

    def run(self, creator):
        # Install packages into creator.bin_dir / creator.script("pip")
        self._install_packages(creator)

    def _install_packages(self, creator):
        # Implementation-specific logic
        pass

Register the entry point:

[virtualenv.seed]
custom = your_package.seed:CustomSeeder

Create an activator plugin

Activator plugins generate shell activation scripts. Register under virtualenv.activate.

Implement the Activator interface:

from virtualenv.activation.activator import Activator


class CustomShellActivator(Activator):
    def generate(self, creator):
        # Generate activation script content
        script_content = self._render_template(creator)
        # Write to activation directory
        dest = creator.bin_dir / self.script_name
        dest.write_text(script_content)

    def _render_template(self, creator):
        # Return activation script content
        return f"""
        # Custom shell activation script
        export VIRTUAL_ENV="{creator.dest}"
        export PATH="{creator.bin_dir}:$PATH"
        """

    @property
    def script_name(self):
        return "activate.custom"

Register the entry point:

[virtualenv.activate]
bash = virtualenv.activation.bash:BashActivator
fish = virtualenv.activation.fish:FishActivator
custom = your_package.activation:CustomShellActivator

Package and distribute a plugin

Use pyproject.toml to declare entry points:

[project]
name = "virtualenv-custom-plugin"
version = "1.0.0"
dependencies = ["virtualenv>=20.0.0"]

[project.entry-points."virtualenv.discovery"]
custom = "virtualenv_custom.discovery:CustomDiscovery"

[project.entry-points."virtualenv.create"]
custom-posix = "virtualenv_custom.creator:CustomCreator"

[project.entry-points."virtualenv.seed"]
custom = "virtualenv_custom.seeder:CustomSeeder"

[project.entry-points."virtualenv.activate"]
custom = "virtualenv_custom.activator:CustomActivator"

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

Install your plugin alongside virtualenv:

$ pip install virtualenv-custom-plugin

Or in development mode:

$ pip install -e /path/to/virtualenv-custom-plugin

Test your plugin by creating a virtual environment:

$ virtualenv --discovery=custom --creator=custom-posix --seeder=custom --activators=custom test-env