3
\$\begingroup\$

Following this post, I have a PowerShell script that aims to interact with the Python Package Index (PyPI) to find the latest release of a specified package that is compatible with the current Python environment. The script has several key functionalities:

  1. Determines the currently installed Python version and adjusts it to only include the major and minor version numbers (e.g., 3.8 from 3.8.5).
  2. Fetches package data from PyPI using the package name.
  3. Parses the JSON response to find the latest release compatible with the determined Python version. Compatibility is based on each release's requires_python field.
  4. Outputs the details of the found release or indicates if no compatible release is found.

Here is the PowerShell script:

# The Python version to match or find a slightly lower version for
$targetPythonVersion = [version](python --version 2>&1 | ForEach-Object { $_ -replace '^Python\s', '' } | ForEach-Object { ($_.Split('.')[0..1]) -join '.' })

function Get-Json {
    param (
        [Parameter(mandatory = $true)]
        [string]$PackageName
    )

    $url = "https://pypi.org/pypi/$PackageName/json"
    $response = Invoke-RestMethod -Uri $url
    return $response
}


# Function to check if a version satisfies the requirement
function Test-VersionCompatibility {
    param (
        [Parameter(mandatory = $true)]
        [version]$requiredVersion,
        [Parameter(mandatory = $true)]
        [string]$requiresPython
    )

    # Split the requirement string into versions, assuming the format is ">=2.6,!=3.0.*,!=3.1.*,!=3.2.*"
    $versionConditions = $requiresPython -split ','

    foreach ($condition in $versionConditions) {
        if ($condition -match '>=?(\d\.\d+)(\.(\d+))?') {
            $minVersion = [version]$Matches[1]
            if ($requiredVersion -lt $minVersion) {
                return $false
            }
        }
        elseif ($condition -match '<=?(\d\.\d+)(\.(\d+))?') {
            $maxVersion = [version]$Matches[1]
            if ($requiredVersion -gt $maxVersion) {
                return $false
            }
        }
        elseif ($condition -match '!=?(\d\.\d+)(\.(\d+))?') {
            $notVersion = [version]$Matches[1]
            if ($requiredVersion -eq $notVersion) {
                return $false
            }
        }
    }

    return $true
}

function Get-Latest {
    param (
        [Parameter(mandatory = $true)]
        [version]$requiredVersion,
        [Parameter(mandatory = $true)]
        [string]$PackageName
    )

    # Read the JSON content
    $jsonContent = Get-Json -PackageName $PackageName

    # Iterate through releases in reverse chronological order
    $latestCompatibleRelease = $null
    foreach ($release in ($jsonContent.releases.PSObject.Properties | Sort-Object Name -Descending)) {
        foreach ($package in $release.Value) {
            if ($null -ne $package.requires_python -and (Test-VersionCompatibility -requiredVersion $requiredVersion -requiresPython $package.requires_python)) {
                $latestCompatibleRelease = $release
                break
            }
        }
        if ($latestCompatibleRelease) {
            break
        }
    }

    # Output the found release
    if ($latestCompatibleRelease) {
        # $latestCompatibleRelease.Name
        return ($latestCompatibleRelease.Value | ConvertTo-Json)
    }
    else {
        Write-Output "No compatible release found for Python $requiredVersion"
    }
    
}

$release = Get-Latest -requiredVersion $targetPythonVersion -PackageName "setuptools"
Write-Output $release

I'm seeking feedback on the following:

  • Best practices for PowerShell scripting, especially regarding error handling and JSON parsing.
  • Any potential performance improvements, particularly in parsing and iterating over the JSON response.
  • Ways to enhance the version comparison logic to determine compatibility, especially considering Python's versioning scheme more accurately.
\$\endgroup\$
1
  • \$\begingroup\$ Have you considered solutions such as Poetry? I am slightly surprised that you've used Powershell and not Python, although Powershell can be installed on Linux platforms too. If you are using Windows PS is very likely available out of the box. \$\endgroup\$
    – Kate
    Commented Mar 26 at 22:05

1 Answer 1

2
\$\begingroup\$

obtaining minor version

$targetPythonVersion = [version](python --version 2>&1 |
  ForEach-Object { $_ -replace '^Python\s', '' } |
  ForEach-Object { ($_.Split('.')[0..1]) -join '.' })

For someone who is fond of authoring powershell code, perhaps that join is the most natural way to express it.

Me? I feel using pre-defined identifiers would be appropriate in this case.

$ python -c 'import sys; v = sys.version_info; print(f"{v.major}.{v.minor}")'
3.12

The March Hare urges that "you should say what you mean." A join approach is fewer characters, but IMHO more cryptic and a bit different from what I really mean:

$ python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))'
3.12

lexical vs. numeric order

  • "11" < "9"
  • 11 > 9

I don't understand lines such as this:

function Test-VersionCompatibility {
            ...
            if ($requiredVersion -lt $minVersion) {

That is, the function's name suggests that it will perform version comparisons. But that conditional appears to be a lexical comparison.

The versioning spec is super complicated. I can't imagine why any mere mortal would wish to wade into those waters in hopes of reinventing the wheel. Better to run away in abject terror, and enlist the aid of a package that knows (some of) the details already.

Consider ripping out the bulk of this code, replacing it with a call to a python (or other) program that can do the heavy lifting for you. There's no shame in outsourcing such effort. Even Newton coded on the shoulders of giants.

Maybe write a two-line requirements.txt file, and ask python -m pip install -r requirements.txt to contact PyPI and figure out the version details for you? I apologize if I'm sort of rejecting the premise behind this development effort. I would expect that asking for pip and setuptools, with no version constraints, would fetch the most current ones.

Also, a word to the wise: unittest!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.