.github/scripts/get_min_versions.py PYTHON 200 lines View on github.com → Search inside
1"""Get minimum versions of dependencies from a pyproject.toml file."""23import sys4from collections import defaultdict56if sys.version_info >= (3, 11):7    import tomllib8else:9    # For Python 3.10 and below, which doesnt have stdlib tomllib10    import tomli as tomllib1112import re13from typing import List1415import requests16from packaging.requirements import Requirement17from packaging.specifiers import SpecifierSet18from packaging.version import Version, parse1920MIN_VERSION_LIBS = [21    "langchain-core",22    "langchain",23    "langchain-text-splitters",24    "numpy",25    "SQLAlchemy",26]2728# some libs only get checked on release because of simultaneous changes in29# multiple libs30SKIP_IF_PULL_REQUEST = [31    "langchain-core",32    "langchain-text-splitters",33    "langchain",34]353637def get_pypi_versions(package_name: str) -> List[str]:38    """Fetch all available versions for a package from PyPI.3940    Args:41        package_name: Name of the package4243    Returns:44        List of all available versions4546    Raises:47        requests.exceptions.RequestException: If PyPI API request fails48        KeyError: If package not found or response format unexpected49    """50    pypi_url = f"https://pypi.org/pypi/{package_name}/json"51    response = requests.get(pypi_url, timeout=10.0)52    response.raise_for_status()53    return list(response.json()["releases"].keys())545556def get_minimum_version(package_name: str, spec_string: str) -> str | None:57    """Find the minimum published version that satisfies the given constraints.5859    Args:60        package_name: Name of the package61        spec_string: Version specification string (e.g., ">=0.2.43,<0.4.0,!=0.3.0")6263    Returns:64        Minimum compatible version or None if no compatible version found65    """66    # Rewrite occurrences of ^0.0.z to 0.0.z (can be anywhere in constraint string)67    spec_string = re.sub(r"\^0\.0\.(\d+)", r"0.0.\1", spec_string)68    # Rewrite occurrences of ^0.y.z to >=0.y.z,<0.y+1 (can be anywhere in constraint string)69    for y in range(1, 10):70        spec_string = re.sub(71            rf"\^0\.{y}\.(\d+)", rf">=0.{y}.\1,<0.{y + 1}", spec_string72        )73    # Rewrite occurrences of ^x.y.z to >=x.y.z,<x+1.0.0 (can be anywhere in constraint string)74    for x in range(1, 10):75        spec_string = re.sub(76            rf"\^{x}\.(\d+)\.(\d+)", rf">={x}.\1.\2,<{x + 1}", spec_string77        )7879    spec_set = SpecifierSet(spec_string)80    all_versions = get_pypi_versions(package_name)8182    valid_versions = []83    for version_str in all_versions:84        try:85            version = parse(version_str)86            if spec_set.contains(version):87                valid_versions.append(version)88        except ValueError:89            continue9091    return str(min(valid_versions)) if valid_versions else None929394def _check_python_version_from_requirement(95    requirement: Requirement, python_version: str96) -> bool:97    if not requirement.marker:98        return True99    else:100        marker_str = str(requirement.marker)101        if "python_version" in marker_str or "python_full_version" in marker_str:102            python_version_str = "".join(103                char104                for char in marker_str105                if char.isdigit() or char in (".", "<", ">", "=", ",")106            )107            return check_python_version(python_version, python_version_str)108        return True109110111def get_min_version_from_toml(112    toml_path: str,113    versions_for: str,114    python_version: str,115    *,116    include: list | None = None,117):118    # Parse the TOML file119    with open(toml_path, "rb") as file:120        toml_data = tomllib.load(file)121122    dependencies = defaultdict(list)123    for dep in toml_data["project"]["dependencies"]:124        requirement = Requirement(dep)125        dependencies[requirement.name].append(requirement)126127    # Initialize a dictionary to store the minimum versions128    min_versions = {}129130    # Iterate over the libs in MIN_VERSION_LIBS131    for lib in set(MIN_VERSION_LIBS + (include or [])):132        if versions_for == "pull_request" and lib in SKIP_IF_PULL_REQUEST:133            # some libs only get checked on release because of simultaneous134            # changes in multiple libs135            continue136        # Check if the lib is present in the dependencies137        if lib in dependencies:138            if include and lib not in include:139                continue140            requirements = dependencies[lib]141            for requirement in requirements:142                if _check_python_version_from_requirement(requirement, python_version):143                    version_string = str(requirement.specifier)144                    break145146            # Use parse_version to get the minimum supported version from version_string147            min_version = get_minimum_version(lib, version_string)148149            # Store the minimum version in the min_versions dictionary150            min_versions[lib] = min_version151152    return min_versions153154155def check_python_version(version_string, constraint_string):156    """Check if the given Python version matches the given constraints.157158    Args:159        version_string: A string representing the Python version (e.g. "3.8.5").160        constraint_string: A string representing the package's Python version161            constraints (e.g. ">=3.6, <4.0").162163    Returns:164        True if the version matches the constraints165    """166167    # Rewrite occurrences of ^0.0.z to 0.0.z (can be anywhere in constraint string)168    constraint_string = re.sub(r"\^0\.0\.(\d+)", r"0.0.\1", constraint_string)169    # Rewrite occurrences of ^0.y.z to >=0.y.z,<0.y+1.0 (can be anywhere in constraint string)170    for y in range(1, 10):171        constraint_string = re.sub(172            rf"\^0\.{y}\.(\d+)", rf">=0.{y}.\1,<0.{y + 1}.0", constraint_string173        )174    # Rewrite occurrences of ^x.y.z to >=x.y.z,<x+1.0.0 (can be anywhere in constraint string)175    for x in range(1, 10):176        constraint_string = re.sub(177            rf"\^{x}\.0\.(\d+)", rf">={x}.0.\1,<{x + 1}.0.0", constraint_string178        )179180    try:181        version = Version(version_string)182        constraints = SpecifierSet(constraint_string)183        return version in constraints184    except Exception as e:185        print(f"Error: {e}")186        return False187188189if __name__ == "__main__":190    # Get the TOML file path from the command line argument191    toml_file = sys.argv[1]192    versions_for = sys.argv[2]193    python_version = sys.argv[3]194    assert versions_for in ["release", "pull_request"]195196    # Call the function to get the minimum versions197    min_versions = get_min_version_from_toml(toml_file, versions_for, python_version)198199    print(" ".join([f"{lib}=={version}" for lib, version in min_versions.items()]))

Code quality findings 4

Avoid unnecessary list conversions; use generators where possible
unnecessary-list
return list(response.json()["releases"].keys())
Ensure functions have docstrings for documentation
missing-docstring
def get_min_version_from_toml(
Use logging module for better control and configurability
print-statement
print(f"Error: {e}")
Use logging module for better control and configurability
print-statement
print(" ".join([f"{lib}=={version}" for lib, version in min_versions.items()]))

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.