Como verificar se o pacote python é a versão mais recente programaticamente?

29

Como você verifica se um pacote está na sua versão mais recente programaticamente em um script e retorna um valor verdadeiro ou falso?

Eu posso verificar com um script como este:

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

ou com linha de comando:

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

Mas como faço para verificar programaticamente e retornar verdadeiro ou falso?

Joseph
fonte
4
Não é uma solução completa, mas pode lhe dar algumas idéias. stackoverflow.com/questions/4888027/...
reyPanda
O pip não tem uma API que você pode chamar?
Aluan Haddad
3
Se você pode usá-lo, o Python 3.8 aprimorou o suporte para esse tipo de coisa, pelo menos no que está instalado localmente . docs.python.org/3/library/importlib.metadata.html
JL Peyret
11
pipnão possui uma API. Você pode assistir ao pip-apiprojeto, mas ainda não há muito.
wim

Respostas:

16

Versão Rápida (Verificando apenas o pacote)

O código abaixo chama o pacote com uma versão indisponível como pip install package_name==random. A chamada retorna todas as versões disponíveis. O programa lê a versão mais recente.

O programa é executado pip show package_namee obtém a versão atual do pacote.

Se encontrar uma correspondência, retornará True, caso contrário, False.

Esta é uma opção confiável, pois permanece em 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

O código a seguir pede 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
fonte
Eu atualizei. Agora ele verifica apenas o pacote no qual o usuário está interessado. Coloquei as duas alternativas.
Yusuf Baktir 31/10/19
11
Geralmente if (boolean): return True else: return Falseé melhor apenas parareturn boolean
wim
Não é bom usar "0" como um número de versão falso, porque às vezes isso simplesmente vai em frente e instala um pacote! (tente pip install p==0por exemplo).
wim
Eu tenho usado randomEstou certo de que não existe uma versão aleatória
Yusuf Baktir
10

Meu projeto johnnydeptem esse recurso.

Com casca:

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

Em Python:

>>> from johnnydep.lib import JohnnyDist
>>> dist = JohnnyDist("gekko")
>>> dist.version_installed  
'0.2.0'
>>> dist.version_latest 
'0.2.3'
wim
fonte
Quando executo isso no prompt de comando do Windows, recebo códigos de escape ANSI que tornam o resultado ilegível. Eu acho que você pode querer corrigir isso?
user541686
Eu tenho colorama == 0.4.1 e structlog == 19.2.0, e sim, também vejo códigos de escape com esse comando. Se ajudar, eu estou executando isso no Windows 10.0.17763.195, Python 3.7.4. Infelizmente, não tenho a chance de postar um problema no momento.
user541686
@ user541686 Este foi o problema232 , resolvido no structlog v20.1.0 lançado hoje. Obrigado pelo relatório.
wim 28/01
incrível, obrigado!
user541686 28/01
4

Editar: Remover pesquisa de pip

Obrigado pelas várias sugestões. Aqui está uma nova versão que não usa, pip searchmas extrai a versão mais recente diretamente, pypiconforme proposto por Daniel Hill . Isso também resolve o problema com as correspondências falsas da substring.

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

Resposta original

Aqui está uma solução rápida que recupera as informações da versão mais recente apenas no gekkopacote de interesse.

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

Isso produz a mensagem Latest version (0.2.3) of gekko is installede retorna Truepara indicar a versão mais recente (ou Falsese não a versão mais recente). Essa pode não ser a melhor solução, pois apenas verifica se há uma substring de versão, if d[name] in s.decode():mas é mais rápida do que pip list --outdatedisso verifica todos os pacotes. Este não é o método mais confiável, pois retornará um incorreto Truese a versão instalada atual for a versão 0.2.3mais recente 0.2.30ou for 0.2.3a. Uma melhoria seria obter programaticamente a versão mais recente e fazer uma comparação direta.

John Hedengren
fonte
Cuidado com pip search. Ele usa uma API XML-RPC reprovada e, às vezes, os resultados da pesquisa são imprecisos / incorretos Na verdade, acho que isso poderá ser removido em breve, consulte Remover o comando pip search # 5216 .
wim
Modifiquei o código para usar o método de Daniel de obter a versão atual do pacote. Outra maneira de obter a versão atual do gekko é fazer import gekkoe, em current_version=gekko.__version__vez de criar um dicionário de todas as versões de pacotes. No entanto, nem todos os pacotes têm um número de versão acessível no pacote.
John Hedengren
4

Última versão:

Meu projeto ludditetem esse recurso:

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

Versão instalada:

A maneira canônica de verificar a versão instalada é apenas acessar o __version__atributo do espaço para nome de nível superior:

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

Infelizmente, nem todos os projetos definem esse atributo. Quando não o fazem, você pode usá pkg_resources-lo para extrair os metadados:

>>> import pkg_resources
>>> pkg_resources.get_distribution("gekko").version
'0.2.0'
wim
fonte
2

Isso deve funcionar, pelo menos para fins de demonstração. Basta ligar isLatestVersioncom o nome do pacote que você deseja verificar. Se você estiver usando isso em algum lugar importante, convém tentar / capturar a solicitação de URL, pois o acesso à Internet pode não estar disponível. Observe também que, se o pacote não estiver instalado isLatestVersion, retornará False.

Isso é testado para Python 3.7.4 e 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
fonte
11
pip._internalnão é uma API pública. É até explicitamente desencorajado nos documentos do pip : " você não deve usar as APIs internas do pip dessa maneira ".
wim
@ wim É bom saber. Eu não estava ciente disso. Obrigado por me avisar. Definitivamente, eu recomendaria pessoas que usem o método de Yusuf Baktir de qualquer maneira, pois é mais simples.
Daniel Hill
2

Não é difícil escrever um script simples consultando a API do PyPI . Com o Python 3.8 mais recente, é possível usar apenas a biblioteca padrão (ao usar o Python 3.7 ou mais antigo, você precisará instalar o 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)

Exemplo de uso:

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

Se você packaginginstalou, é uma alternativa melhor para distutils.versiona análise de versão:

from distutils.version import LooseVersion

...

LooseVersion(s)

torna-se

from packaging.version import parse

...

parse(s)
hoefling
fonte
Isto pode dar resultados diferentes para pip se índices adicionais ou alternativos estão configurados para o utilizador (através de arquivo pip.conf ou PIP_INDEX_URL ou env PIP_EXTRA_INDEX_URL vars)
wim