Newer
Older
"""
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 get_pyver():
"""
return running python version
"""
return ".".join(map(str, sys.version_info[:3]))
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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):
"""
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)))
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)
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
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):
"""
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)
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()
outdated_packages = get_outdated_packages()
while True:
upgradable_packages = find_upgradable_packages(
outdated_packages, get_dependencies_tree(), pyver=pyver
)
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)
sys.exit(1)
else:
print "There is nothing to upgrade."
sys.exit(0)
for index, pkg in enumerate(upgradable_packages):
if pkg['package'] == 'pip':
package = upgradable_packages.pop(index)
upgrade_package(package['package'], package['upgradable_version'])