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

status-detection.mddocs/

Status Detection

PyPI API integration for checking package upgrade availability, supporting both JSON API and simple HTML formats, with custom index URL handling.

Capabilities

Package Status Detection

Queries PyPI or custom package indexes to detect available upgrades for packages from requirements files.

class PackagesStatusDetector:
    def __init__(self, packages, use_default_index=False):
        """
        Initialize package status detector.
        
        Args:
            packages (list): List of package specification strings from requirements files
            use_default_index (bool): If True, skip custom index URL detection and use PyPI
        """
    
    def detect_available_upgrades(self, options):
        """
        Detect available upgrades for all packages.
        
        Args:
            options (dict): Options dictionary containing:
                --prerelease (bool): Include prerelease versions  
                -p (list): List of explicitly selected packages or ['all']
                
        Returns:
            dict: Package status map with package names as keys and status dicts as values
        """

Custom Index URL Configuration

Automatically detects custom PyPI index URLs from pip configuration files and environment variables.

def _update_index_url_from_configs(self):
    """
    Check for alternative index-url in pip configuration files.
    
    Priority order:
    1. PIP_INDEX_URL environment variable
    2. pip.conf/pip.ini files in various locations:
       - ~/.pip/pip.conf, ~/.pip/pip.ini  
       - ~/.config/pip/pip.conf, ~/.config/pip/pip.ini
       - Virtual environment pip.conf, pip.ini
       - System-wide pip configuration files
       
    Updates self.PYPI_API_URL and self.PYPI_API_TYPE accordingly.
    """

def _prepare_api_url(self, index_url):
    """
    Prepare API URL based on index URL format.
    
    Args:
        index_url (str): Base index URL from configuration
        
    Returns:
        str: API URL template for package queries
        
    Supported formats:
    - Standard PyPI JSON API: /pypi/{package}/json
    - Simple HTML API: /simple/{package}/  
    - Custom formats with /pypi/ in path
    """

Package Information Fetching

Fetches package information from PyPI or custom indexes with timeout and error handling.

def _fetch_index_package_info(self, package_name, current_version):
    """
    Fetch package information from the configured index.
    
    Args:
        package_name (str): Name of the package to query
        current_version (packaging.version.Version): Current version from requirements
        
    Returns:
        tuple: (package_status_dict, reason) where:
            package_status_dict (dict or False): Package status information or False on error
            reason (str): Success message or error description
            
    Network configuration:
    - 15 second timeout for HTTP requests
    - Handles HTTPError and connection issues
    - Uses canonical package names for simple HTML API
    """

Package Line Parsing

Parses package specification lines to extract package names and versions.

def _expand_package(self, package_line):
    """
    Extract package name and version from requirements line.
    
    Args:
        package_line (str): Package specification (e.g., "django==3.2.0")
        
    Returns:
        tuple: (package_name, version) or (None, None) if not parseable
        
    Supports:
    - Exact version pins: package==1.0.0
    - Package extras: package[extra1,extra2]==1.0.0
    - Filters out packages without == specifications
    """

API Format Support

PyPI JSON API

Default format using PyPI's JSON API endpoint.

def _parse_pypi_json_package_info(self, package_name, current_version, response):
    """
    Parse PyPI JSON API response for package information.
    
    Args:
        package_name (str): Package name
        current_version (packaging.version.Version): Current version
        response (requests.Response): HTTP response from PyPI JSON API
        
    Returns:
        tuple: (package_status_dict, reason)
        
    JSON API features:
    - Full version history in releases dict
    - Upload timestamps for each release
    - Package metadata and description
    - Handles prerelease and postrelease versions
    """

Simple HTML API

Fallback format for custom package indexes that provide simple HTML listing.

def _parse_simple_html_package_info(self, package_name, current_version, response):
    """
    Parse simple HTML API response for package information.
    
    Args:
        package_name (str): Package name  
        current_version (packaging.version.Version): Current version
        response (requests.Response): HTTP response from simple HTML API
        
    Returns:
        tuple: (package_status_dict, reason)
        
    HTML API features:
    - Regex parsing of package links
    - Version extraction from filenames
    - No upload timestamp information (returns '-')
    - Handles prerelease versions through version parsing
    """

Configuration Sources

Environment Variables

export PIP_INDEX_URL="https://pypi.company.com/simple/"

Takes highest priority over configuration files.

Pip Configuration Files

Searched in order of priority:

~/.pip/pip.conf          # User-specific (Unix)
~/.pip/pip.ini           # User-specific (Windows)  
~/.config/pip/pip.conf   # XDG config (Unix)
~/.config/pip/pip.ini    # XDG config (Windows)
$VIRTUAL_ENV/pip.conf    # Virtual environment
$VIRTUAL_ENV/pip.ini     # Virtual environment  
[system locations]       # System-wide configurations

Configuration format:

[global]
index-url = https://pypi.company.com/simple/

Usage Examples

Basic Status Detection

from pip_upgrader.packages_status_detector import PackagesStatusDetector
from packaging import version

# Initialize with packages from requirements files  
packages = ['django==3.2.0', 'requests==2.25.1']
detector = PackagesStatusDetector(packages)

# Check for upgrades
options = {'--prerelease': False, '-p': []}
status_map = detector.detect_available_upgrades(options)

# Example output
print(status_map)
{
    'django': {
        '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'
    },
    'requests': {
        'name': 'requests', 
        'current_version': Version('2.25.1'),
        'latest_version': Version('2.25.1'),
        'upgrade_available': False,
        'upload_time': '2021-01-05 14:22:18'
    }
}

Custom Index Usage

# Use custom index from environment or config
detector = PackagesStatusDetector(packages, use_default_index=False)

# Force default PyPI index
detector = PackagesStatusDetector(packages, use_default_index=True)

Prerelease Handling

# Include prerelease versions
options = {'--prerelease': True, '-p': []}
status_map = detector.detect_available_upgrades(options)

# This might return version 4.2.0rc1 instead of 4.1.0 for django

Explicit Package Selection

# Only check specific packages
options = {'--prerelease': False, '-p': ['django', 'flask']}
status_map = detector.detect_available_upgrades(options)

# Check all packages
options = {'--prerelease': False, '-p': ['all']}
status_map = detector.detect_available_upgrades(options)

Package Status Dictionary

Each package in the returned status map contains:

{
    'name': str,                    # Package name as specified in requirements
    'current_version': Version,     # Current version from requirements file
    'latest_version': Version,      # Latest available version from index
    'upgrade_available': bool,      # True if latest > current
    'upload_time': str             # Upload timestamp (JSON API) or '-' (HTML API)
}

Version Handling

Version Comparison

Uses the packaging library for robust version comparison:

from packaging import version

current = version.parse('1.0.0')
latest = version.parse('1.1.0')
upgrade_available = current < latest  # True

Prerelease Logic

  • Default behavior: Excludes prerelease and postrelease versions
  • Prerelease flag: Includes all prerelease/postrelease versions
  • Current version logic: If current version is prerelease/postrelease, includes prereleases even without flag

Version Parsing Edge Cases

  • Non-RFC versions: Falls back to version from package info when release parsing fails
  • Missing releases: Returns error when no valid versions found
  • Complex version schemes: Handles PEP 440 compliant versions through packaging library

Error Handling

Network Errors

  • HTTP timeouts: 15-second timeout with graceful failure
  • HTTP errors: Catches HTTPError exceptions and returns error message
  • Connection issues: Network connectivity problems are handled with error messages

Parsing Errors

  • Invalid JSON: JSON parsing errors result in error status
  • Missing data: Missing fields in API responses are handled gracefully
  • Version parsing: Invalid version strings are caught and reported

API Compatibility

  • Index format detection: Automatically determines JSON vs HTML API format
  • Canonical names: Uses packaging.utils.canonicalize_name for HTML APIs
  • Fallback handling: Graceful degradation when API format is unsupported

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