3

For example function available('foobar>=1.1') should check that package foobar is installed and its version is >= 1.1.

  • There is importlib.util.find_spec, which can check whether module is installed.
  • and LooseVersion from distutils.version to compare versions

But how can I parser string like foobar>=1.1 and split it into module, version and comparator?

Or, what is more interesting, maybe there is already some standard way to do all these stuff at the same time? pip is probably doing the same thing when it reads packages from requirements.txt

3 Answers 3

5

You can do the following if this helps:

import pkg_resources
my_packages = list(pkg_resources.find_distributions("C:/python27/lib/site-packages"))
version_string = my_packages[0].version

In [24]: print(my_packages[0])
zc.buildout 2.9.4
In [25]: my_packages[0]
Out[25]: '2.9.4'

The requirements functionality is implemented in pkg_resource in this fairly huge document:

https://setuptools.readthedocs.io/en/latest/pkg_resources.html

For instance the following checks whether or not the packages are available and provides an exception if they are not

In [31]: pkg_resources.require('zc.buildout == 2.9.4')
Out[31]:
[zc.buildout 2.9.4 (c:\python27\lib\site-packages),
 setuptools 40.6.2 (c:\python27\lib\site-packages)]

To parse the name of a requirements you can use pkg_resources.parse_requirements this will give you the version and the module name and the comparator used.

require = list(pkg_resources.parse_requirements("zc.buildout == 2.9.4"))[0]
print(require.name)
'zc.buildout'
print(require.specs)
 [('==', '2.9.4')]

For the function which you wish to write you can do the following, Note this does not check if the package is available or not.

def split_package_requirement(package_string='foobar>=1.1'):
    """Splits the requirement into, name, comparator and version."""
    requirement = next(pkg_resources.parse_requirements("zc.buildout == 2.9.4"))
    comparator, version = requirement.specs[0]

    return requirement.name, comparator, version
Sign up to request clarification or add additional context in comments.

Comments

0

You can use something like this for parsing module name and version

line = 'foobar>=1.1'
def f(line):

  i = line.find('=')
  if i!=-1:
    if line[i+1]!='=':
        i=i-1
    return line[:i],line[i+2:]
  else:
    return line,''

Output:

('foobar', '1.1')

Comments

0

If you don't mind using the subprocess module (Security concerns) There is a way that depends on the output of pip list.

Simple way to check existence and minimum version

import sys
import subprocess
from distutils.version import LooseVersion

def available(desired_package, min_version):
    packages = subprocess.check_output([sys.executable, '-m', 'pip', 'list']).decode().split('\n')[2:-1] # First 2 lines, and last line are not packages
    package_version = [package.split() for package in packages]

    package_dict = {}
    for package, version in package_version:
        package_dict[package] = version

    try: 
        if LooseVersion(package_dict[desired_package]) >= LooseVersion(min_version):
            print('Minimum version requirement met')
        else:
            print('Minimum version requirement not met')
    except KeyError:
            print('{} not found.'.format(desired_package))
>>> available('tensorflow', '0.0.1')
Minimum version requirement met
>>> available('tensorflow', '20.0.1')
Minimum version requirement not met
>>> available('tensorflowasd', '20.0.1')
tensorflowasd not found.

More complicated way if exact comparison needs to be done from an input: "tensorflow>1.13.0"

import sys
import subprocess
from distutils.version import LooseVersion
import re

def available(input):
    def version_comparer(version, desired_version, comparator):
        if comparator == '<=': return LooseVersion(version) <= LooseVersion(desired_version)
        elif comparator == '>=': return LooseVersion(version) >= LooseVersion(desired_version)
        elif comparator == '==': return LooseVersion(version) == LooseVersion(desired_version)
        elif comparator == '>': return LooseVersion(version) > LooseVersion(desired_version)
        elif comparator == '<': return LooseVersion(version) < LooseVersion(desired_version)
        else:
            print('Unexpected comparator')
            exit(1)

    match = re.match('([A-Za-z0-9-_]+)([^A-Za-z0-9-_]+)([\d\.]+$)', input)
    desired_package, comparator, desired_version = match[1], match[2], match[3]

    packages = subprocess.check_output([sys.executable, '-m', 'pip', 'list']).decode().split('\n')[2:-1] # First 2 lines, and last line are not packages
    package_version = [package.split() for package in packages]

    package_dict = {}
    for package, version in package_version:
        package_dict[package] = version
    try: 
        if version_comparer(package_dict[desired_package], desired_version, comparator):
            print('Input validated')
        else:
            print('Package exists, but requirement not met')
    except KeyError:
        print('{} not found.'.format(desired_package))
>>> available('tensorflow<0.0.1')
Package exists, but requirement not met
>>> available('tensorflowasd<0.0.1')
tensorflowasd not found.
>>> available('tensorflow>0.0.1')
Input validated

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.