Jak sprawdzić, czy pakiet Python jest najnowszą wersją programowo?

29

Jak sprawdzisz, czy pakiet jest w najnowszej wersji programowo w skrypcie, i zwróci prawdę, czy fałsz?

Mogę sprawdzić za pomocą takiego skryptu:

package='gekko'
import pip
if hasattr(pip, 'main'):
    from pip import main as pipmain
else:
    from pip._internal import main as pipmain
pipmain(['search','gekko'])

lub z linii poleceń:

(base) C:\User>pip search gekko
gekko (0.2.3)  - Machine learning and optimization for dynamic systems
  INSTALLED: 0.2.3 (latest)

Ale jak sprawdzać programowo i zwracać wartość prawda lub fałsz?

Joseph
źródło
4
nie jest to kompletne rozwiązanie, ale może dać ci kilka pomysłów. stackoverflow.com/questions/4888027/…
reyPanda
Czy pip nie ma interfejsu API, do którego można zadzwonić?
Aluan Haddad
3
Jeśli możesz z niego skorzystać, Python 3.8 poprawił obsługę tego rodzaju rzeczy, przynajmniej po stronie zainstalowanej lokalnie . docs.python.org/3/library/importlib.metadata.html
JL Peyret
1
pipnie ma interfejsu API. Możesz obejrzeć pip-apiprojekt, ale nie ma tam jeszcze wiele.
wim

Odpowiedzi:

16

Szybka wersja (tylko sprawdzanie pakietu)

Poniższy kod wywołuje pakiet z niedostępną wersją, taką jak pip install package_name==random . Połączenie zwraca wszystkie dostępne wersje. Program czyta najnowszą wersję.

Następnie program uruchamia się pip show package_namei pobiera bieżącą wersję pakietu.

Jeśli znajdzie dopasowanie, zwraca wartość Prawda, w przeciwnym razie Fałsz.

Jest to niezawodna opcja, biorąc pod uwagę, że trwa pip

import subprocess
import sys
def check(name):
    latest_version = str(subprocess.run([sys.executable, '-m', 'pip', 'install', '{}==random'.format(name)], capture_output=True, text=True))
    latest_version = latest_version[latest_version.find('(from versions:')+15:]
    latest_version = latest_version[:latest_version.find(')')]
    latest_version = latest_version.replace(' ','').split(',')[-1]

    current_version = str(subprocess.run([sys.executable, '-m', 'pip', 'show', '{}'.format(name)], capture_output=True, text=True))
    current_version = current_version[current_version.find('Version:')+8:]
    current_version = current_version[:current_version.find('\\n')].replace(' ','') 

    if latest_version == current_version:
        return True
    else:
        return False

Następujący kod wymaga pip list --outdated:

import subprocess
import sys

def check(name):
    reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'list','--outdated'])
    outdated_packages = [r.decode().split('==')[0] for r in reqs.split()]
    return name in outdated_packages
Yusuf Baktir
źródło
Zaktualizowałem to. Teraz sprawdza tylko pakiet, którym zainteresowany jest użytkownik. Podałem obie alternatywy.
Yusuf Baktir
1
Zwykle if (boolean): return True else: return Falselepiej jest po prostureturn boolean
wim
Używanie „0” jako fałszywego numeru wersji nie jest dobre, ponieważ czasami wystarczy, że zainstalujesz pakiet! (spróbuj pip install p==0na przykład).
wim
Użyłem randomJestem pewien, że nie ma losowej wersji
Yusuf Baktir
10

Mój projekt johnnydepma tę funkcję.

W skorupkach:

pip install --upgrade pip johnnydep
pip install gekko==0.2.0

W Pythonie:

>>> from johnnydep.lib import JohnnyDist
>>> dist = JohnnyDist("gekko")
>>> dist.version_installed  
'0.2.0'
>>> dist.version_latest 
'0.2.3'
wim
źródło
Gdy uruchomię to w wierszu polecenia systemu Windows, otrzymuję kody ucieczki ANSI, które uniemożliwiają odczytanie wyniku. Myślę, że możesz to naprawić?
user541686,
Mam colorama == 0.4.1 i structlog == 19.2.0 i tak, widzę kody ucieczki również z tym poleceniem. Jeśli to pomaga, uruchamiam to w systemie Windows 10.0.17763.195, Python 3.7.4. Niestety nie mam teraz szansy opublikować problemu.
user541686,
@ user541686 To był problem 232 rozwiązany w structlog v20.1.0 wydanym dzisiaj. Dziękuję za raport.
wim
Wielkie dzieki!
user541686
4

Edycja: Usuń wyszukiwanie pip

Dziękuję za kilka sugestii. Oto nowa wersja, która nie korzysta, pip searchale pobiera najnowszą wersję bezpośrednio z pypipropozycji Daniela Hilla . To rozwiązuje również problem z fałszywymi dopasowaniami podłańcucha.

def check(name):
    import subprocess
    import sys
    import json
    import urllib.request

    # create dictionary of package versions
    pkgs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
    keys = [p.decode().split('==')[0] for p in pkgs.split()]
    values = [p.decode().split('==')[1] for p in pkgs.split()]
    d = dict(zip(keys, values)) # dictionary of all package versions

    # retrieve info on latest version
    contents = urllib.request.urlopen('https://pypi.org/pypi/'+name+'/json').read()
    data = json.loads(contents)
    latest_version = data['info']['version']

    if d[name]==latest_version:
        print('Latest version (' + d[name] + ') of '+str(name)+' is installed')
        return True
    else:
        print('Version ' + d[name] + ' of '+str(name)+' not the latest '+latest_version)
        return False

print(check('gekko'))

Oryginalna odpowiedź

Oto szybkie rozwiązanie, które pobiera informacje o najnowszej wersji tylko gekkoo interesującym pakiecie.

def check(name):
    import subprocess
    import sys
    # create dictionary of package versions
    pkgs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
    keys = [p.decode().split('==')[0] for p in pkgs.split()]
    values = [p.decode().split('==')[1] for p in pkgs.split()]
    d = dict(zip(keys, values)) # dictionary of all package versions

    # retrieve info on latest version
    s = subprocess.check_output([sys.executable, '-m', 'pip', 'search', name])

    if d[name] in s.decode():  # weakness
        print('Latest version (' + d[name] + ') of '+str(name)+' is installed')
        return True
    else:
        print(s.decode())
        return False

print(check('gekko'))

Spowoduje to wyświetlenie komunikatu Latest version (0.2.3) of gekko is installedi powrót Truedo wskazania najnowszej wersji (lub Falsejeśli nie najnowszej wersji). To może nie być najlepsze rozwiązanie, ponieważ sprawdza tylko podciąg wersji, if d[name] in s.decode():ale jest szybszy niż pip list --outdatedwszystkie pakiety. Nie jest to najbardziej niezawodna metoda, ponieważ zwróci ona nieprawidłową wartość, Truejeśli bieżąca wersja jest zainstalowana, 0.2.3ale najnowsza wersja to 0.2.30lub 0.2.3a. Ulepszeniem byłoby programowo uzyskanie najnowszej wersji i bezpośrednie porównanie.

John Hedengren
źródło
Ostrożnie z pip search. Używa przestarzałego interfejsu API XML-RPC, a czasem wyniki wyszukiwania są niedokładne / niepoprawne. Właściwie myślę, że może zostać wkrótce usunięte, zobacz Polecenie usuwania pip search # 5216 .
wim
Zmodyfikowałem kod, aby użyć metody pobierania aktualnej wersji pakietu przez Daniela. Innym sposobem uzyskania aktualnej wersji gekko jest zrobienie, import gekkoa następnie current_version=gekko.__version__utworzenie słownika wszystkich wersji pakietów. Jednak nie wszystkie pakiety mają numer wersji dostępny w pakiecie.
John Hedengren,
4

Ostatnia wersja:

Mój projekt ludditema tę funkcję:

>>> import luddite
>>> luddite.get_version_pypi("gekko")
'0.2.3'

Zainstalowana wersja:

Kanonicznym sposobem sprawdzenia zainstalowanej wersji jest po prostu dostęp do __version__atrybutu przestrzeni nazw najwyższego poziomu:

>>> import gekko
>>> gekko.__version__
'0.2.0'

Niestety nie wszystkie projekty ustawiają ten atrybut. Jeśli nie, możesz użyć, pkg_resourcesaby wykopać je z metadanych:

>>> import pkg_resources
>>> pkg_resources.get_distribution("gekko").version
'0.2.0'
wim
źródło
2

To powinno załatwić sprawę przynajmniej w celach demonstracyjnych. Po prostu zadzwoń isLatestVersionz nazwą pakietu, który chcesz sprawdzić. Jeśli używasz tego gdzieś ważnego, chciałbyś spróbować złapać żądanie adresu URL, ponieważ dostęp do Internetu może być niedostępny. Pamiętaj również, że jeśli pakiet nie jest zainstalowanyisLatestVersion zostanie , zwróci False.

Jest to testowane dla Pythona 3.7.4 i Pip 19.0.3.

import pip
import subprocess
import json
import urllib.request
from pip._internal.operations.freeze import freeze

def isLatestVersion(pkgName):
    # Get the currently installed version
    current_version = ''
    for requirement in freeze(local_only=False):
        pkg = requirement.split('==')
        if pkg[0] == pkgName:
            current_version = pkg[1]

    # Check pypi for the latest version number
    contents = urllib.request.urlopen('https://pypi.org/pypi/'+pkgName+'/json').read()
    data = json.loads(contents)
    latest_version = data['info']['version']

    return latest_version == current_version
Daniel Hill
źródło
1
pip._internalnie jest publicznym API. Dokumenty pipa wyraźnie odradzają nawet : „ nie wolno używać wewnętrznych interfejsów API pip w ten sposób ”.
wim
@wim Dobrze wiedzieć. Nie byłem tego świadomy. Dzięki, że dałeś mi znać. Zdecydowanie poleciłbym zamiast tego ludzi korzystających z metody Yusufa Baktira, ponieważ jest to prostsze.
Daniel Hill
2

Nie jest trudno napisać prosty skrypt samodzielnie, sprawdzając API PyPI . W najnowszym Pythonie 3.8 możliwe jest używanie tylko standardowej biblioteki (w przypadku Pythona w wersji 3.7 lub starszej będziesz musiał zainstalować importlib_metadatabackport):

# check_version.py

import json
import urllib.request
import sys

try:
    from importlib.metadata import version
except ImportError:
    from importlib_metadata import version

from distutils.version import LooseVersion


if __name__ == '__main__':
    name = sys.argv[1]
    installed_version = LooseVersion(version(name))

    # fetch package metadata from PyPI
    pypi_url = f'https://pypi.org/pypi/{name}/json'
    response = urllib.request.urlopen(pypi_url).read().decode()
    latest_version = max(LooseVersion(s) for s in json.loads(response)['releases'].keys())

    print('package:', name, 'installed:', installed_version, 'latest:', latest_version)

Przykład użycia:

$ python check_version.py setuptools
package: setuptools installed: 41.2.0 latest: 41.6.0

Jeśli akurat masz packagingzainstalowany, jest to lepsza alternatywa distutils.versiondla analizy wersji:

from distutils.version import LooseVersion

...

LooseVersion(s)

staje się

from packaging.version import parse

...

parse(s)
hoefling
źródło
Może to dać pipowi różne wyniki, jeśli dla użytkownika zostaną skonfigurowane dodatkowe lub alternatywne indeksy (za pomocą pliku pip.conf lub PIP_INDEX_URL lub PIP_EXTRA_INDEX_URL env var)
wim