CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-hatch

Modern, extensible Python project management tool with comprehensive environment and build system support

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Extensible plugin architecture supporting environment types, publishers, project templates, and environment collectors. Provides hook specifications and plugin manager for registration and discovery.

Capabilities

Plugin Manager

Central manager for plugin discovery, registration, and lifecycle management across all plugin types.

class PluginManager:
    """
    Plugin manager for discovering and managing hatch plugins.
    
    Handles registration and discovery of environment, publisher, template,
    and environment collector plugins through the hook system.
    """
    
    def __init__(self):
        """Initialize plugin manager."""
    
    def initialize(self) -> None:
        """
        Initialize plugin system and discover available plugins.
        
        This method must be called before using any plugin functionality.
        """
    
    def hatch_register_environment(self) -> dict[str, type]:
        """
        Get registered environment plugins.
        
        Returns:
            Dict mapping plugin names to environment plugin classes
        """
    
    def hatch_register_publisher(self) -> dict[str, type]:
        """
        Get registered publisher plugins.
        
        Returns:
            Dict mapping plugin names to publisher plugin classes
        """
    
    def hatch_register_template(self) -> dict[str, type]:
        """
        Get registered template plugins.
        
        Returns:
            Dict mapping plugin names to template plugin classes
        """
    
    def hatch_register_environment_collector(self) -> dict[str, type]:
        """
        Get registered environment collector plugins.
        
        Returns:
            Dict mapping plugin names to collector plugin classes
        """
    
    def list_name_plugin(self) -> list[tuple[str, str]]:
        """
        List all registered plugins with their types.
        
        Returns:
            List of (plugin_name, plugin_type) tuples
        """
    
    def get_plugin(self, plugin_type: str, plugin_name: str) -> type | None:
        """
        Get specific plugin class by type and name.
        
        Args:
            plugin_type (str): Type of plugin ('environment', 'publisher', etc.)
            plugin_name (str): Name of the plugin
            
        Returns:
            Plugin class or None if not found
        """

Hook Specifications

Hook specifications define the plugin registration interface that plugins must implement to be discovered by hatch.

def hatch_register_environment():
    """
    Hook for registering environment plugins.
    
    Environment plugins must implement the EnvironmentInterface and provide
    isolated execution environments for project operations.
    
    Returns:
        Dict mapping environment type names to plugin classes
    """

def hatch_register_environment_collector():
    """
    Hook for registering environment collector plugins.
    
    Environment collectors discover and provide environment configurations
    from various sources like Docker Compose, tox.ini, etc.
    
    Returns:
        Dict mapping collector names to collector classes
    """

def hatch_register_publisher():
    """
    Hook for registering publisher plugins.
    
    Publisher plugins handle uploading packages to various indices
    and repositories with authentication and metadata support.
    
    Returns:
        Dict mapping publisher names to publisher classes
    """

def hatch_register_template():
    """
    Hook for registering template plugins.
    
    Template plugins provide project scaffolding and initialization
    templates for creating new projects with predefined structures.
    
    Returns:
        Dict mapping template names to template classes
    """

def hatch_register_version_scheme():
    """
    Hook for registering version scheme plugins.
    
    Version scheme plugins handle version parsing, comparison,
    and manipulation for different versioning systems.
    
    Returns:
        Dict mapping scheme names to version scheme classes
    """

Environment Plugin Interface

Base interface that all environment plugins must implement to provide environment management capabilities.

class EnvironmentInterface:
    """
    Base interface for environment plugins.
    
    Environment plugins provide isolated execution environments for
    project operations including dependency management and command execution.
    """
    
    PLUGIN_NAME: str = ''  # Must be overridden by implementations
    
    def __init__(self, root, metadata, name, config, matrix_variables, data_directory, platform, verbosity, app):
        """
        Initialize environment plugin.
        
        Args:
            root: Project root directory
            metadata: Project metadata
            name (str): Environment name
            config (dict): Environment configuration
            matrix_variables (dict): Matrix variables for this environment
            data_directory: Data directory for environment storage
            platform: Platform utilities
            verbosity (int): Verbosity level
            app: Application instance
        """
    
    # Core abstract methods that must be implemented
    def exists(self) -> bool:
        """Check if environment exists."""
    
    def create(self) -> None:
        """Create the environment."""
    
    def remove(self) -> None:
        """Remove the environment."""
    
    def install_project(self) -> None:
        """Install project in the environment."""
    
    def install_project_dev_mode(self) -> None:
        """Install project in development mode."""
    
    def dependencies_in_sync(self) -> bool:
        """Check if dependencies are synchronized."""
    
    def sync_dependencies(self) -> None:
        """Synchronize dependencies with configuration."""

Publisher Plugin Interface

Base interface that all publisher plugins must implement to handle package publishing to various repositories.

class PublisherInterface:
    """
    Base interface for publisher plugins.
    
    Publisher plugins handle uploading built packages to package repositories
    with authentication, metadata validation, and upload progress tracking.
    """
    
    PLUGIN_NAME: str = ''  # Must be overridden by implementations
    
    def __init__(self, app, root, cache_dir, project_config, plugin_config):
        """
        Initialize publisher plugin.
        
        Args:
            app: Application instance
            root: Project root directory
            cache_dir: Cache directory for temporary files
            project_config (dict): Project publishing configuration
            plugin_config (dict): Plugin-specific configuration
        """
    
    @property
    def app(self):
        """Application instance."""
    
    @property
    def root(self):
        """Project root directory."""
    
    @property
    def project_config(self) -> dict:
        """Project publishing configuration."""
    
    @property
    def plugin_config(self) -> dict:
        """Plugin-specific configuration."""
    
    @property
    def disable(self) -> bool:
        """Whether this publisher is disabled."""
    
    def publish(self, artifacts: list[str], options: dict) -> None:
        """
        Publish artifacts to repository.
        
        Args:
            artifacts (list[str]): List of artifact file paths to publish
            options (dict): Publishing options and metadata
        """

Template Plugin Interface

Base interface that all template plugins must implement to provide project scaffolding capabilities.

class TemplateInterface:
    """
    Base interface for template plugins.
    
    Template plugins provide project initialization and scaffolding
    capabilities for creating new projects with predefined structures.
    """
    
    PLUGIN_NAME: str = ''  # Must be overridden by implementations
    PRIORITY: int = 100   # Plugin priority for ordering
    
    def __init__(self, plugin_config: dict, cache_dir, creation_time):
        """
        Initialize template plugin.
        
        Args:
            plugin_config (dict): Plugin configuration
            cache_dir: Cache directory for template files
            creation_time: Template creation timestamp
        """
    
    def initialize_config(self, config: dict) -> None:
        """
        Initialize template configuration with user input.
        
        Args:
            config (dict): Configuration dictionary to populate
        """
    
    def get_files(self, config: dict) -> list:
        """
        Get list of template files to create.
        
        Args:
            config (dict): Template configuration
            
        Returns:
            List of File objects representing template files
        """
    
    def finalize_files(self, config: dict, files: list) -> None:
        """
        Finalize template files after creation.
        
        Args:
            config (dict): Template configuration
            files (list): List of created File objects
        """

Environment Collector Interface

Base interface for environment collector plugins that discover environment configurations from external sources.

class EnvironmentCollectorInterface:
    """
    Base interface for environment collector plugins.
    
    Environment collectors discover and provide environment configurations
    from external sources like Docker Compose files, tox.ini, etc.
    """
    
    PLUGIN_NAME: str = ''  # Must be overridden by implementations
    
    def __init__(self, root, config):
        """
        Initialize environment collector plugin.
        
        Args:
            root: Project root directory
            config (dict): Collector configuration
        """
    
    @property
    def root(self):
        """Project root directory."""
    
    @property
    def config(self) -> dict:
        """Collector configuration."""
    
    def get_initial_config(self) -> dict[str, dict]:
        """
        Get initial environment configurations.
        
        Returns:
            Dict mapping environment names to configurations
        """
    
    def finalize_config(self, config: dict[str, dict]) -> None:
        """
        Finalize environment configurations.
        
        Args:
            config (dict): Environment configurations to finalize
        """
    
    def finalize_environments(self, config: dict[str, dict]) -> None:
        """
        Finalize environment setup after configuration.
        
        Args:
            config (dict): Final environment configurations
        """

Plugin Registration

Utilities for registering and discovering plugins both from entry points and direct registration.

def register_plugin(plugin_type: str, plugin_name: str, plugin_class: type) -> None:
    """
    Register plugin programmatically.
    
    Args:
        plugin_type (str): Type of plugin ('environment', 'publisher', etc.)
        plugin_name (str): Unique name for the plugin
        plugin_class (type): Plugin implementation class
    """

def discover_plugins() -> dict[str, dict[str, type]]:
    """
    Discover plugins from entry points and registered plugins.
    
    Returns:
        Dict mapping plugin types to dicts of plugin name -> class mappings
    """

def validate_plugin(plugin_class: type, plugin_type: str) -> list[str]:
    """
    Validate plugin implementation against interface requirements.
    
    Args:
        plugin_class (type): Plugin class to validate
        plugin_type (str): Expected plugin type
        
    Returns:
        List of validation errors (empty if valid)
    """

def load_plugin_config(plugin_name: str, config_section: dict) -> dict:
    """
    Load and validate plugin configuration.
    
    Args:
        plugin_name (str): Name of the plugin
        config_section (dict): Configuration section for plugin
        
    Returns:
        Validated plugin configuration
    """

Usage Examples

Implementing an Environment Plugin

from hatch.env.plugin.interface import EnvironmentInterface

class DockerEnvironment(EnvironmentInterface):
    """Docker-based environment plugin."""
    
    PLUGIN_NAME = 'docker'
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.container_name = f'hatch-{self.name}-{self.root.name}'
    
    def exists(self) -> bool:
        """Check if Docker container exists."""
        result = self.platform.run_command(
            ['docker', 'inspect', self.container_name],
            capture_output=True
        )
        return result.returncode == 0
    
    def create(self) -> None:
        """Create Docker container environment."""
        # Get Docker image from config
        image = self.config.get('image', 'python:3.11')
        
        # Create container
        self.platform.run_command([
            'docker', 'create',
            '--name', self.container_name,
            '--volume', f'{self.root}:/workspace',
            '--workdir', '/workspace',
            image,
            'sleep', 'infinity'
        ])
        
        # Start container
        self.platform.run_command([
            'docker', 'start', self.container_name
        ])
        
        # Install project dependencies
        self.sync_dependencies()
    
    def remove(self) -> None:
        """Remove Docker container."""
        self.platform.run_command([
            'docker', 'rm', '-f', self.container_name
        ])
    
    def sync_dependencies(self) -> None:
        """Install dependencies in container."""
        if self.dependencies:
            pip_cmd = ['pip', 'install'] + self.dependencies
            self.run_command(pip_cmd)
    
    def run_command(self, command: list[str], **kwargs):
        """Execute command in Docker container."""
        docker_cmd = [
            'docker', 'exec',
            '-w', '/workspace',
            self.container_name
        ] + command
        
        return self.platform.run_command(docker_cmd, **kwargs)

# Register the plugin
def hatch_register_environment():
    return {'docker': DockerEnvironment}

Implementing a Publisher Plugin

from hatch.publish.plugin.interface import PublisherInterface
import httpx

class CustomIndexPublisher(PublisherInterface):
    """Publisher for custom package index."""
    
    PLUGIN_NAME = 'custom-index'
    
    def publish(self, artifacts: list[str], options: dict) -> None:
        """Publish packages to custom index."""
        # Get index URL from config
        index_url = self.plugin_config.get('url', 'https://pypi.example.com')
        username = self.plugin_config.get('username')
        password = self.plugin_config.get('password')
        
        # Create HTTP client
        client = httpx.Client()
        
        for artifact_path in artifacts:
            self.app.display_info(f'Uploading {artifact_path}...')
            
            # Prepare upload data
            with open(artifact_path, 'rb') as f:
                files = {'file': f}
                data = {
                    'name': self.project_config.get('name'),
                    'version': self.project_config.get('version')
                }
                
                # Upload artifact
                response = client.post(
                    f'{index_url}/upload',
                    files=files,
                    data=data,
                    auth=(username, password) if username else None
                )
                
                if response.status_code != 200:
                    self.app.abort(f'Upload failed: {response.text}')
                    
            self.app.display_success(f'Successfully uploaded {artifact_path}')

# Register the plugin
def hatch_register_publisher():
    return {'custom-index': CustomIndexPublisher}

Implementing a Template Plugin

from hatch.template.plugin.interface import TemplateInterface
from hatch.template import File

class FastAPITemplate(TemplateInterface):
    """FastAPI project template."""
    
    PLUGIN_NAME = 'fastapi'
    PRIORITY = 50
    
    def initialize_config(self, config: dict) -> None:
        """Initialize template configuration."""
        config.setdefault('package_name', config['project_name'].replace('-', '_'))
        config.setdefault('use_database', False)
        config.setdefault('use_auth', False)
    
    def get_files(self, config: dict) -> list:
        """Get template files to create."""
        files = []
        package_name = config['package_name']
        
        # Main application file
        files.append(File(
            f'{package_name}/main.py',
            '''from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}
'''
        ))
        
        # Requirements file
        requirements = ['fastapi>=0.68.0', 'uvicorn[standard]>=0.15.0']
        if config['use_database']:
            requirements.append('sqlalchemy>=1.4.0')
        if config['use_auth']:
            requirements.append('python-jose[cryptography]>=3.3.0')
            
        files.append(File(
            'requirements.txt',
            '\n'.join(requirements) + '\n'
        ))
        
        # pyproject.toml
        files.append(File(
            'pyproject.toml',
            f'''[project]
name = "{config['project_name']}"
version = "0.1.0"
description = "FastAPI application"
dependencies = {requirements}

[project.scripts]
dev = "uvicorn {package_name}.main:app --reload"
'''
        ))
        
        return files
    
    def finalize_files(self, config: dict, files: list) -> None:
        """Finalize template after file creation."""
        # Could add post-processing here
        pass

# Register the plugin
def hatch_register_template():
    return {'fastapi': FastAPITemplate}

Using the Plugin System

from hatch.plugin.manager import PluginManager

# Initialize plugin manager
plugins = PluginManager()
plugins.initialize()

# Discover available plugins
env_plugins = plugins.hatch_register_environment()
publisher_plugins = plugins.hatch_register_publisher()
template_plugins = plugins.hatch_register_template()

print(f"Environment plugins: {list(env_plugins.keys())}")
print(f"Publisher plugins: {list(publisher_plugins.keys())}")
print(f"Template plugins: {list(template_plugins.keys())}")

# Get specific plugin
docker_plugin = plugins.get_plugin('environment', 'docker')
if docker_plugin:
    print(f"Found Docker plugin: {docker_plugin.PLUGIN_NAME}")

# List all plugins
all_plugins = plugins.list_name_plugin()
for name, plugin_type in all_plugins:
    print(f"{plugin_type}: {name}")

Install with Tessl CLI

npx tessl i tessl/pypi-hatch

docs

application.md

cli-commands.md

configuration.md

environment-management.md

index.md

plugin-system.md

project-management.md

python-management.md

tile.json