Skip to content

Instantly share code, notes, and snippets.

@mgaitan
Created November 2, 2023 13:08
Show Gist options
  • Save mgaitan/5bc6d834465e1ab68ed5880a27e4ace4 to your computer and use it in GitHub Desktop.
Save mgaitan/5bc6d834465e1ab68ed5880a27e4ace4 to your computer and use it in GitHub Desktop.
Checks the compatibility of Python packages listed in a requirements.txt file with a specified version of Python
"""
This script checks the compatibility of Python packages listed in a requirements.txt file with a
specified version of Python. It queries the PyPI API to fetch the latest versions of the packages,
their release dates, and the minimum required versions that are compatible with the given Python version.
The script can output the results in either Markdown format printed to stdout or CSV format written
to a specified file. It accepts a requirements.txt file generated via pip-compile that includes
package versions and hashes.
Usage:
python check-python-upgrade.py requirements.txt [-o OUTPUT_FILE] [-t TARGET_PYTHON_VERSION]
Arguments:
requirements_file (str): The path to the requirements.txt file to be processed.
Optional Arguments:
-o, --output (str): The output file path where the report will be saved. If a file extension
is provided, it determines the format (.csv for CSV output, any other for Markdown). If omitted,
the script prints the Markdown report to stdout.
-t, --target-python-version (str): The Python version to check package compatibility against (defaults to '3.12').
The report includes the following information for each package:
- Current installed version
- Latest available version
- Release date of the latest version
- Minimum version compatible with the target Python version
- A flag indicating if an update is required to be compatible with the target Python version
If the current package version is not compatible with the specified version of Python, the script marks it in the report, suggesting that an update is required.
Please note that an internet connection is required to fetch the latest package information from the PyPI API.
Example:
To check against Python 3.12 and output the report in CSV format to 'compatibility_report.csv':
python check-python-upgrade.py requirements.txt -o compatibility_report.csv
To print the report to stdout in Markdown format:
python check-python-upgrade.py requirements.txt
"""
import argparse
import csv
import os
from pkg_resources import parse_version
from packaging.specifiers import SpecifierSet
import requests
def get_pypi_package_info(package_name, current_version, target_python_version):
url = f"https://pypi.org/pypi/{package_name}/json"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
# Get the latest version
latest_version = data["info"]["version"]
# Get the release date of the latest version
latest_release_date = data["releases"][latest_version][0]["upload_time"]
# To find the minimum version that is compatible with Python 3.12, you would need to iterate
# over the releases and check which one specifies that it is compatible.
compatible_releases = {
version: release
for version, release in data["releases"].items()
for release_info in release
if release_info.get("requires_python")
and target_python_version in SpecifierSet(release_info["requires_python"])
}
min_version = min(compatible_releases, key=parse_version) if compatible_releases else "Unknown"
try:
requires_update = target_python_version not in SpecifierSet(
data["releases"][current_version][0]["requires_python"]
)
except AttributeError:
requires_update = "Unknown"
return latest_version, latest_release_date, min_version, requires_update
else:
return "Unknown", "Unknown Date", "Unknown", "Unknown"
# Function to process the requirements.txt file and get the information
def process_requirements(file_path, target_python_version):
with open(file_path) as file:
lines = file.readlines()
requirements_info = []
for line in lines:
if "==" in line:
package_name, current_version = line.strip("\\\n ").split("==")
latest_version, latest_release_date, min_version, requires_update = get_pypi_package_info(
package_name, current_version, target_python_version
)
# We do not have a direct way to get the minimum compatible version with Python 3.12 without internet access
requirements_info.append(
{
"package": package_name,
"current_version": current_version,
"latest_version": latest_version,
"latest_release_date": latest_release_date,
"python_target_min_version": min_version,
"requires_update": requires_update,
}
)
return requirements_info
def generate_markdown_report(requirements_info, file=None):
headers = [
"Package",
"Current Version",
"Latest Version",
"Latest Release Date",
"Min Version for Python",
"Requires Update",
]
rows = []
for req_info in requirements_info:
rows.append(
[
req_info["package"],
req_info["current_version"],
req_info["latest_version"],
req_info["latest_release_date"],
req_info["python_target_min_version"],
req_info["requires_update"],
]
)
# Determine the maximum width for each column
column_widths = [max(len(str(row[i])) for row in rows) for i in range(len(headers))]
# Create the header row
header_row = " | ".join(header.ljust(column_widths[i]) for i, header in enumerate(headers))
# Create the separator row
separator_row = " | ".join("-" * column_widths[i] for i, _ in enumerate(headers))
# Create the data rows
data_rows = [" | ".join(str(row[i]).ljust(column_widths[i]) for i in range(len(headers))) for row in rows]
# Combine all rows into a single string
markdown_output = "\n".join([header_row, separator_row, *data_rows])
# Print to stdout or write to a file
if file:
with open(file, "w") as f:
f.write(markdown_output)
else:
print(markdown_output)
def generate_csv_report(requirements_info, filename):
fieldnames = ['Package', 'Current Version', 'Latest Version', 'Latest Release Date', 'Min Version for Python', 'Requires Update']
with open(filename, "w", newline="") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for req_info in requirements_info:
row = {
'Package': req_info['package'],
'Current Version': req_info['current_version'],
'Latest Version': req_info['latest_version'],
'Latest Release Date': req_info['latest_release_date'],
'Min Version for Python': req_info['python_target_min_version'],
'Requires Update': req_info['requires_update'],
}
writer.writerow(row)
# Set up the argument parser
parser = argparse.ArgumentParser(description="Check package compatibility with a specified version of Python.")
parser.add_argument("requirements_file", type=str, help="Path to the requirements.txt file.")
parser.add_argument(
"-o",
"--output",
type=str,
help="Output file name. Format is determined by file extension (.csv for CSV, otherwise Markdown).",
)
parser.add_argument(
"-t",
"--target-python-version",
type=str,
default="3.12",
help='Target Python version for compatibility check. Default is "3.12".',
)
args = parser.parse_args()
# Process the requirements file
requirements_info = process_requirements(args.requirements_file, args.target_python_version)
if args.output:
file_ext = os.path.splitext(args.output)[-1]
if file_ext.lower() == ".csv":
generate_csv_report(requirements_info, args.output)
else:
generate_markdown_report(requirements_info, args.output)
else:
generate_markdown_report(requirements_info)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment