CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pip-upgrader

An interactive pip requirements upgrader that also updates the version in your requirements.txt file

Pending
Overview
Eval results
Files

package-upgrading.mddocs/

Package Upgrading

Package installation via pip and atomic updating of version numbers in requirements files, with dry-run support and error handling.

Capabilities

Package Installation and Requirements Updating

Executes the final upgrade process by installing packages via pip and updating version numbers in requirements files.

class PackagesUpgrader:
    def __init__(self, selected_packages, requirements_files, options):
        """
        Initialize package upgrader.
        
        Args:
            selected_packages (list): List of package dictionaries from interactive selector
            requirements_files (list): List of requirements file paths to update
            options (dict): Command-line options including:
                --dry-run (bool): Simulate without actual changes
                --skip-package-installation (bool): Only update files, don't install
        """
    
    def do_upgrade(self):
        """
        Execute upgrade process for all selected packages.
        
        Returns:
            list: List of successfully upgraded package dictionaries
            
        Process for each package:
        1. Install package via pip (unless skipped)
        2. Update version in all requirements files
        3. Handle installation failures gracefully
        """

Individual Package Processing

Handles the upgrade process for a single package with proper error handling.

def _update_package(self, package):
    """
    Update (install) a single package and update requirements files.
    
    Args:
        package (dict): Package dictionary with name, current_version, latest_version
        
    Process:
    1. Install exact version via pip (unless dry-run or skip-installation)
    2. Update requirements files only if installation succeeds
    3. Handle CalledProcessError from pip installation
    """

Requirements File Updating

Atomically updates version numbers in requirements files with rollback on failure.

def _update_requirements_package(self, package):
    """
    Update package version in all requirements files.
    
    Args:
        package (dict): Package dictionary with version information
        
    Atomic operation:
    1. Read all lines from each requirements file
    2. Update matching package lines
    3. Write updated content to file
    4. Rollback original content on any exception
    """

def _maybe_update_line_package(self, line, package):
    """
    Update package version in a single requirements line if it matches.
    
    Args:
        line (str): Single line from requirements file
        package (dict): Package dictionary with version information
        
    Returns:
        str: Updated line or original line if no match
        
    Matching criteria:
    - Package name matches (case-insensitive regex)
    - Uses exact version pinning (==)
    - Handles package extras [extra1,extra2]
    - Preserves line formatting and comments
    """

Installation Process

Pip Installation

Packages are installed using exact version pinning:

pip install django==4.1.0
pip install requests[security]==2.28.1

Installation modes:

  1. Normal mode: Installs packages via subprocess.check_call
  2. Dry-run mode: Skips installation, prints simulation message
  3. Skip-installation mode: Only updates requirements files

Subprocess Execution

import subprocess

# Install exact version
pinned = f"{package['name']}=={package['latest_version']}"
subprocess.check_call(['pip', 'install', pinned])

Error handling:

  • CalledProcessError: Installation failure, skips requirements update
  • Continues processing other packages on individual failures

Requirements File Updates

Line Matching

Uses regex pattern matching to identify package lines:

import re

pattern = r'\b{package}(?:\[\w*\])?=={old_version}\b'.format(
    package=re.escape(package['name']),
    old_version=re.escape(str(package['current_version']))
)

Pattern features:

  • Word boundaries prevent partial matches
  • Optional extras support: package[extra]==version
  • Exact version matching only
  • Case-insensitive matching

Atomic File Updates

Requirements files are updated atomically:

  1. Read: Load all lines into memory
  2. Process: Update matching lines
  3. Write: Write all lines to file
  4. Rollback: Restore original content on any exception
# Atomic update process
lines = []
with open(filename, 'r') as frh:
    for line in frh:
        lines.append(line)

try:
    with open(filename, 'w') as fwh:
        for line in lines:
            updated_line = self._maybe_update_line_package(line, package)
            fwh.write(updated_line)
except Exception as e:
    # Rollback on any error
    with open(filename, 'w') as fwh:
        for line in lines:
            fwh.write(line)
    raise e

Usage Examples

Basic Package Upgrading

from pip_upgrader.packages_upgrader import PackagesUpgrader

# Selected packages from interactive selector
selected_packages = [
    {
        'name': 'django',
        'current_version': Version('3.2.0'),
        'latest_version': Version('4.1.0'),
        'upgrade_available': True,
        'upload_time': '2022-08-03 08:15:30'
    }
]

# Requirements files to update
requirements_files = ['requirements.txt', 'requirements/dev.txt']

# Options
options = {
    '--dry-run': False,
    '--skip-package-installation': False
}

# Execute upgrades
upgrader = PackagesUpgrader(selected_packages, requirements_files, options)
upgraded = upgrader.do_upgrade()

print(f"Successfully upgraded {len(upgraded)} packages")

Dry Run Mode

# Simulate upgrades without making changes
options = {
    '--dry-run': True,
    '--skip-package-installation': False
}

upgrader = PackagesUpgrader(selected_packages, requirements_files, options)
upgraded = upgrader.do_upgrade()

# Prints simulation messages, no actual changes made

Skip Package Installation

# Only update requirements files, don't install packages
options = {
    '--dry-run': False,
    '--skip-package-installation': True
}

upgrader = PackagesUpgrader(selected_packages, requirements_files, options)
upgraded = upgrader.do_upgrade()

# Files updated, but pip install not executed

Requirements File Format Support

Standard Format

django==3.2.0
requests==2.25.1
flask==2.0.0

Updated to:

django==4.1.0
requests==2.28.1  
flask==2.2.2

Package Extras

django[rest]==3.2.0
requests[security,socks]==2.25.1

Updated to:

django[rest]==4.1.0
requests[security,socks]==2.28.1

Mixed Requirements

# Web framework
django==3.2.0  # Core framework
django-rest-framework==3.12.0

# HTTP client
requests==2.25.1
urllib3>=1.26.0  # Not updated (not exact pin)

# Development
pytest==6.2.4

Only exact pins (==) are updated. Other version specifiers (>=, ~=, etc.) are left unchanged.

Error Handling

Installation Failures

When pip install fails:

try:
    subprocess.check_call(['pip', 'install', pinned])
    self._update_requirements_package(package)
except CalledProcessError:
    print(Color('{{autored}}Failed to install package "{}"{{/autored}}'.format(package['name'])))
    # Continue with next package

Behavior:

  • Prints colored error message
  • Skips requirements file update for failed package
  • Continues processing remaining packages
  • Does not add failed package to upgraded list

File Update Failures

Requirements file update failures trigger rollback:

try:
    # Write updated lines
    pass
except Exception as e:
    # Restore original file content
    with open(filename, 'w') as fwh:
        for line in lines:
            fwh.write(line)
    raise e

Common failure scenarios:

  • File permission errors
  • Disk space issues
  • File locking by other processes

Environment Variable Override

The PIP_UPGRADER_SKIP_PACKAGE_INSTALLATION environment variable can override the skip installation option:

export PIP_UPGRADER_SKIP_PACKAGE_INSTALLATION=1
pip-upgrade requirements.txt

This forces skip-installation mode regardless of command-line options.

Dry Run Output

In dry-run mode, the upgrader provides detailed simulation output:

Package Installation Simulation

[Dry Run]: skipping package installation: django
[Dry Run]: skipping package installation: requests

Requirements Update Simulation

[Dry Run]: skipping requirements replacement: django==3.2.0  /  django==4.1.0
[Dry Run]: skipping requirements replacement: requests==2.25.1  /  requests==2.28.1

Features:

  • Shows original and updated versions
  • Indicates which operations are being skipped
  • Maintains upgrade tracking for final summary

Return Value

The do_upgrade() method returns a list of successfully upgraded packages:

[
    {
        'name': 'django',
        'current_version': Version('3.2.0'),
        'latest_version': Version('4.1.0'),
        'upgrade_available': True,
        'upload_time': '2022-08-03 08:15:30'
    }
]

Inclusion criteria:

  • Package was found and updated in at least one requirements file
  • Installation succeeded (if not skipped)
  • No file update errors occurred

Exclusion criteria:

  • Package installation failed (CalledProcessError)
  • Package not found in any requirements file
  • File update exceptions occurred

Install with Tessl CLI

npx tessl i tessl/pypi-pip-upgrader

docs

cli-interface.md

environment-validation.md

index.md

interactive-selection.md

package-parsing.md

package-upgrading.md

requirements-detection.md

status-detection.md

tile.json