Как проверить, является ли пакет python последней версией программно?

29

Как вы проверяете, является ли пакет в его последней версии программно в скрипте и возвращаете истину или ложь?

Я могу проверить с помощью сценария, как это:

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'])

или с командной строкой:

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

Но как мне проверить программно и вернуть true или false?

Джозеф
источник
4
не полное решение, но оно может дать вам некоторые идеи. stackoverflow.com/questions/4888027/…
reyPanda
У pip нет API, к которому можно обратиться?
Алуан Хаддад
3
Если вы можете использовать его, Python 3.8 улучшил поддержку такого рода вещей, по крайней мере, на той стороне , которая установлена ​​локально . docs.python.org/3/library/importlib.metadata.html
JL Peyret
1
pipне имеет API. Возможно, вы захотите посмотреть pip-apiпроект, но там еще немногое.
Вим

Ответы:

16

Быстрая версия (только проверка пакета)

Код ниже вызывает пакет с недоступной версией вроде pip install package_name==random. Вызов возвращает все доступные версии. Программа читает последнюю версию.

Затем программа запускается pip show package_nameи получает текущую версию пакета.

Если он находит совпадение, он возвращает True, иначе False.

Это надежный вариант, учитывая, что он стоит на 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

Следующий код требует 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
Юсуф Бактир
источник
Я обновил это. Теперь он проверяет только тот пакет, который интересует пользователя. Я поставил обе альтернативы.
Юсуф Бактир
1
Обычно if (boolean): return True else: return Falseлучше простоreturn boolean
WIM
Неверно использовать «0» в качестве фиктивного номера версии, потому что иногда это просто приводит к установке пакета! (попробуйте pip install p==0например).
Вим
Я использовал, randomя уверен, что нет случайной версии
Юсуф Бактир
10

Мой проект johnnydepимеет эту функцию.

В ракушке:

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

В Python:

>>> from johnnydep.lib import JohnnyDist
>>> dist = JohnnyDist("gekko")
>>> dist.version_installed  
'0.2.0'
>>> dist.version_latest 
'0.2.3'
Wim
источник
Когда я запускаю это в командной строке Windows, я получаю управляющие коды ANSI, которые делают результат нечитаемым. Я думаю, вы можете это исправить?
user541686
У меня есть colorama == 0.4.1 и structlog == 19.2.0, и да, я также вижу escape-коды с этой командой. Если это поможет, я запускаю это на Windows 10.0.17763.195, Python 3.7.4. У меня сейчас нет возможности опубликовать вопрос, к сожалению.
user541686
@ user541686 Это была проблема232 , решенная в structlog v20.1.0, выпущенной сегодня. Спасибо за отчет.
Вим
офигенно, спасибо!
user541686
4

Редактировать: Удалить поиск пипса

Спасибо за несколько предложений. Вот новая версия, которая не использует, pip searchно вместо этого извлекает последнюю версию прямо из pypiпредложенного Дэниэлом Хиллом . Это также решает проблему с ложными совпадениями подстроки.

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'))

Оригинальный ответ

Вот быстрое решение, которое извлекает информацию о последней версии только gekkoдля интересующего вас пакета.

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'))

Это создает сообщение Latest version (0.2.3) of gekko is installedи возвращает, Trueчтобы указать последнюю версию (или, Falseесли не последняя версия). Это может быть не лучшим решением, потому что оно проверяет только подстроку версии, if d[name] in s.decode():но это быстрее, чем pip list --outdatedпроверяет все пакеты. Это не самый надежный метод, потому что он вернет неправильный, Trueесли текущая установленная версия, 0.2.3но последняя версия 0.2.30или 0.2.3a. Улучшение будет заключаться в том, чтобы программно получить последнюю версию и сделать прямое сравнение.

Джон Хеденгрен
источник
Осторожнее с pip search. Он использует устаревший API-интерфейс XML-RPC, и иногда результаты поиска являются неточными / неправильными. На самом деле, я думаю, что он может быть скоро удален, см. Раздел Удаление команды поиска pip # 5216 .
Вим
Я изменил код, чтобы использовать метод Дэниела для извлечения текущей версии пакета. Другой способ получить текущую версию gekko - это сделать, import gekkoа current_version=gekko.__version__не создавать словарь всех версий пакета. Однако не все пакеты имеют номер версии, доступный в пакете.
Джон Хеденгрен
4

Последняя версия:

Мой проект ludditeимеет эту функцию:

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

Установленная версия:

Канонический способ проверить установленную версию - просто получить доступ к __version__атрибуту пространства имен верхнего уровня:

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

К сожалению, не все проекты устанавливают этот атрибут. Когда они этого не делают, вы можете использовать их pkg_resourcesдля метаданных:

>>> import pkg_resources
>>> pkg_resources.get_distribution("gekko").version
'0.2.0'
Wim
источник
2

Это должно помочь, по крайней мере, в демонстрационных целях. Просто позвоните isLatestVersionс названием пакета, который вы хотели бы проверить. Если вы используете это где-то важное, вы можете попробовать / поймать URL-запрос, так как доступ в Интернет может быть недоступен. Также обратите внимание, что если пакет не установлен isLatestVersion, вернется False.

Это проверено на Python 3.7.4 и 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
Дэниэл Хилл
источник
1
pip._internalне является публичным API. Это даже явно не рекомендуется в документах pip : « Вы не должны использовать внутренние API pip таким образом ».
Вим
@ Wim Полезно знать. Я не знал об этом. Спасибо, что дали мне знать. Я определенно рекомендовал бы людям, использующим метод Юсуфа Бактира, так или иначе, поскольку это проще.
Даниэль Хилл
2

Нетрудно написать простой скрипт, обратившись к PyPI API . В последнем Python 3.8 можно использовать только стандартную библиотеку (при использовании Python 3.7 или более ранней версии вам необходимо установить importlib_metadataбэкпорт):

# 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)

Пример использования:

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

Если вы случайно packagingустановили, это лучшая альтернатива distutils.versionдля анализа версии:

from distutils.version import LooseVersion

...

LooseVersion(s)

становится

from packaging.version import parse

...

parse(s)
Хефлинг
источник
Это может дать разные результаты для pip, если для пользователя настроены дополнительные или альтернативные индексы (через файл pip.conf или PIP_INDEX_URL или PIP_EXTRA_INDEX_URL env vars)
wim