Skip to content
Snippets Groups Projects
pipdeps.py 5.68 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
    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 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 upgrade_pip():
        """
        pip install --upgrade pip
        """
        subprocess.check_call(["pip", "install", "--upgrade", "pip"], stderr=subprocess.STDOUT)
    
    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):
        """
        Return descending list of available strict version
        """
        url = "https://pypi.python.org/pypi/%s/json" % (package_name,)
        data = json.load(urllib2.urlopen(urllib2.Request(url)))
        versions = data["releases"].keys()
        data = [version for version in versions if check_strict_version(version)]
        return sorted(data, key=distutils.version.StrictVersion, reverse=True)
    
    def find_upgradable_version(available_version, installed_version, required_version):
        """
        Return upgradable version, otherwise return none.
        """
        while True:
            try:
                version = available_version.pop(0)
            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):
        """
        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)
                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()
        upgrade_pip()
        outdated_packages = get_outdated_packages()
    
        while True:
            upgradable_packages = find_upgradable_packages(outdated_packages, get_dependencies_tree())
            if arguments.list:
                if upgradable_packages:
                    print upgradable_packages
                    sys.exit(1)
                else:
                    print "There is nothing to upgrade."
                    sys.exit(0)
    
            try:
                package = upgradable_packages.pop(-1)
            except IndexError:
                break
            upgrade_package(package['package'], package['upgradable_version'])
    
        print "Done."
    
    if __name__ == "__main__":
        main()