Skip to content

Commit

Permalink
Merge 0f0f7a3 into 8b11a5a
Browse files Browse the repository at this point in the history
  • Loading branch information
diegoduncan21 committed Dec 12, 2015
2 parents 8b11a5a + 0f0f7a3 commit d27e48d
Show file tree
Hide file tree
Showing 5 changed files with 1,796 additions and 0 deletions.
51 changes: 51 additions & 0 deletions fades/helpers.py
Expand Up @@ -22,6 +22,11 @@
import logging
import subprocess

from urllib import request
from urllib.error import HTTPError

import pkg_resources

logger = logging.getLogger(__name__)

SHOW_VERSION_CMD = """
Expand All @@ -31,6 +36,8 @@
print(json.dumps(d))
"""

BASE = 'http://pypi.python.org/pypi/{name}/json'


def logged_exec(cmd):
"""Execute a command, redirecting the output to the log."""
Expand Down Expand Up @@ -95,3 +102,47 @@ def get_interpreter_version(requested_interpreter):
logger.debug('Interpreter=%s. It is the same as fades?=%s',
requested_interpreter, is_current)
return (requested_interpreter, is_current)


def get_latest_version_number(project_name):
"""Return latest version of a package."""
try:
raw = request.urlopen(BASE.format(name=project_name)).read()
try:
data = json.loads(raw.decode("utf8"))
latest_version = data["info"]["version"]
except (KeyError, ValueError) as error: # malformed json or empty string
logger.error("Could not get the version of the package, error: %s", error)
return None
return latest_version
except HTTPError as error:
logger.warning("Requested project named %s is not found in PyPI, error: %s",
project_name, error)
return None


def check_pypi_updates(dependencies):
"""Return a list of dependencies to upgrade."""
dependencies_up_to_date = []
for dependency in dependencies.get('pypi', []):
# get latest version from PyPI api
latest_version = get_latest_version_number(dependency.project_name)

# get required version
required_version = None
if dependency.specs:
_, required_version = dependency.specs[0]

# log in INFO if there is a new version
if latest_version is not None and required_version is not None:
if latest_version > required_version:
logger.info("There is a new version %s of %s",
latest_version, dependency.project_name)

if required_version:
dependencies_up_to_date.append(dependency)
else:
project_name = "{}=={}".format(dependency.project_name, latest_version)
dependencies_up_to_date.append(pkg_resources.Requirement.parse(project_name))
dependencies["pypi"] = dependencies_up_to_date
return dependencies
6 changes: 6 additions & 0 deletions fades/main.py
Expand Up @@ -94,6 +94,8 @@ def go(argv):
parser.add_argument('--virtualenv-options', action='append', default=[],
help=("Extra options to be supplied to virtualenv. (this option can be"
"used multiple times)"))
parser.add_argument('--check-updates', action='store_true',
help=("check for packages updates"))
parser.add_argument('--pip-options', action='append', default=[],
help=("Extra options to be supplied to pip. (this option can be"
"used multiple times)"))
Expand Down Expand Up @@ -173,6 +175,10 @@ def go(argv):
l.debug("Dependencies from parameters: %s", manual_deps)
indicated_deps = _merge_deps(ipython_dep, indicated_deps, reqfile_deps, manual_deps)

# Check for packages updates
if args.check_updates:
helpers.check_pypi_updates(indicated_deps)

# get the interpreter version requested for the child_program
interpreter, is_current = helpers.get_interpreter_version(args.python)

Expand Down
1 change: 1 addition & 0 deletions tests/examples/pypi_get_version_fail.json
@@ -0,0 +1 @@
{"MALFORMED": {"json": "1.0"}}
1,672 changes: 1,672 additions & 0 deletions tests/examples/pypi_get_version_ok.json

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions tests/test_helpers.py
Expand Up @@ -16,15 +16,21 @@

"""Tests for functions in helpers."""

import io
import os
import sys
import unittest
from unittest.mock import patch
from urllib.error import HTTPError

import logassert

from fades import helpers


PATH_TO_EXAMPLES = "tests/examples/"


class GetInterpreterVersionTestCase(unittest.TestCase):
"""Some tests for get_interpreter_version."""

Expand Down Expand Up @@ -125,3 +131,63 @@ def test_requested_not_exists(self):
helpers._get_interpreter_info('pythonME')
self.assertLoggedError("Error getting requested interpreter version:"
" [Errno 2] No such file or directory: 'pythonME'")


class GetLatestVersionNumberTestCase(unittest.TestCase):
"""Some tests for get_latest_version_number."""

def setUp(self):
logassert.setup(self, 'fades.helpers')

def test_get_version_correct(self):
with open(os.path.join(PATH_TO_EXAMPLES, 'pypi_get_version_ok.json'), "rb") as fh:
with patch('urllib.request.urlopen') as mock_urlopen:
mock_urlopen.return_value = fh
last_version = helpers.get_latest_version_number("some_package")
mock_urlopen.assert_called_once_with(helpers.BASE.format(name="some_package"))
self.assertEquals(last_version, '2.8.1')

def test_get_version_wrong(self):
with patch('urllib.request.urlopen') as mock_urlopen:
with patch('http.client.HTTPResponse') as mock_http_response:
mock_http_response.read.side_effect = HTTPError("url",
500,
"mgs",
{},
io.BytesIO())
mock_urlopen.return_value = mock_http_response
result = helpers.get_latest_version_number("some_package")
self.assertLoggedWarning("Requested project named some_package is not found in",
"PyPI")
self.assertEqual(result, None)

def test_get_version_fail(self):
with open(os.path.join(PATH_TO_EXAMPLES, 'pypi_get_version_fail.json'), "rb") as fh:
with patch('urllib.request.urlopen') as mock_urlopen:
mock_urlopen.return_value = fh
last_version = helpers.get_latest_version_number("some_package")
self.assertLoggedError("Could not get the version of the package")
mock_urlopen.assert_called_once_with(helpers.BASE.format(name="some_package"))
self.assertEquals(last_version, None)


class CheckPyPIUpdatesTestCase(unittest.TestCase):
"""Some tests for check_pypi_updates."""

def setUp(self):
from fades import parsing
logassert.setup(self, 'fades.helpers')
self.deps = parsing.parse_manual(["django==1.7.5", "requests"])

def test_check_pypi_updates_with_and_without_version(self):
with patch('urllib.request.urlopen') as mock_urlopen:
with patch('http.client.HTTPResponse') as mock_http_response:
mock_http_response.read.side_effect = [b'{"info": {"version": "1.9"}}',
b'{"info": {"version": "2.1"}}']
mock_urlopen.return_value = mock_http_response
dependencies = helpers.check_pypi_updates(self.deps)
dep_django = dependencies['pypi'][0]
dep_request = dependencies['pypi'][1]
self.assertLoggedInfo('There is a new version 1.9 of django')
self.assertEquals(dep_request.specs, [('==', '2.1')])
self.assertEquals(dep_django.specs, [('==', '1.7.5')])

0 comments on commit d27e48d

Please sign in to comment.