An interactive pip requirements upgrader that also updates the version in your requirements.txt file
—
Package installation via pip and atomic updating of version numbers in requirements files, with dry-run support and error handling.
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
"""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
"""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
"""Packages are installed using exact version pinning:
pip install django==4.1.0
pip install requests[security]==2.28.1Installation modes:
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 updateUses 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:
package[extra]==versionRequirements files are updated atomically:
# 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 efrom 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")# 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# 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 executeddjango==3.2.0
requests==2.25.1
flask==2.0.0Updated to:
django==4.1.0
requests==2.28.1
flask==2.2.2django[rest]==3.2.0
requests[security,socks]==2.25.1Updated to:
django[rest]==4.1.0
requests[security,socks]==2.28.1# 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.4Only exact pins (==) are updated. Other version specifiers (>=, ~=, etc.) are left unchanged.
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 packageBehavior:
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 eCommon failure scenarios:
The PIP_UPGRADER_SKIP_PACKAGE_INSTALLATION environment variable can override the skip installation option:
export PIP_UPGRADER_SKIP_PACKAGE_INSTALLATION=1
pip-upgrade requirements.txtThis forces skip-installation mode regardless of command-line options.
In dry-run mode, the upgrader provides detailed simulation output:
[Dry Run]: skipping package installation: django
[Dry Run]: skipping package installation: requests[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.1Features:
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:
Exclusion criteria:
Install with Tessl CLI
npx tessl i tessl/pypi-pip-upgrader