import contextlib
import subprocess
import pip
import pkg_resources
from django_test_tools.exceptions import DjangoTestToolsException
from django_test_tools.utils import versiontuple
if versiontuple(pip.__version__) >= (10, 0, 0):
from pip._internal.req import parse_requirements
from pip._internal.req.req_install import InstallRequirement
else:
from pip.req import parse_requirements, InstallRequirement
import re
import requests
import logging
logger = logging.getLogger(__name__)
[docs]@contextlib.contextmanager
def capture():
import sys
from io import StringIO
oldout, olderr = sys.stdout, sys.stderr
try:
out = [StringIO(), StringIO()]
sys.stdout, sys.stderr = out
yield out
finally:
sys.stdout, sys.stderr = oldout, olderr
out[0] = out[0].getvalue()
out[1] = out[1].getvalue()
[docs]def parse_pip_list(line):
regexp = re.compile(r'([\w\-]*)\s\(([\w\-_\.]*)\)\s\-\sLatest:\s([\w\-_\.]*)\s\[([a-z]*)\]')
regexp_new = re.compile(r'([\w\-]*)\s+([\w\-_\.]*)\s+([\w\-_\.]*)\s+([\w\-_\.]*)')
regexp_list = re.compile(r'([\w\-]*)\s+([\w\-_\.]*)')
match = regexp.match(line)
if match is None:
match = regexp_new.match(line)
if match:
library = dict()
library['name'] = match.group(1).lower()
library['current_version'] = match.group(2)
library['new_version'] = match.group(3)
return library
match_list = regexp_list.match(line)
if match_list:
library = dict()
library['name'] = match_list.group(1).lower()
library['current_version'] = match_list.group(2)
return library
return None
[docs]def get_latest_version(package_name):
url = 'https://pypi.python.org/pypi/{}/json'.format(package_name)
r = requests.get(url)
versions = sorted(r.json()["releases"], key=pkg_resources.parse_version)
return versions[-1]
[docs]def parse_comes_from(comes_from):
regexp = re.compile(r'(\-r)\s([/\w\.\-]*)\s\(line\s(\d*)\)')
match = regexp.match(comes_from)
if match:
return match.group(1), match.group(2), int(match.group(3))
else:
raise ValueError('Invalid comes from "{}"'.format(comes_from))
[docs]def parse_specifier(specifier):
regexp = re.compile(r'((?:[=>])=)([\w\-_\.]*)')
match = regexp.match(specifier)
if match:
return match.group(1), match.group(2)
else:
raise ValueError('Invalid speficier "{}"'.format(specifier))
[docs]def read_requirement_file(req_file):
# print('pip version {}'.format(pip.__version__))
requirements = dict()
for item in parse_requirements(req_file, session="somesession"):
if isinstance(item, InstallRequirement):
requirement = dict()
requirement['name'] = item.name
if len(str(item.req.specifier)) > 0:
operator, version = parse_specifier(str(item.req.specifier))
requirement['operator'] = operator
requirement['version'] = version
requirement['comes_from'] = dict()
requirement['comes_from']['value'] = item.comes_from
file_indicator, filename, line_no = parse_comes_from(item.comes_from)
requirement['comes_from']['file_indicator'] = file_indicator
requirement['comes_from']['filename'] = filename
requirement['comes_from']['line_no'] = line_no
requirements[item.name] = requirement
return requirements
[docs]def list_outdated_libraries():
return list_libraries(True)
[docs]def list_libraries(outdated=False):
command_list = ['pip', 'list']
if outdated:
command_list.append('--outdated')
pip_list = subprocess.check_output(command_list)
# pip.main(['list', '--outdated'])
library_lines = pip_list.decode("utf-8").split('\n')
outdated_libraries = list()
for line in library_lines:
library = parse_pip_list(line)
if library is not None:
outdated_libraries.append(library)
return outdated_libraries
[docs]def update_outdated_libraries(requirement_file, **kwargs):
"""
Updates the requirements to their latest version.
:param requirement_file: String with the path to the requirement fiel
:param kwargs:
:return: list of dictionaries containting the changes
"""
requirements = read_requirement_file(requirement_file)
outdated_libraries = list_outdated_libraries()
changes = list()
for outdated_library in outdated_libraries:
library_name = outdated_library['name']
if requirements.get(library_name):
change = dict()
change['library_name'] = library_name
change['filename'] = requirements[library_name]['comes_from']['filename']
line_no = requirements[library_name]['comes_from']['line_no'] - 1
operator = requirements[library_name]['operator']
with open(change['filename'], 'r') as file:
data = file.readlines()
try:
new_value = '{}{}{}\n'.format(library_name, operator, outdated_library['new_version'])
except KeyError as e:
msg = 'Could not find new version for library {library_name} in {filename}'.format(**change)
raise DjangoTestToolsException(msg)
change['previous'] = data[line_no].strip('\n')
change['new'] = new_value.strip('\n')
data[line_no] = new_value
with open(change['filename'], 'w') as file:
file.writelines(data)
change['line_no'] = line_no
changes.append(change)
return changes