52

How do you check if a package is at its latest version programmatically in a script and return a true or false?

I can check with a script like this:

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

or with command line:

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

But how do I check programmatically and return true or false?

Joseph
  • 755
  • 1
  • 5
  • 7
  • 4
    not a complete solution but it might give you some ideas. https://stackoverflow.com/questions/4888027/python-and-pip-list-all-versions-of-a-package-thats-available – reyPanda Oct 31 '19 at 17:45
  • Doesn't pip have an api you can call into? – Aluan Haddad Oct 31 '19 at 18:03
  • 4
    If you can make use of it, Python 3.8 has improved support for this kinda stuff, at least on the *what’s installed locally* side of it. https://docs.python.org/3/library/importlib.metadata.html – JL Peyret Oct 31 '19 at 18:29
  • 1
    `pip` does not have an API. You might want to watch [`pip-api`](https://github.com/di/pip-api) project, but there is not much there yet. – wim Oct 31 '19 at 19:54

9 Answers9

28

Fast Version (Checking the package only)

The code below calls the package with an unavailable version like pip install package_name==random. The call returns all the available versions. The program reads the latest version.

The program then runs pip show package_name and gets the current version of the package.

If it finds a match, it returns True, otherwise False.

This is a reliable option given that it stands on 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

Edit 2021: The code below no longer works with the new version of pip

The following code calls for 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__
  • 1,511
  • 2
  • 9
  • 21
  • I have updated it. Now it checks only the package the user is interested in. I have put both alternatives. – 1__ Oct 31 '19 at 19:31
  • 1
    Usually `if (boolean): return True else: return False` is better just to `return boolean` – wim Nov 01 '19 at 14:43
  • It's not good to use "0" as a bogus version number, because sometimes this will just go ahead and install a package! (try `pip install p==0` for example). – wim Nov 01 '19 at 14:58
  • I have used ```random``` I am sure there is no random version – 1__ Nov 01 '19 at 15:11
  • 1
    The latest version trick is no longer working in current versions of pip. – wim Jan 11 '21 at 19:20
  • It should be noted that this will not work in versions of python before 3.7 as subprocess.run has no capture_output option before that version. – The Elemental of Destruction Jul 12 '21 at 02:03
16

My project johnnydep has this feature.

In shell:

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

In Python:

>>> from johnnydep.lib import JohnnyDist
>>> dist = JohnnyDist("gekko")
>>> dist.version_installed  
'0.2.0'
>>> dist.version_latest 
'0.2.3'
wim
  • 338,267
  • 99
  • 616
  • 750
  • When I run this in the Windows Command Prompt, I get ANSI escape codes that make the result unreadable. I think you might want to fix this? – user541686 Nov 01 '19 at 11:19
  • I have colorama==0.4.1 and structlog==19.2.0, and yes, I do see escape codes with that command as well. If it helps I'm running this on Windows 10.0.17763.195, Python 3.7.4. I don't have a chance right now to post an issue unfortunately. – user541686 Nov 01 '19 at 15:12
  • @user541686 This was [issue232](https://github.com/hynek/structlog/issues/232), resolved in structlog v20.1.0 released today. Thanks for the report. – wim Jan 28 '20 at 16:21
16

To just check the latest version on PyPi (for Python 3.6 and up):

pip install requests

Then:

import requests

package = 'django'  # replace with the package you want to check
response = requests.get(f'https://pypi.org/pypi/{package}/json')
latest_version = response.json()['info']['version']
vvvvv
  • 25,404
  • 19
  • 49
  • 81
Christiaan Herrewijn
  • 1,031
  • 10
  • 11
10

Checking Installed version:

One way to check installed version is just to access the __version__ attribute of the top-level namespace:

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

Unfortunately not all projects set this attribute, it's just a common convention in Python. When they don't have a version attribute, you can use importlib.metadata to query the package version. This way does not actually require importing the package itself, since it's retrieved from the package metadata which gets written out when the package was installed.

>>> import importlib.metadata
>>> importlib.metadata.version("gekko")
'0.2.0'

This functionality is available since Python 3.8. In older Python versions, you can use pkg_resources similarly, which is a part of setuptools:

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

Checking Latest version:

There isn't currently a way to do this within stdlib. But my project luddite has this feature:

>>> import luddite
>>> luddite.get_version_pypi("gekko")
'0.2.3'
wim
  • 338,267
  • 99
  • 616
  • 750
2

This should do the trick at least for demo purposes. Simply call isLatestVersion with the name of the package you would like to check. If you are using this somewhere important you would want to try/catch the url request as internet access may not be available. Also note that if the package is not installed isLatestVersion will return False.

This is tested for Python 3.7.4 and 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
  • 841
  • 6
  • 12
  • 1
    `pip._internal` is not public API. It's even explicitly discouraged in [pip's docs](https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program): "***you must not use pip’s internal APIs in this way***". – wim Oct 31 '19 at 18:45
  • @wim Good to know. I was not aware of this. Thanks for letting me know. I would definitely recommend people using Yusuf Baktir's method instead anyway as it is simpler. – Daniel Hill Oct 31 '19 at 19:00
2

Edit: Remove pip search

Thanks for the several suggestions. Here is a new version that doesn't use pip search but instead pulls the latest version directly from pypi as proposed by Daniel Hill. This also resolves the issue with the substring false matches.

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

Original Response

Here is a fast solution that retrieves latest version information on only the gekko package of interest.

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

This produces the message Latest version (0.2.3) of gekko is installed and returns True to indicate latest version (or False if not the latest version). This may not be the best solution because it only checks for a version substring with if d[name] in s.decode(): but it is faster than pip list --outdated that checks all the packages. This isn't the most reliable method because it will return an incorrect True if current installed version is 0.2.3 but latest version is 0.2.30 or 0.2.3a. An improvement would be to programmatically get the latest version and do a direct comparison.

John Hedengren
  • 12,068
  • 1
  • 21
  • 25
  • Careful with `pip search`. It uses a deprecated XML-RPC API, and sometimes the search [results are inaccurate/wrong](https://github.com/pypa/pip/issues/395) In fact I think it might be removed soon, see [Remove the pip search command #5216](https://github.com/pypa/pip/issues/5216). – wim Oct 31 '19 at 19:14
  • I modified the code to use Daniel's method of pulling the current package version. Another way to get the current gekko version is to do `import gekko` and then `current_version=gekko.__version__` instead of creating a dictionary of all package versions. However, not all packages have a version number accessible in the package. – John Hedengren Oct 31 '19 at 20:04
2

It's not hard to write a simple script yourself by querying the PyPI API. With the latest Python 3.8, it's possible using only the standard library (when using Python 3.7 or older, you'll have to install the importlib_metadata backport):

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

Usage example:

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

If you're happen to have packaging installed, it's a better alternative to distutils.version for version parsing:

from distutils.version import LooseVersion

...

LooseVersion(s)

becomes

from packaging.version import parse

...

parse(s)
hoefling
  • 59,418
  • 12
  • 147
  • 194
  • This can give different results to pip if additional or alternate indexes are configured for the user (via pip.conf file or PIP_INDEX_URL or PIP_EXTRA_INDEX_URL env vars) – wim Nov 01 '19 at 13:48
0

Based on the accepted answer you can do:

pip list --outdated | grep name_of_package
Ahmad
  • 8,811
  • 11
  • 76
  • 141
-1

For pydicom these work fine,

import pydicom
print(pydicom.__version__) # output is a string: 'x.y.z'

or

import pydicom
print(pydicom.__version_info__) # output is a tuple: ('x', 'y', 'z')
vpap
  • 1,389
  • 2
  • 21
  • 32