Modern, extensible Python project management tool with comprehensive environment and build system support
—
Environment plugin system for creating, managing, and executing commands in isolated Python environments. Supports virtual environments, Docker containers, and custom environment types through plugins.
Base interface for all environment plugins providing standardized operations for environment lifecycle management.
class EnvironmentInterface:
"""
Base interface for environment plugins.
All environment plugins must inherit from this class and implement
the abstract methods to provide environment management capabilities.
"""
PLUGIN_NAME: str = '' # Must be set by plugin implementations
def __init__(self, root, metadata, name, config, matrix_variables, data_directory, platform, verbosity, app):
"""
Initialize environment interface.
Args:
root: Project root directory
metadata: Project metadata
name (str): Environment name
config (dict): Environment configuration
matrix_variables (dict): Matrix variables for environment
data_directory: Directory for environment data
platform: Platform utilities
verbosity (int): Verbosity level
app: Application instance
"""
@property
def root(self):
"""Project root directory."""
@property
def metadata(self):
"""Project metadata."""
@property
def name(self) -> str:
"""Environment name."""
@property
def config(self) -> dict:
"""Environment configuration."""
@property
def matrix_variables(self) -> dict:
"""Matrix variables for this environment."""
@property
def data_directory(self):
"""Directory for environment data storage."""
@property
def platform(self):
"""Platform utilities for command execution."""
@property
def app(self):
"""Application instance."""
@property
def env_vars(self) -> dict:
"""
Environment variables for this environment.
Returns:
Dict of environment variables to set during execution
"""
@property
def dependencies(self) -> list:
"""
List of dependencies for this environment.
Returns:
List of dependency specifications
"""
@property
def features(self) -> list[str]:
"""
List of features enabled for this environment.
Returns:
List of feature names
"""
@property
def scripts(self) -> dict[str, str]:
"""
Scripts defined for this environment.
Returns:
Dict mapping script names to commands
"""
def exists(self) -> bool:
"""
Check if environment exists.
Returns:
True if environment exists and is ready for use
"""
def create(self) -> None:
"""
Create the environment.
This should create all necessary resources for the environment
including installing base dependencies.
"""
def remove(self) -> None:
"""
Remove the environment.
This should clean up all resources associated with the environment.
"""
def install_project(self) -> None:
"""
Install the project in the environment.
This should install the project package in the environment
without development mode.
"""
def install_project_dev_mode(self) -> None:
"""
Install the project in development mode.
This should install the project package in editable/development mode
so changes to source code are immediately reflected.
"""
def dependencies_in_sync(self) -> bool:
"""
Check if dependencies are synchronized with configuration.
Returns:
True if installed dependencies match configuration
"""
def sync_dependencies(self) -> None:
"""
Synchronize dependencies with configuration.
This should install, upgrade, or remove dependencies to match
the current environment configuration.
"""
def run_command(
self,
command: list[str],
*,
shell: bool = False,
env_vars: dict | None = None,
capture_output: bool = False
):
"""
Run command in the environment.
Args:
command (list[str]): Command and arguments to execute
shell (bool): Whether to use shell execution
env_vars (dict, optional): Additional environment variables
capture_output (bool): Whether to capture command output
Returns:
Command execution result
"""
def enter_shell(self, name: str, path: str, args: list[str]):
"""
Enter interactive shell in the environment.
Args:
name (str): Shell name
path (str): Shell executable path
args (list[str]): Shell arguments
"""
def build_environment(self, targets: list[str], env_vars: dict):
"""
Build artifacts in the environment.
Args:
targets (list[str]): Build targets
env_vars (dict): Environment variables for build
"""
def get_build_process(self, build_environment, **kwargs):
"""
Get build process for this environment.
Args:
build_environment: Build environment configuration
**kwargs: Additional build arguments
Returns:
Build process instance
"""Built-in virtual environment plugin providing standard Python virtual environment support.
class VirtualEnvironment(EnvironmentInterface):
"""
Virtual environment plugin using Python's venv module.
Provides isolated Python environments with dependency management
and project installation support.
"""
PLUGIN_NAME = 'virtual'
@property
def python_info(self) -> dict:
"""Information about Python installation in environment."""
@property
def python_path(self) -> str:
"""Path to Python executable in environment."""
@property
def site_packages_path(self) -> str:
"""Path to site-packages directory in environment."""
@property
def scripts_path(self) -> str:
"""Path to scripts/bin directory in environment."""
def activate_environment(self) -> dict[str, str]:
"""
Get environment variables for activating virtual environment.
Returns:
Dict of environment variables to set for activation
"""
def deactivate_environment(self) -> dict[str, str]:
"""
Get environment variables for deactivating virtual environment.
Returns:
Dict of environment variables to unset for deactivation
"""
def install_packages(self, packages: list[str], *, dev: bool = False) -> None:
"""
Install packages in the virtual environment.
Args:
packages (list[str]): Package specifications to install
dev (bool): Whether to install development dependencies
"""
def uninstall_packages(self, packages: list[str]) -> None:
"""
Uninstall packages from the virtual environment.
Args:
packages (list[str]): Package names to uninstall
"""
def list_packages(self) -> list[str]:
"""
List installed packages in the environment.
Returns:
List of installed package specifications
"""Configuration system for defining environments including dependencies, scripts, environment variables, and plugin settings.
class EnvironmentConfig:
"""Configuration for a single environment."""
@property
def type(self) -> str:
"""Environment type/plugin name."""
@property
def dependencies(self) -> list[str]:
"""List of dependencies for this environment."""
@property
def extra_dependencies(self) -> list[str]:
"""Extra dependencies beyond project dependencies."""
@property
def features(self) -> list[str]:
"""Project features to install."""
@property
def dev_mode(self) -> bool:
"""Whether to install project in development mode."""
@property
def skip_install(self) -> bool:
"""Whether to skip project installation."""
@property
def python(self) -> str:
"""Python version or path for this environment."""
@property
def matrix(self) -> dict:
"""Matrix variables for environment variants."""
@property
def env_vars(self) -> dict[str, str]:
"""Environment variables to set."""
@property
def env_include(self) -> list[str]:
"""Environment variables to include from host."""
@property
def env_exclude(self) -> list[str]:
"""Environment variables to exclude."""
@property
def scripts(self) -> dict[str, str]:
"""Scripts defined for this environment."""
@property
def pre_install_commands(self) -> list[str]:
"""Commands to run before installing dependencies."""
@property
def post_install_commands(self) -> list[str]:
"""Commands to run after installing dependencies."""
@property
def template(self) -> str:
"""Template environment to inherit from."""Utilities for discovering, validating, and collecting environment configurations from various sources.
def discover_environments(project) -> dict[str, EnvironmentConfig]:
"""
Discover environment configurations from project.
Args:
project: Project instance
Returns:
Dict mapping environment names to configurations
"""
def validate_environment_config(config: dict) -> list[str]:
"""
Validate environment configuration.
Args:
config (dict): Environment configuration to validate
Returns:
List of validation errors (empty if valid)
"""
def resolve_environment_dependencies(
base_dependencies: list[str],
env_config: EnvironmentConfig,
features: list[str]
) -> list[str]:
"""
Resolve final dependency list for environment.
Args:
base_dependencies (list[str]): Project base dependencies
env_config (EnvironmentConfig): Environment configuration
features (list[str]): Enabled features
Returns:
Final list of dependencies to install
"""
def expand_environment_matrix(
env_config: EnvironmentConfig
) -> list[tuple[str, dict]]:
"""
Expand environment matrix into individual configurations.
Args:
env_config (EnvironmentConfig): Environment with matrix
Returns:
List of (environment_name, matrix_variables) tuples
"""High-level operations for managing environments including creation, synchronization, cleanup, and status reporting.
def create_environment(app, env_name: str) -> None:
"""
Create named environment.
Args:
app: Application instance
env_name (str): Environment name to create
"""
def remove_environment(app, env_name: str) -> None:
"""
Remove named environment.
Args:
app: Application instance
env_name (str): Environment name to remove
"""
def sync_environment(app, env_name: str) -> None:
"""
Synchronize environment dependencies.
Args:
app: Application instance
env_name (str): Environment name to synchronize
"""
def prune_environments(app) -> list[str]:
"""
Remove unused environments.
Args:
app: Application instance
Returns:
List of removed environment names
"""
def show_environment_info(app, env_name: str) -> dict:
"""
Get environment information.
Args:
app: Application instance
env_name (str): Environment name
Returns:
Dict with environment information
"""
def find_environment_path(app, env_name: str) -> str | None:
"""
Find path to environment directory.
Args:
app: Application instance
env_name (str): Environment name
Returns:
Path to environment or None if not found
"""from hatch.cli.application import Application
from hatch.env.plugin.interface import EnvironmentInterface
app = Application(lambda code: exit(code))
# Get environment interface
env = app.get_environment("test")
# Create environment if it doesn't exist
if not env.exists():
print("Creating test environment...")
env.create()
# Install project in development mode
env.install_project_dev_mode()
# Synchronize dependencies
if not env.dependencies_in_sync():
print("Synchronizing dependencies...")
env.sync_dependencies()
# Run command in environment
result = env.run_command(
["python", "-m", "pytest", "tests/"],
capture_output=True
)
print(f"Test result: {result.returncode}")from hatch.env import discover_environments
# Discover environments from project
project = app.project
environments = discover_environments(project)
# Access environment configuration
test_env_config = environments.get("test")
if test_env_config:
print(f"Test dependencies: {test_env_config.dependencies}")
print(f"Test scripts: {test_env_config.scripts}")
print(f"Python version: {test_env_config.python}")
# Validate configuration
errors = validate_environment_config(test_env_config.__dict__)
if errors:
print(f"Configuration errors: {errors}")from hatch.env.plugin.interface import EnvironmentInterface
class DockerEnvironment(EnvironmentInterface):
"""Custom Docker-based environment plugin."""
PLUGIN_NAME = 'docker'
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
self.platform.run_command([
'docker', 'create',
'--name', self.container_name,
'--volume', f'{self.root}:/workspace',
self.config.get('image', 'python:3.11'),
'sleep', 'infinity'
])
# Start container
self.platform.run_command([
'docker', 'start', self.container_name
])
def remove(self) -> None:
# Remove Docker container
self.platform.run_command([
'docker', 'rm', '-f', self.container_name
])
@property
def container_name(self) -> str:
return f'hatch-{self.name}-{self.root.name}'
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)from hatch.env import expand_environment_matrix
# Environment configuration with matrix
config = EnvironmentConfig({
'matrix': {
'python': ['3.9', '3.10', '3.11'],
'django': ['3.2', '4.0', '4.1']
},
'dependencies': [
'django=={matrix:django}'
]
})
# Expand matrix into individual environments
environments = expand_environment_matrix(config)
for env_name, matrix_vars in environments:
print(f"Environment: {env_name}")
print(f"Variables: {matrix_vars}")
# Result: test-py3.9-django3.2, test-py3.10-django4.0, etc.Install with Tessl CLI
npx tessl i tessl/pypi-hatch