Skip to content
Snippets Groups Projects
pipdeps.py 6.93 KiB
Newer Older
  • Learn to ignore specific revisions
  • Marek Chrastina's avatar
    Marek Chrastina committed
    """
    pipdeps
    """
    import argparse
    import json
    import distutils.version
    import os
    import urllib2
    import re
    import subprocess
    import sys
    
    Marek Chrastina's avatar
    Marek Chrastina committed
    import tabulate
    
    Marek Chrastina's avatar
    Marek Chrastina committed
    import packaging.specifiers
    import packaging.version
    
    def arg_parse():
        """
        argument parser
        """
        parser = argparse.ArgumentParser(
            description="Pipdeps shows/upgrades outdated packages with respect to existing \
                         dependencies."
        )
        group = parser.add_mutually_exclusive_group(required=True)
        group.add_argument('-l', '--list',
                           action='store_true',
                           help="show upgradable packages and versions")
        group.add_argument('-u', '--upgrade',
                           action='store_true',
                           help="upgrade upgradable packages")
        return parser.parse_args()
    
    
    def get_pyver():
        """
        return running python version
        """
        return ".".join(map(str, sys.version_info[:3]))
    
    
    Marek Chrastina's avatar
    Marek Chrastina committed
    def check_strict_version(version):
        """
        Return true if version is strict, otherwise return false
        """
        try:
            distutils.version.StrictVersion(version)
        except ValueError:
            return False
        return True
    
    
    def check_requires_python(pyver, requires_python):
        """
        check if running python conforms version required by package
        """
        if not requires_python:
            return True
        elif pyver is None:
            return True
        else:
            ver = packaging.version.Version(pyver)
            spec = packaging.specifiers.SpecifierSet(",".join(requires_python))
            if spec.contains(ver):
                return True
        return False
    
    
    Marek Chrastina's avatar
    Marek Chrastina committed
    def upgrade_package(package, versions):
        """
        pip install --upgrade "<package><versions>"
        """
        subprocess.check_call(
            ["pip", "install", "--upgrade", "%s==%s" % (package, "".join(versions))],
            stderr=subprocess.STDOUT
            )
    
    def get_outdated_packages():
        """
        pip list --outdated
        """
        outdated_packages = subprocess.check_output(
            ["pip", "list", "--outdated"],
            stderr=subprocess.STDOUT
        )
        return [line.split()[0] for line in outdated_packages.strip().split("\n")[2:]]
    
    def get_dependencies_tree():
        """
        pipdeptree --json-tree
        """
        pipdeptree = subprocess.check_output(
            ["pipdeptree", "--json-tree"],
            stderr=subprocess.STDOUT
        )
        return json.loads(pipdeptree.strip())
    
    def json_search(jsonpipdeptree, package, key):
        """
        find package dependencies in json tree
        """
        if isinstance(jsonpipdeptree, dict):
            keys = jsonpipdeptree.keys()
            if 'package_name' in keys and key in keys:
                if re.search(r'^%s$' % package, jsonpipdeptree['package_name'], re.IGNORECASE):
                    yield jsonpipdeptree[key]
            for child_val in json_search(jsonpipdeptree['dependencies'], package, key):
                yield child_val
        elif isinstance(jsonpipdeptree, list):
            for item in jsonpipdeptree:
                for item_val in json_search(item, package, key):
                    yield item_val
    
    
    def find_available_versions(package_name, pyver):
    
    Marek Chrastina's avatar
    Marek Chrastina committed
        """
        Return descending list of available strict version
        """
    
        versions = []
    
    Marek Chrastina's avatar
    Marek Chrastina committed
        url = "https://pypi.python.org/pypi/%s/json" % (package_name,)
        data = json.load(urllib2.urlopen(urllib2.Request(url)))
    
        releases = data["releases"].keys()
        for release in releases:
            requires_python = [item['requires_python'] for item in data["releases"][release] if item['requires_python'] is not None] # pylint: disable=line-too-long
            if check_strict_version(release) and check_requires_python(pyver, requires_python):
                versions.append(release)
        return sorted(versions, key=distutils.version.StrictVersion, reverse=True)
    
    Marek Chrastina's avatar
    Marek Chrastina committed
    
    def find_upgradable_version(available_version, installed_version, required_version):
        """
        Return upgradable version, otherwise return none.
        """
    
        av_version = list(available_version) # copy list as new list
    
    Marek Chrastina's avatar
    Marek Chrastina committed
        while True:
            try:
    
                version = av_version.pop(0)
    
    Marek Chrastina's avatar
    Marek Chrastina committed
            except IndexError:
                break
            aver = packaging.version.Version(version)
            iver = packaging.version.Version(installed_version)
            rver = packaging.specifiers.SpecifierSet(",".join(required_version))
            if rver.contains(aver):
                if aver == iver:
                    break
                elif aver > iver:
                    return version
        return None
    
    
    def find_upgradable_packages(package_list, jsonpipdeptree, exclude=None, pyver=None):
    
    Marek Chrastina's avatar
    Marek Chrastina committed
        """
        Collect data about upgradable packages and return as list of dictionaries
        """
        result = []
        for package in package_list:
            if isinstance(exclude, list):
                if package in exclude:
                    continue
            dependencies = [_ for _ in json_search(jsonpipdeptree, package, 'required_version')]
            dependencies = list(set(dependencies))
            if not [dep for dep in dependencies if re.search(r'(^==.*|^\d.*)', dep) is not None]:
                installed_version = "".join(list(set(
                    [_ for _ in json_search(jsonpipdeptree, package, 'installed_version')]
                )))
                required_version = [dep for dep in dependencies if 'Any' not in dep]
    
                available_version = find_available_versions(package, pyver)
    
    Marek Chrastina's avatar
    Marek Chrastina committed
                upgradable_version = find_upgradable_version(
                    available_version, installed_version, required_version)
                rev = {'package': package,
                       'installed_version': installed_version,
                       'required_version': required_version,
                       'available_version': available_version}
                if upgradable_version is not None:
                    rev['upgradable_version'] = upgradable_version
                    result.append(rev)
        return result
    
    def main():
        """
        main function
        """
        os.environ["PYTHONWARNINGS"] = "ignore:DEPRECATION"
        arguments = arg_parse()
    
        pyver = get_pyver()
    
    Marek Chrastina's avatar
    Marek Chrastina committed
        outdated_packages = get_outdated_packages()
        while True:
    
            upgradable_packages = find_upgradable_packages(
                outdated_packages, get_dependencies_tree(), pyver=pyver
            )
    
    Marek Chrastina's avatar
    Marek Chrastina committed
            if arguments.list:
                if upgradable_packages:
    
    Marek Chrastina's avatar
    Marek Chrastina committed
                    data = [[pkg['package'], pkg['installed_version'], pkg['upgradable_version']] for pkg in upgradable_packages] # pylint: disable=line-too-long
                    header = ['package', 'installed_version', 'upgradable_version']
                    print tabulate.tabulate(data, header)
    
    Marek Chrastina's avatar
    Marek Chrastina committed
                    sys.exit(1)
                else:
                    print "There is nothing to upgrade."
                    sys.exit(0)
    
    
    Marek Chrastina's avatar
    Marek Chrastina committed
            for index, pkg  in enumerate(upgradable_packages):
                if pkg['package'] == 'pip':
                    package = upgradable_packages.pop(index)
                    upgrade_package(package['package'], package['upgradable_version'])
    
    
    Marek Chrastina's avatar
    Marek Chrastina committed
            try:
                package = upgradable_packages.pop(-1)
            except IndexError:
                break
            upgrade_package(package['package'], package['upgradable_version'])
    
        print "Done."
    
    if __name__ == "__main__":
        main()