diff --git a/Pipfile b/Pipfile index dd8a3a48..496b040e 100644 --- a/Pipfile +++ b/Pipfile @@ -1,6 +1,7 @@ [dev-packages] pytest = "*" sphinx = "*" +mock = "*" [packages] click = "*" diff --git a/pipenv/cli.py b/pipenv/cli.py index 3f8e8593..7bc321c8 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -551,10 +551,16 @@ def do_init(dev=False, requirements=False, skip_virtualenv=False, allow_global=F def pip_install(package_name=None, r=None, allow_global=False): - if r: - c = delegator.run('{0} install -r {1} --require-hashes -i {2}'.format(which_pip(allow_global=allow_global), r, project.source['url'])) - else: - c = delegator.run('{0} install "{1}" -i {2}'.format(which_pip(allow_global=allow_global), package_name, project.source['url'])) + # try installing for each source in project.sources + for source in project.sources: + if r: + c = delegator.run('{0} install -r {1} --require-hashes -i {2}'.format(which_pip(allow_global=allow_global), r, source['url'])) + else: + c = delegator.run('{0} install "{1}" -i {2}'.format(which_pip(allow_global=allow_global), package_name, source['url'])) + + if c.return_code == 0: + break + # return the result of the first one that runs ok or the last one that didn't work return c diff --git a/pipenv/project.py b/pipenv/project.py index 12886000..3ca986c2 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -127,16 +127,16 @@ class Project(object): f.write(format_toml(toml.dumps(data))) @property - def source(self): + def sources(self): if self.lockfile_exists: meta_ = self.lockfile_content['_meta'] sources_ = meta_.get('sources') if sources_: - return sources_[0] + return sources_ if 'source' in self.parsed_pipfile: - return self.parsed_pipfile['source'][0] + return self.parsed_pipfile['source'] else: - return {u'url': u'https://pypi.python.org/simple', u'verify_ssl': True} + return [{u'url': u'https://pypi.python.org/simple', u'verify_ssl': True}] def remove_package_from_pipfile(self, package_name, dev=False): diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index b894e0df..219376e7 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -1,13 +1,16 @@ import os +from mock import patch, Mock, PropertyMock + import pytest import delegator import toml from pipenv.cli import (activate_virtualenv, ensure_proper_casing, - parse_download_fname, parse_install_output) + parse_download_fname, parse_install_output, pip_install) from pipenv.project import Project + class TestPipenv(): @pytest.mark.parametrize('fname, name, expected', [ @@ -131,3 +134,53 @@ class TestPipenv(): venv = Project().virtualenv_location assert command == '{0}/bin/activate'.format(venv) + + @patch('pipenv.project.Project.sources', new_callable=PropertyMock) + @patch('delegator.run') + def test_pip_install_should_try_every_possible_source(self, mocked_delegator, mocked_sources): + sources = [ + {'url': 'http://dontexistis.in.pypi/simple'}, + {'url': 'http://existis.in.pypi/simple'} + ] + mocked_sources.return_value = sources + first_cmd_return = Mock() + first_cmd_return.return_code = 1 + second_cmd_return = Mock() + second_cmd_return.return_code = 0 + mocked_delegator.side_effect = [first_cmd_return, second_cmd_return] + c = pip_install('package') + assert c.return_code == 0 + + @patch('pipenv.project.Project.sources', new_callable=PropertyMock) + @patch('delegator.run') + def test_pip_install_should_return_the_last_error_if_no_cmd_worked(self, mocked_delegator, mocked_sources): + sources = [ + {'url': 'http://dontexistis.in.pypi/simple'}, + {'url': 'http://dontexistis.in.pypi/simple'} + ] + mocked_sources.return_value = sources + first_cmd_return = Mock() + first_cmd_return.return_code = 1 + second_cmd_return = Mock() + second_cmd_return.return_code = 1 + mocked_delegator.side_effect = [first_cmd_return, second_cmd_return] + c = pip_install('package') + assert c.return_code == 1 + assert c == second_cmd_return + + @patch('pipenv.project.Project.sources', new_callable=PropertyMock) + @patch('delegator.run') + def test_pip_install_should_return_the_first_cmd_that_worked(self, mocked_delegator, mocked_sources): + sources = [ + {'url': 'http://existis.in.pypi/simple'}, + {'url': 'http://existis.in.pypi/simple'} + ] + mocked_sources.return_value = sources + first_cmd_return = Mock() + first_cmd_return.return_code = 0 + second_cmd_return = Mock() + second_cmd_return.return_code = 0 + mocked_delegator.side_effect = [first_cmd_return, second_cmd_return] + c = pip_install('package') + assert c.return_code == 0 + assert c == first_cmd_return