CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pdm

A modern Python package and dependency manager supporting the latest PEP standards

Pending
Overview
Eval results
Files

cli-framework.mddocs/

CLI Framework

Extensible command-line interface supporting custom commands, argument parsing, plugin integration, and comprehensive action implementations for PDM's command system.

Capabilities

Base Command System

Foundation classes for implementing PDM CLI commands with consistent argument parsing and execution patterns.

from argparse import ArgumentParser, Namespace, _SubParsersAction
from typing import Any, Sequence

class BaseCommand:
    """
    Base class for all CLI commands providing consistent interface
    and integration with PDM's command system.
    """
    
    # Class attributes
    name: str | None = None
    description: str | None = None
    arguments: Sequence[Option] = (verbose_option, global_option, project_option)
    
    @classmethod
    def register_to(cls, subparsers: _SubParsersAction, name: str | None = None, **kwargs: Any) -> None:
        """
        Register command to argument parser subcommands.
        
        Args:
            subparsers: Subparser for command registration
            name: Optional command name override
            **kwargs: Additional arguments for add_parser
        """
    
    def add_arguments(self, parser: ArgumentParser) -> None:
        """
        Add command-specific arguments to parser.
        
        Args:
            parser: Argument parser for this command
        """
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Execute command with parsed options.
        
        Args:
            project: PDM project instance
            options: Parsed command line options
            
        Raises:
            PdmUsageError: Command usage errors
            PdmException: Command execution errors
        """

Core CLI Commands

Comprehensive set of built-in commands covering all PDM functionality.

Dependency Management Commands

class AddCommand(BaseCommand):
    """Add dependencies to project"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Add dependencies with version constraints and groups.
        
        Options:
        - packages: Package specifications to add
        - group: Dependency group name
        - dev: Add to development dependencies
        - editable: Install in editable mode
        - no-sync: Don't sync after adding
        """

class RemoveCommand(BaseCommand):
    """Remove dependencies from project"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Remove specified packages from project.
        
        Options:
        - packages: Package names to remove
        - group: Dependency group name
        - dev: Remove from development dependencies
        - no-sync: Don't sync after removing
        """

class UpdateCommand(BaseCommand):
    """Update project dependencies"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Update dependencies to latest compatible versions.
        
        Options:
        - packages: Specific packages to update (default: all)
        - group: Update specific dependency group
        - top: Only update top-level dependencies
        - dry-run: Show what would be updated
        """

Project Lifecycle Commands

class InitCommand(BaseCommand):
    """Initialize new PDM project"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Initialize project with pyproject.toml and configuration.
        
        Options:
        - name: Project name
        - version: Initial version
        - author: Author information
        - license: License specification
        - python: Python version requirement
        """

class InstallCommand(BaseCommand):
    """Install project dependencies"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Install all project dependencies from lockfile or requirements.
        
        Options:
        - group: Install specific dependency group
        - production: Skip development dependencies
        - dry-run: Show what would be installed
        - no-lock: Don't update lockfile
        """

class SyncCommand(BaseCommand):
    """Synchronize environment with lockfile"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Sync environment to exactly match lockfile specifications.
        
        Options:
        - group: Sync specific dependency group
        - production: Only production dependencies
        - clean: Remove packages not in lockfile
        - dry-run: Show sync operations
        """

Build and Publishing Commands

class BuildCommand(BaseCommand):
    """Build distribution packages"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Build wheel and/or source distributions.
        
        Options:
        - dest: Output directory for distributions
        - no-sdist: Skip source distribution
        - no-wheel: Skip wheel distribution
        - config-settings: Build configuration overrides
        """

class PublishCommand(BaseCommand):
    """Publish packages to repository"""
    
    def handle(self, project: Project, options: Namespace) -> None:
        """
        Upload distributions to package repository.
        
        Options:
        - repository: Repository URL or name
        - username: Authentication username
        - password: Authentication password
        - comment: Upload comment
        - sign: Sign uploads with GPG
        """

CLI Actions

Core implementation functions that handle the actual command logic.

def do_add(
    project: Project,
    requirements: list[str],
    group: str = "default",
    dev: bool = False,
    editable: bool = False,
    sync: bool = True,
    **kwargs
) -> dict:
    """
    Add dependencies to project.
    
    Args:
        project: Project instance
        requirements: List of requirement specifications
        group: Dependency group name
        dev: Add to development dependencies (deprecated)
        editable: Install packages in editable mode
        sync: Synchronize environment after adding
        
    Returns:
        Dictionary with operation results and statistics
    """

def do_install(
    project: Project,
    groups: list[str] | None = None,
    production: bool = False,
    check: bool = False,
    **kwargs
) -> None:
    """
    Install project dependencies.
    
    Args:
        project: Project instance
        groups: Specific dependency groups to install
        production: Skip development dependencies
        check: Verify installation without installing
    """

def do_lock(
    project: Project, 
    groups: list[str] | None = None,
    refresh: bool = False,
    **kwargs
) -> dict:
    """
    Generate project lockfile.
    
    Args:
        project: Project instance
        groups: Dependency groups to include
        refresh: Refresh all cached data
        
    Returns:
        Dictionary with lock operation results
    """

def do_sync(
    project: Project,
    groups: list[str] | None = None,
    production: bool = False,
    clean: bool = False,
    **kwargs  
) -> None:
    """
    Synchronize environment with lockfile.
    
    Args:
        project: Project instance
        groups: Dependency groups to sync
        production: Only production dependencies
        clean: Remove packages not in lockfile
    """

def do_update(
    project: Project,
    requirements: list[str] | None = None,
    groups: list[str] | None = None,
    top: bool = False,
    **kwargs
) -> dict:
    """
    Update project dependencies.
    
    Args:
        project: Project instance
        requirements: Specific packages to update
        groups: Dependency groups to update
        top: Only update top-level dependencies
        
    Returns:
        Dictionary with update results
    """

def do_remove(
    project: Project,
    requirements: list[str],
    group: str = "default", 
    dev: bool = False,
    sync: bool = True,
    **kwargs
) -> dict:
    """
    Remove dependencies from project.
    
    Args:
        project: Project instance
        requirements: Package names to remove
        group: Dependency group name
        dev: Remove from development dependencies (deprecated)
        sync: Synchronize environment after removal
        
    Returns:
        Dictionary with removal results
    """

Argument Parsing Utilities

Enhanced argument parsing with PDM-specific extensions and error handling.

class ArgumentParser:
    """
    Enhanced argument parser with PDM-specific functionality.
    
    Provides better error messages, command suggestions, and
    integration with PDM's help system.
    """
    
    def add_subparsers(self, **kwargs):
        """Add subparser for commands"""
        
    def parse_args(self, args: list[str] | None = None) -> Namespace:
        """Parse command line arguments with error handling"""

class ErrorArgumentParser(ArgumentParser):
    """
    Argument parser that raises PdmArgumentError instead of exiting.
    
    Used for testing and programmatic command invocation.
    """
    
    def error(self, message: str) -> None:
        """Raise PdmArgumentError instead of system exit"""

def format_similar_command(command: str, available: list[str]) -> str:
    """
    Format suggestion for similar commands when command not found.
    
    Args:
        command: Command that was not found
        available: List of available commands
        
    Returns:
        Formatted suggestion message
    """

Usage Examples

Creating Custom Commands

from pdm.cli.commands.base import BaseCommand
from pdm.core import Core
from argparse import ArgumentParser, Namespace

class MyCustomCommand(BaseCommand):
    """Custom command example"""
    
    @property
    def name(self) -> str:
        return "mycmd"
    
    @property  
    def description(self) -> str:
        return "My custom PDM command"
    
    def add_arguments(self, parser: ArgumentParser) -> None:
        parser.add_argument(
            "--my-option",
            help="Custom option for my command"
        )
        parser.add_argument(
            "target",
            help="Target for command operation"
        )
    
    def handle(self, project, options: Namespace) -> None:
        print(f"Executing custom command on {options.target}")
        if options.my_option:
            print(f"Option value: {options.my_option}")

# Register command via plugin
def my_plugin(core: Core) -> None:
    core.register_command(MyCustomCommand(), "mycmd")

Using CLI Actions Programmatically

from pdm.project import Project
from pdm.cli.actions import do_add, do_install, do_lock, do_sync

# Load project
project = Project()

# Add dependencies programmatically
result = do_add(
    project,
    requirements=["requests>=2.25.0", "click>=8.0"], 
    group="default",
    sync=False  # Don't sync yet
)
print(f"Added {len(result['added'])} packages")

# Add dev dependencies
do_add(
    project,
    requirements=["pytest", "black", "mypy"],
    group="dev", 
    sync=False
)

# Generate lockfile
lock_result = do_lock(project)
print(f"Locked {len(lock_result['candidates'])} packages")

# Install all dependencies
do_install(project, production=False)

# Or sync to exact lockfile state
do_sync(project, clean=True)

Command Option Handling

from pdm.cli.commands.base import BaseCommand
from argparse import ArgumentParser, Namespace

class ExampleCommand(BaseCommand):
    """Example showing comprehensive option handling"""
    
    def add_arguments(self, parser: ArgumentParser) -> None:
        # Boolean flags
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would be done without executing"
        )
        
        # String options with choices
        parser.add_argument(
            "--level",
            choices=["debug", "info", "warning", "error"],
            default="info",
            help="Logging level"
        )
        
        # Multiple values
        parser.add_argument(
            "--exclude",
            action="append",
            help="Packages to exclude (can be repeated)"
        )
        
        # Positional arguments
        parser.add_argument(
            "packages",
            nargs="*", 
            help="Package names to process"
        )
    
    def handle(self, project, options: Namespace) -> None:
        if options.dry_run:
            print("DRY RUN - no changes will be made")
            
        print(f"Log level: {options.level}")
        
        if options.exclude:
            print(f"Excluding: {', '.join(options.exclude)}")
            
        for package in options.packages:
            print(f"Processing package: {package}")

Install with Tessl CLI

npx tessl i tessl/pypi-pdm

docs

cli-framework.md

core-management.md

dependency-resolution.md

environment-management.md

index.md

installation-sync.md

project-management.md

tile.json