From e054d772546c8ef77b9c5ce59d06d961430e03c5 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Fri, 13 Apr 2018 01:15:17 -0400 Subject: [PATCH 01/17] Add `--extra-index-url` from all extra indexes - Always add extra indexes when installing - Look up indexes by key if key is given instead of url - Fixes #1973, #1974, #1852 Signed-off-by: Dan Ryan --- pipenv/core.py | 108 ++++++++++++++++++++++++---------------------- pipenv/project.py | 1 - pipenv/utils.py | 35 +++++++++++++++ 3 files changed, 91 insertions(+), 53 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index f58db399..d6f442b0 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -18,7 +18,7 @@ import delegator from .vendor import pexpect import pipfile from blindspin import spinner - +from first import first from requests.packages import urllib3 from requests.packages.urllib3.exceptions import InsecureRequestWarning @@ -46,6 +46,8 @@ from .utils import ( is_star, TemporaryDirectory, rmtree, + split_index, + split_extra_index, ) from .import pep508checker, progress from .environments import ( @@ -790,12 +792,8 @@ def do_install_dependencies( for dep, ignore_hash, block in deps_list_bar: if len(procs) < PIPENV_MAX_SUBPROCESS: # Use a specific index, if specified. - index = None - if ' -i ' in dep: - dep, index = dep.split(' -i ') - dep = '{0} {1}'.format(dep, ' '.join(index.split()[1:])).strip( - ) - index = index.split()[0] + dep, index = split_index(dep) + dep, extra_index = split_extra_index(dep) # Install the module. c = pip_install( dep, @@ -806,6 +804,7 @@ def do_install_dependencies( block=block, index=index, requirements_dir=requirements_dir, + extra_indexes=extra_index, ) c.dep = dep c.ignore_hash = ignore_hash @@ -824,12 +823,9 @@ def do_install_dependencies( for dep, ignore_hash in progress.bar( failed_deps_list, label=INSTALL_LABEL2 ): - index = None - if ' -i ' in dep: - dep, index = dep.split(' -i ') - dep = '{0} {1}'.format(dep, ' '.join(index.split()[1:])).strip( - ) - index = index.split()[0] + # Use a specific index, if specified. + dep, index = split_index(dep) + dep, extra_index = split_extra_index(dep) # Install the module. c = pip_install( dep, @@ -839,6 +835,7 @@ def do_install_dependencies( verbose=verbose, index=index, requirements_dir=requirements_dir, + extra_index=extra_index, ) # The Installation failed... if c.return_code != 0: @@ -1367,6 +1364,7 @@ def pip_install( pre=False, selective_upgrade=False, requirements_dir=None, + extra_indexes=None, ): import pip9 @@ -1415,51 +1413,57 @@ def pip_install( src = '' else: src = '' + # Try installing for each source in project.sources. if index: + valid_indexes = [p['name'] for p in project.parsed_pipfile['source']] + if not is_valid_url(index) and index in valid_indexes: + index = first([p['url'] for p in project.parsed_pipfile['source'] if p['name'] == index]) sources = [{'url': index}] + if extra_indexes: + extra_indexes = [{'url': extra_src} for extra_src in extra_indexes if extra_src != index] + else: + extra_indexes = [{'url': s['url']} for s in project.parsed_pipfile['source'] if s['url'] != index] + sources = sources.extend(extra_indexes) else: sources = project.sources - for source in sources: - if package_name.startswith('-e '): - install_reqs = ' -e "{0}"'.format(package_name.split('-e ')[1]) - elif r: - install_reqs = ' -r {0}'.format(r) - else: - install_reqs = ' "{0}"'.format(package_name) - # Skip hash-checking mode, when appropriate. - if r: - with open(r) as f: - if '--hash' not in f.read(): - ignore_hashes = True - else: - if '--hash' not in install_reqs: + if package_name.startswith('-e '): + install_reqs = ' -e "{0}"'.format(package_name.split('-e ')[1]) + elif r: + install_reqs = ' -r {0}'.format(r) + else: + install_reqs = ' "{0}"'.format(package_name) + # Skip hash-checking mode, when appropriate. + if r: + with open(r) as f: + if '--hash' not in f.read(): ignore_hashes = True - verbose_flag = '--verbose' if verbose else '' - if not ignore_hashes: - install_reqs += ' --require-hashes' - no_deps = '--no-deps' if no_deps else '' - pre = '--pre' if pre else '' - quoted_python = which('python', allow_global=allow_global) - quoted_python = escape_grouped_arguments(quoted_python) - upgrade_strategy = '--upgrade --upgrade-strategy=only-if-needed' if selective_upgrade else '' - pip_command = '{0} -m pipenv.vendor.pip9 install {4} {5} {6} {7} {3} {1} {2} --exists-action w'.format( - quoted_python, - install_reqs, - ' '.join(prepare_pip_source_args([source])), - no_deps, - pre, - src, - verbose_flag, - upgrade_strategy, - ) - if verbose: - click.echo('$ {0}'.format(pip_command), err=True) - c = delegator.run(pip_command, block=block) - if c.return_code == 0: - break - - # Return the result of the first one that runs ok, or the last one that didn't work. + else: + if '--hash' not in install_reqs: + ignore_hashes = True + verbose_flag = '--verbose' if verbose else '' + if not ignore_hashes: + install_reqs += ' --require-hashes' + no_deps = '--no-deps' if no_deps else '' + pre = '--pre' if pre else '' + quoted_python = which('python', allow_global=allow_global) + quoted_python = escape_grouped_arguments(quoted_python) + sources = [] + print(sources) + upgrade_strategy = '--upgrade --upgrade-strategy=only-if-needed' if selective_upgrade else '' + pip_command = '{0} -m pipenv.vendor.pip9 install {4} {5} {6} {7} {3} {1} {2} --exists-action w'.format( + quoted_python, + install_reqs, + ' '.join(prepare_pip_source_args(sources)), + no_deps, + pre, + src, + verbose_flag, + upgrade_strategy, + ) + if verbose: + click.echo('$ {0}'.format(pip_command), err=True) + c = delegator.run(pip_command, block=block) return c diff --git a/pipenv/project.py b/pipenv/project.py index bf0c50fe..d45a11e1 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -6,7 +6,6 @@ import re import sys import base64 import hashlib - import contoml import pipfile import pipfile.api diff --git a/pipenv/utils.py b/pipenv/utils.py index 59e8f5b2..897821a6 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -14,6 +14,7 @@ import stat import warnings from click import echo as click_echo +from first import first try: from weakref import finalize except ImportError: @@ -1291,3 +1292,37 @@ class TemporaryDirectory(object): def cleanup(self): if self._finalizer.detach(): rmtree(self.name) + + +def split_index(req): + """Split an index argument from a requirement (finds None if not present) + + returns req, index + """ + index_entries = [' -i ', ' --index ', ' --index='] + index = None + index_entry = first([entry for entry in index_entries if entry in req]) + if index_entry: + req, index = req.split(index_entry) + remaining_line = index.split() + if len(remaining_line) > 1: + index, more_req = remaining_line[0], ' '.join(remaining_line[1:]) + req = '{0} {1}'.format(req, more_req) + return req, index + + +def split_extra_index(req): + """Split an extra index argument from a requirement (or None if not present) + + returns req, index + """ + index_entries = [' --extra-index-url ', ' --extra-index-url='] + index = None + index_entry = first([entry for entry in index_entries if entry in req]) + if index_entry: + req, index = req.split(index_entry) + remaining_line = index.split() + if len(remaining_line) > 1: + index, more_req = remaining_line[0], ' '.join(remaining_line[1:]) + req = '{0} {1}'.format(req, more_req) + return req, index From 2000f4728105819cf7bfdaf60346e6b88b4e5a4d Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Fri, 13 Apr 2018 10:16:00 -0400 Subject: [PATCH 02/17] Check for sources in pipfile before validating Signed-off-by: Dan Ryan --- pipenv/core.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index d6f442b0..1452d3cb 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1416,13 +1416,16 @@ def pip_install( # Try installing for each source in project.sources. if index: - valid_indexes = [p['name'] for p in project.parsed_pipfile['source']] + valid_indexes = [] + extra_indexes = [] if not extra_indexes else [extra_indexes] + if 'source' in project.parsed_pipfile: + valid_indexes = [p['name'] for p in project.parsed_pipfile['source']] if not is_valid_url(index) and index in valid_indexes: index = first([p['url'] for p in project.parsed_pipfile['source'] if p['name'] == index]) sources = [{'url': index}] if extra_indexes: extra_indexes = [{'url': extra_src} for extra_src in extra_indexes if extra_src != index] - else: + elif 'source' in project.parsed_pipfile and len(project.parsed_pipfile['source']) > 1: extra_indexes = [{'url': s['url']} for s in project.parsed_pipfile['source'] if s['url'] != index] sources = sources.extend(extra_indexes) else: From 8401accbbb4b29b423f8eed2e977a765b0f31363 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 12 Apr 2018 02:42:49 -0400 Subject: [PATCH 03/17] add PIPENV_INSTALL_TIMEOUT to envar index Signed-off-by: Dan Ryan --- pipenv/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pipenv/core.py b/pipenv/core.py index 1452d3cb..0818b1da 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1452,7 +1452,6 @@ def pip_install( quoted_python = which('python', allow_global=allow_global) quoted_python = escape_grouped_arguments(quoted_python) sources = [] - print(sources) upgrade_strategy = '--upgrade --upgrade-strategy=only-if-needed' if selective_upgrade else '' pip_command = '{0} -m pipenv.vendor.pip9 install {4} {5} {6} {7} {3} {1} {2} --exists-action w'.format( quoted_python, From 34d34aea234d6b0d7f9e9539e3fc6a1b5e1e4358 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Fri, 13 Apr 2018 19:55:16 -0400 Subject: [PATCH 04/17] Handle indexes, extra indexes, uncached sources - Handle extra-index-urls when resolving - Handle extra-index-url when using `--skip-lock` - Parse index arguments when installing individual packages - Translate index aliases to urls - Always include extra indexes when installing a packages - `get_source()` falls back to `parsed_pipfile['source']` for sources when not present in the lockfile (#1994) - Include index and extra-index-url arguments in `pipenv lock -r` output - Fixes #1973, #1974, #1852, #1977, #1994 Signed-off-by: Dan Ryan --- pipenv/core.py | 50 +++++++++++++++++++++++++++++++++-------------- pipenv/project.py | 40 ++++++++++++++++++++++++++++++------- pipenv/utils.py | 3 +++ 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index 0818b1da..9f79310d 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -18,12 +18,12 @@ import delegator from .vendor import pexpect import pipfile from blindspin import spinner -from first import first from requests.packages import urllib3 from requests.packages.urllib3.exceptions import InsecureRequestWarning +import six from .cmdparse import ScriptEmptyError -from .project import Project +from .project import Project, SourceNotFound from .utils import ( convert_deps_from_pip, convert_deps_to_pip, @@ -777,7 +777,10 @@ def do_install_dependencies( l[i] = list(l[i]) if '--hash' in l[i][0]: l[i][0] = (l[i][0].split('--hash')[0].strip()) + index_args = prepare_pip_source_args(project.sources) + index_args = ' '.join(index_args).replace(' -', '\n-') # Output only default dependencies + click.echo(index_args) if not dev: click.echo('\n'.join(d[0] for d in sorted(deps_list))) sys.exit(0) @@ -835,7 +838,7 @@ def do_install_dependencies( verbose=verbose, index=index, requirements_dir=requirements_dir, - extra_index=extra_index, + extra_indexes=extra_index, ) # The Installation failed... if c.return_code != 0: @@ -1416,20 +1419,25 @@ def pip_install( # Try installing for each source in project.sources. if index: - valid_indexes = [] - extra_indexes = [] if not extra_indexes else [extra_indexes] - if 'source' in project.parsed_pipfile: - valid_indexes = [p['name'] for p in project.parsed_pipfile['source']] - if not is_valid_url(index) and index in valid_indexes: - index = first([p['url'] for p in project.parsed_pipfile['source'] if p['name'] == index]) + if not is_valid_url(index): + index = project.find_source(index).get('url') sources = [{'url': index}] if extra_indexes: - extra_indexes = [{'url': extra_src} for extra_src in extra_indexes if extra_src != index] - elif 'source' in project.parsed_pipfile and len(project.parsed_pipfile['source']) > 1: - extra_indexes = [{'url': s['url']} for s in project.parsed_pipfile['source'] if s['url'] != index] - sources = sources.extend(extra_indexes) + if isinstance(extra_indexes, six.string_types): + extra_indexes = [extra_indexes,] + for idx in extra_indexes: + try: + extra_src = project.find_source(idx).get('url') + except SourceNotFound: + extra_src = idx + if extra_src != index: + sources.append({'url': extra_src}) + else: + for idx in project.pipfile_sources: + if idx['url'] != sources[0]['url']: + sources.append({'url': idx['url']}) else: - sources = project.sources + sources = project.pipfile_sources if package_name.startswith('-e '): install_reqs = ' -e "{0}"'.format(package_name.split('-e ')[1]) elif r: @@ -1451,7 +1459,6 @@ def pip_install( pre = '--pre' if pre else '' quoted_python = which('python', allow_global=allow_global) quoted_python = escape_grouped_arguments(quoted_python) - sources = [] upgrade_strategy = '--upgrade --upgrade-strategy=only-if-needed' if selective_upgrade else '' pip_command = '{0} -m pipenv.vendor.pip9 install {4} {5} {6} {7} {3} {1} {2} --exists-action w'.format( quoted_python, @@ -1847,6 +1854,17 @@ def do_install( more_packages = list(more_packages) if package_name == '-e': package_name = ' '.join([package_name, more_packages.pop(0)]) + # capture indexes and extra indexes + line = [package_name] + more_packages + index_indicators = ['-i', '--index', '--extra-index-url'] + index, extra_indexes = None, None + if more_packages and any(more_packages[0].startswith(s) for s in index_indicators): + line, index = split_index(' '.join(line)) + line, extra_indexes = split_extra_index(line) + package_names = line.split() + package_name = package_names[0] + if len(package_names) > 1: + more_packages = package_names[1:] # Capture . argument and assign it to nothing if package_name == '.': package_name = False @@ -1926,6 +1944,8 @@ def do_install( verbose=verbose, pre=pre, requirements_dir=requirements_directory.name, + index=index, + extra_indexes=extra_indexes, ) # Warn if --editable wasn't passed. try: diff --git a/pipenv/project.py b/pipenv/project.py index d45a11e1..eed62a54 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -610,6 +610,12 @@ class Project(object): # pipfile is mutated! self.clear_pipfile_cache() + @property + def pipfile_sources(self): + if 'source' in self.parsed_pipfile: + return self.parsed_pipfile['source'] + return [DEFAULT_SOURCE] + @property def sources(self): if self.lockfile_exists: @@ -623,15 +629,35 @@ class Project(object): else: return [DEFAULT_SOURCE] - def get_source(self, name=None, url=None): - for source in self.sources: - if name: - if source.get('name') == name: - return source + def find_source(self, source): + """given a source, find it. + source can be a url or an index name. + """ + if not is_valid_url(source): + try: + source = self.get_source(name=source) + except SourceNotFound: + source = self.get_source(url=source) + else: + source = self.get_source(url=source) + return source + + def get_source(self, name=None, url=None): + def find_source(sources, name=None, url=None): + if name: + source = [s for s in sources if s.get('name') == name] elif url: - if source.get('url') in url: - return source + source = [s for s in sources if s.get('url') in url] + if source: + return source[0] + + found_source = find_source(self.sources, name=name, url=url) + if found_source: + return found_source + found_source = find_source(self.pipfile_sources, name=name, url=url) + if found_source: + return found_source raise SourceNotFound(name or url) def destroy_lockfile(self): diff --git a/pipenv/utils.py b/pipenv/utils.py index 897821a6..5e5b9027 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -686,6 +686,9 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): pip_src_args = [] if 'index' in deps[dep]: pip_src_args = [project.get_source(deps[dep]['index'])] + for idx in project.sources: + if idx['url'] != pip_src_args[0]['url']: + pip_src_args.append(idx) else: pip_src_args = project.sources pip_args = prepare_pip_source_args(pip_src_args) From fa299dbbb7c0aa50943472c87ea99b0a33f47b86 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 11 Apr 2018 22:32:55 -0400 Subject: [PATCH 05/17] add more envars to docs --- docs/advanced.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 364cea67..a82efa4a 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -387,13 +387,16 @@ will detect it. directory where the Pipfile is located, instruct pipenv to find the Pipfile in the location specified by this environment variable. - - ``PIPENV_CACHE_DIR`` — Location for Pipenv to store it's package cache. + - ``PIPENV_CACHE_DIR`` — Location for Pipenv to store it's package cache. This is useful in the + same situations that you would change ``PIP_CACHE_DIR``. - ``PIPENV_HIDE_EMOJIS`` — Disable emojis in output. - - ``PIPENV_DOTENV_LOCATION`` — Location for Pipenv to load your project's .env. + - ``PIPENV_DOTENV_LOCATION`` — Location of the project's .env file, only useful to set if it's not inthe same directory as the Pipfile. - - ``PIPENV_DONT_LOAD_ENV`` — Tell Pipenv not to load the .env files automatically. + - ``PIPENV_DONT_LOAD_ENV`` — Tell Pipenv not to load the .env file. + + - ``PIPENV_VENV_IN_PROJECT`` — Set to create the virtual environment folder in the project instead of the default location. If you'd like to set these environment variables on a per-project basis, I recommend utilizing the fantastic `direnv `_ project, in order to do so. From f91facd99f7eae38bdcedd9d0657e814291bd8bd Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 11 Apr 2018 23:24:51 -0400 Subject: [PATCH 06/17] add section explaining PIPENV_VENV_IN_PROJECT --- docs/advanced.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index a82efa4a..eb0e989e 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -396,8 +396,6 @@ will detect it. - ``PIPENV_DONT_LOAD_ENV`` — Tell Pipenv not to load the .env file. - - ``PIPENV_VENV_IN_PROJECT`` — Set to create the virtual environment folder in the project instead of the default location. - If you'd like to set these environment variables on a per-project basis, I recommend utilizing the fantastic `direnv `_ project, in order to do so. Also note that `pip itself supports environment variables `_, if you need additional customization. From 1a64a8d456cdde8ca72a2a8c3189fc9f8848d60e Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 11 Apr 2018 23:29:21 -0400 Subject: [PATCH 07/17] clarify wording on some envars --- docs/advanced.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index eb0e989e..364cea67 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -387,14 +387,13 @@ will detect it. directory where the Pipfile is located, instruct pipenv to find the Pipfile in the location specified by this environment variable. - - ``PIPENV_CACHE_DIR`` — Location for Pipenv to store it's package cache. This is useful in the - same situations that you would change ``PIP_CACHE_DIR``. + - ``PIPENV_CACHE_DIR`` — Location for Pipenv to store it's package cache. - ``PIPENV_HIDE_EMOJIS`` — Disable emojis in output. - - ``PIPENV_DOTENV_LOCATION`` — Location of the project's .env file, only useful to set if it's not inthe same directory as the Pipfile. + - ``PIPENV_DOTENV_LOCATION`` — Location for Pipenv to load your project's .env. - - ``PIPENV_DONT_LOAD_ENV`` — Tell Pipenv not to load the .env file. + - ``PIPENV_DONT_LOAD_ENV`` — Tell Pipenv not to load the .env files automatically. If you'd like to set these environment variables on a per-project basis, I recommend utilizing the fantastic `direnv `_ project, in order to do so. From e8efe3fd72e25e50a306fdbf6b87e8a3946b03dd Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Fri, 13 Apr 2018 20:39:48 -0400 Subject: [PATCH 08/17] Add encoding to jenkins test runner Signed-off-by: Dan Ryan --- run-tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 8e3eddde..37d39cf1 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -27,7 +27,7 @@ if [[ ! -z "$CI" ]]; then echo "Installing Pipenv…" - + pip uninstall -y pipenv pip install -e "$(pwd)" --upgrade pipenv install --deploy --system --dev @@ -62,8 +62,8 @@ fi # Use tap output if in a CI environment, otherwise just run the tests. if [[ "$TAP_OUTPUT" ]]; then - echo "$ pipenv run time pytest -v -n auto tests -m \"$TEST_SUITE\" --tap-stream | tee report-$PYTHON.tap" - pipenv run time pytest -v -n auto tests -m "$TEST_SUITE" --tap-stream | tee report.tap + echo "$ pipenv run time python -m pytest -v -n auto -m \"$TEST_SUITE\" --tap-stream tests/ | tee report-$PYTHON.tap" + pipenv run time python -m pytest -v -n auto -m "$TEST_SUITE" --tap-stream tests/ | tee report.tap else echo "$ pipenv run time pytest -v -n auto tests -m \"$TEST_SUITE\"" From f93b9f24287632e29285840ade6150c581d4cbda Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Fri, 13 Apr 2018 23:49:42 -0400 Subject: [PATCH 09/17] Add tests for project source changes Signed-off-by: Dan Ryan --- tests/integration/test_project.py | 106 ++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/integration/test_project.py diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py new file mode 100644 index 00000000..df241d6e --- /dev/null +++ b/tests/integration/test_project.py @@ -0,0 +1,106 @@ +# -*- coding=utf-8 -*- +import pytest +import os +from pipenv.project import Project + + +@pytest.mark.project +@pytest.mark.sources +def test_get_cached_source(PipenvInstance, pypi): + with PipenvInstance(pypi=pypi, chdir=True) as p: + + # Make sure unparseable packages don't wind up in the pipfile + # Escape $ for shell input + with open(p.pipfile_path, 'w') as f: + contents = """ +[[source]] +url = "{0}" +verify_ssl = false +name = "testindex" + +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = "true" +name = "pypi" + +[packages] +pytz = "*" +six = {{version = "*", index = "pypi"}} + +[dev-packages] + """.format(os.environ.get('PIPENV_TEST_INDEX')).strip() + f.write(contents) + c = p.pipenv('lock') + assert c.return_code == 0 + project = Project() + sources = [ + ['pypi', 'https://pypi.python.org/simple'], + ['testindex', os.environ.get('PIPENV_TEST_INDEX')] + ] + for src in sources: + name, url = src + source = [s for s in project.sources if s.get('name') == name] + assert source[0]['name'] == name + assert source[0]['url'] == url + source = project.get_source(name=name) + assert source['name'] == name + assert source['url'] == url + source = project.get_source(url=url) + assert source['name'] == name + assert source['url'] == url + source = project.find_source(name) + assert source['name'] == name + assert source['url'] == url + source = project.find_source(url) + assert source['name'] == name + assert source['url'] == url + + +@pytest.mark.project +@pytest.mark.sources +def test_get_uncached_source(PipenvInstance, pypi): + with PipenvInstance(pypi=pypi, chdir=True) as p: + + # Make sure unparseable packages don't wind up in the pipfile + # Escape $ for shell input + with open(p.pipfile_path, 'w') as f: + contents = """ +[[source]] +url = "{0}" +verify_ssl = false +name = "testindex" + +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = "true" +name = "pypi" + +[packages] +pytz = "*" +six = {{version = "*", index = "pypi"}} + +[dev-packages] + """.format(os.environ.get('PIPENV_TEST_INDEX')).strip() + f.write(contents) + project = Project() + sources = [ + ['pypi', 'https://pypi.python.org/simple'], + ['testindex', os.environ.get('PIPENV_TEST_INDEX')] + ] + for src in sources: + name, url = src + source = [s for s in project.pipfile_sources if s.get('name') == name] + assert source[0]['name'] == name + assert source[0]['url'] == url + source = project.get_source(name=name) + assert source['name'] == name + assert source['url'] == url + source = project.get_source(url=url) + assert source['name'] == name + assert source['url'] == url + source = project.find_source(name) + assert source['name'] == name + assert source['url'] == url + source = project.find_source(url) + assert source['name'] == name + assert source['url'] == url From c78628ba54d40239ae3c8c07780b2c0dcecd7948 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Sat, 14 Apr 2018 01:01:13 -0400 Subject: [PATCH 10/17] Add test coverage for all fixes Signed-off-by: Dan Ryan --- pipenv/core.py | 2 + tests/integration/test_install_uri.py | 31 +++++++++++++- tests/integration/test_lock.py | 61 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/pipenv/core.py b/pipenv/core.py index 9f79310d..a354d69e 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1865,6 +1865,8 @@ def do_install( package_name = package_names[0] if len(package_names) > 1: more_packages = package_names[1:] + else: + more_packages = [] # Capture . argument and assign it to nothing if package_name == '.': package_name = False diff --git a/tests/integration/test_install_uri.py b/tests/integration/test_install_uri.py index 50b56d10..f193a2e1 100644 --- a/tests/integration/test_install_uri.py +++ b/tests/integration/test_install_uri.py @@ -1,5 +1,5 @@ import pytest - +import os from flaky import flaky @@ -92,3 +92,32 @@ def test_install_editable_git_tag(PipenvInstance, pip_src_dir, pypi): assert 'git' in p.lockfile['default']['six'] assert p.lockfile['default']['six']['git'] == 'https://github.com/benjaminp/six.git' assert 'ref' in p.lockfile['default']['six'] + + +@pytest.mark.install +@pytest.mark.index +def test_install_named_index_alias(PipenvInstance, pypi): + # This uses the real PyPI since we need Internet to access the Git + # dependency anyway. + with PipenvInstance(pypi=pypi) as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[[source]] +url = "{0}" +verify_ssl = false +name = "testindex" + +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = "true" +name = "pypi" + +[packages] +pytz = "*" +six = {{version = "*", index = "pypi"}} + +[dev-packages] + """.format(os.environ.get('PIPENV_TEST_INDEX')).strip() + f.write(contents) + c = p.pipenv('install pytz --index pypi') + assert c.return_code == 0 diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index b157e12a..b31e2281 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -150,3 +150,64 @@ records = {extras = ["pandas"], version = "==0.5.2"} assert c.return_code == 0 assert 'tablib' in p.lockfile['default'] assert 'pandas' in p.lockfile['default'] + + +@pytest.mark.skip_lock +@pytest.mark.index +@pytest.mark.needs_internet +def test_private_index_skip_lock(PipenvInstance): + # This uses the real PyPI since we need Internet to access the Git + # dependency anyway. + with PipenvInstance() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[[source]] +url = "https://testpypi.python.org/pypi" +verify_ssl = true +name = "testpypi" + +[packages] +pipenv-test-private-package = {version = "*", index = "testpypi"} +requests = "*" + """.strip() + f.write(contents) + c = p.pipenv('install --skip-lock') + assert c.return_code == 0 + + +@pytest.mark.requirements +@pytest.mark.lock +@pytest.mark.index +@pytest.mark.needs_internet +def test_private_index_lock_requirements(PipenvInstance): + # This uses the real PyPI since we need Internet to access the Git + # dependency anyway. + with PipenvInstance() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[[source]] +url = "https://testpypi.python.org/pypi" +verify_ssl = true +name = "testpypi" + +[packages] +pipenv-test-private-package = {version = "*", index = "testpypi"} +requests = "*" + """.strip() + f.write(contents) + c = p.pipenv('install') + assert c.return_code == 0 + c = p.pipenv('lock -r') + assert c.return_code == 0 + assert '-i https://pypi.python.org/simple' in c.out.strip() + assert '--extra-index-url https://testpypi.python.org/pypi' in c.out.strip() From 8d20caf2942ec1f6d421ce193cb98374e9af5f4f Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Mon, 16 Apr 2018 20:45:58 -0400 Subject: [PATCH 11/17] Update test pypi index Signed-off-by: Dan Ryan --- tests/integration/test_lock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index b31e2281..7a779641 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -167,7 +167,7 @@ verify_ssl = true name = "pypi" [[source]] -url = "https://testpypi.python.org/pypi" +url = "https://test.pypi.org/simple" verify_ssl = true name = "testpypi" @@ -196,7 +196,7 @@ verify_ssl = true name = "pypi" [[source]] -url = "https://testpypi.python.org/pypi" +url = "https://test.pypi.org/simple" verify_ssl = true name = "testpypi" @@ -210,4 +210,4 @@ requests = "*" c = p.pipenv('lock -r') assert c.return_code == 0 assert '-i https://pypi.python.org/simple' in c.out.strip() - assert '--extra-index-url https://testpypi.python.org/pypi' in c.out.strip() + assert '--extra-index-url https://test.pypi.org/simple' in c.out.strip() From 81a2367e7518b9a48a7f7cea6653c8d8d47861ff Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Mon, 16 Apr 2018 22:11:27 -0400 Subject: [PATCH 12/17] Run private indexes in uncached tests Signed-off-by: Dan Ryan --- tests/integration/test_lock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index 7a779641..4b2c3527 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -155,6 +155,7 @@ records = {extras = ["pandas"], version = "==0.5.2"} @pytest.mark.skip_lock @pytest.mark.index @pytest.mark.needs_internet +@pytest.mark.install # private indexes need to be uncached for resolution def test_private_index_skip_lock(PipenvInstance): # This uses the real PyPI since we need Internet to access the Git # dependency anyway. @@ -183,6 +184,7 @@ requests = "*" @pytest.mark.requirements @pytest.mark.lock @pytest.mark.index +@pytest.mark.install # private indexes need to be uncached for resolution @pytest.mark.needs_internet def test_private_index_lock_requirements(PipenvInstance): # This uses the real PyPI since we need Internet to access the Git From b89a73c8957267a47034e48baba07d17c3b1f5d2 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Mon, 16 Apr 2018 23:53:50 -0400 Subject: [PATCH 13/17] Update tests Signed-off-by: Dan Ryan --- tests/integration/test_install_uri.py | 14 ++++++-------- tests/integration/test_lock.py | 5 +---- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/integration/test_install_uri.py b/tests/integration/test_install_uri.py index f193a2e1..5133e507 100644 --- a/tests/integration/test_install_uri.py +++ b/tests/integration/test_install_uri.py @@ -96,9 +96,8 @@ def test_install_editable_git_tag(PipenvInstance, pip_src_dir, pypi): @pytest.mark.install @pytest.mark.index +@pytest.mark.needs_internet def test_install_named_index_alias(PipenvInstance, pypi): - # This uses the real PyPI since we need Internet to access the Git - # dependency anyway. with PipenvInstance(pypi=pypi) as p: with open(p.pipfile_path, 'w') as f: contents = """ @@ -108,16 +107,15 @@ verify_ssl = false name = "testindex" [[source]] -url = "https://pypi.python.org/simple" +url = "https://test.pypi.org/simple" verify_ssl = "true" -name = "pypi" +name = "testpypi" [packages] -pytz = "*" -six = {{version = "*", index = "pypi"}} +six = * [dev-packages] - """.format(os.environ.get('PIPENV_TEST_INDEX')).strip() + """.format(os.environ['PIPENV_TEST_INDEX']).strip() f.write(contents) - c = p.pipenv('install pytz --index pypi') + c = p.pipenv('install pipenv-test-private-package --index testpypi') assert c.return_code == 0 diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index 4b2c3527..29d472e5 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -157,8 +157,6 @@ records = {extras = ["pandas"], version = "==0.5.2"} @pytest.mark.needs_internet @pytest.mark.install # private indexes need to be uncached for resolution def test_private_index_skip_lock(PipenvInstance): - # This uses the real PyPI since we need Internet to access the Git - # dependency anyway. with PipenvInstance() as p: with open(p.pipfile_path, 'w') as f: contents = """ @@ -187,8 +185,7 @@ requests = "*" @pytest.mark.install # private indexes need to be uncached for resolution @pytest.mark.needs_internet def test_private_index_lock_requirements(PipenvInstance): - # This uses the real PyPI since we need Internet to access the Git - # dependency anyway. + # Don't use the local fake pypi with PipenvInstance() as p: with open(p.pipfile_path, 'w') as f: contents = """ From c2462e3b23cf775a0196454240ad4e35cacfbaaa Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Tue, 17 Apr 2018 00:24:34 -0400 Subject: [PATCH 14/17] Refactor `split_index` and `split_extra_index` - Interpolate environment vars into pipfile sources - Use first to more efficiently handle list comprehensions Signed-off-by: Dan Ryan --- pipenv/core.py | 15 +++++++-------- pipenv/project.py | 29 ++++++++++++++++------------- pipenv/utils.py | 32 +++++++++++--------------------- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index a354d69e..39013c0f 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -46,8 +46,7 @@ from .utils import ( is_star, TemporaryDirectory, rmtree, - split_index, - split_extra_index, + split_argument, ) from .import pep508checker, progress from .environments import ( @@ -795,8 +794,8 @@ def do_install_dependencies( for dep, ignore_hash, block in deps_list_bar: if len(procs) < PIPENV_MAX_SUBPROCESS: # Use a specific index, if specified. - dep, index = split_index(dep) - dep, extra_index = split_extra_index(dep) + dep, index = split_argument(dep, short='i', long_='index') + dep, extra_index = split_argument(dep, long_='extra-index-url') # Install the module. c = pip_install( dep, @@ -827,8 +826,8 @@ def do_install_dependencies( failed_deps_list, label=INSTALL_LABEL2 ): # Use a specific index, if specified. - dep, index = split_index(dep) - dep, extra_index = split_extra_index(dep) + dep, index = split_argument(dep, short='i', long_='index') + dep, extra_index = split_argument(dep, long_='extra-index-url') # Install the module. c = pip_install( dep, @@ -1859,8 +1858,8 @@ def do_install( index_indicators = ['-i', '--index', '--extra-index-url'] index, extra_indexes = None, None if more_packages and any(more_packages[0].startswith(s) for s in index_indicators): - line, index = split_index(' '.join(line)) - line, extra_indexes = split_extra_index(line) + line, index = split_argument(' '.join(line), short='i', long_='index') + line, extra_indexes = split_argumetn(line, long_='extra-index-url') package_names = line.split() package_name = package_names[0] if len(package_names) > 1: diff --git a/pipenv/project.py b/pipenv/project.py index eed62a54..99bac0a3 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -7,6 +7,7 @@ import sys import base64 import hashlib import contoml +from first import first import pipfile import pipfile.api import toml @@ -613,7 +614,11 @@ class Project(object): @property def pipfile_sources(self): if 'source' in self.parsed_pipfile: - return self.parsed_pipfile['source'] + sources = [] + for s in self.parsed_pipfile['source']: + s['url'] = os.path.expandvars(s['url']) + sources.append(s) + return sources return [DEFAULT_SOURCE] @property @@ -645,19 +650,17 @@ class Project(object): def get_source(self, name=None, url=None): def find_source(sources, name=None, url=None): - if name: - source = [s for s in sources if s.get('name') == name] - elif url: + if url: source = [s for s in sources if s.get('url') in url] - if source: - return source[0] + else: + source = [s for s in sources if s.get('name') == name] + return first(source) found_source = find_source(self.sources, name=name, url=url) - if found_source: - return found_source - found_source = find_source(self.pipfile_sources, name=name, url=url) - if found_source: - return found_source + if not found_source: + found_source = find_source(self.pipfile_sources, name=name, url=url) + if found_source: + return found_source raise SourceNotFound(name or url) def destroy_lockfile(self): @@ -693,7 +696,7 @@ class Project(object): p = self.parsed_pipfile # Don't re-capitalize file URLs or VCSs. converted = convert_deps_from_pip(package_name) - converted = converted[[k for k in converted.keys()][0]] + converted = converted[first(k for k in converted.keys())] if not ( is_file(package_name) or is_vcs(converted) or 'path' in converted ): @@ -703,7 +706,7 @@ class Project(object): if key not in p: p[key] = {} package = convert_deps_from_pip(package_name) - package_name = [k for k in package.keys()][0] + package_name = first(k for k in package.keys()) name = self.get_package_name_in_pipfile(package_name, dev) if name and converted == '*': # Skip for wildcard version diff --git a/pipenv/utils.py b/pipenv/utils.py index 5e5b9027..8f43c788 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -1297,29 +1297,19 @@ class TemporaryDirectory(object): rmtree(self.name) -def split_index(req): - """Split an index argument from a requirement (finds None if not present) +def split_argument(req, short=None, long_=None): + """Split an argument from a string (finds None if not present). - returns req, index + Uses -short , --long , and --long=arg as permutations. + + returns string, index """ - index_entries = [' -i ', ' --index ', ' --index='] - index = None - index_entry = first([entry for entry in index_entries if entry in req]) - if index_entry: - req, index = req.split(index_entry) - remaining_line = index.split() - if len(remaining_line) > 1: - index, more_req = remaining_line[0], ' '.join(remaining_line[1:]) - req = '{0} {1}'.format(req, more_req) - return req, index - - -def split_extra_index(req): - """Split an extra index argument from a requirement (or None if not present) - - returns req, index - """ - index_entries = [' --extra-index-url ', ' --extra-index-url='] + index_entries = [] + if long_: + long_ = ' --{0}'.format(long_) + index_entries.extend(['{0}{1}'.format(long_, s) for s in [' ', '=']]) + if short: + index_entries.append(' -{0} '.format(short)) index = None index_entry = first([entry for entry in index_entries if entry in req]) if index_entry: From 7a8d01edc3b7e095cebfb018fc5f35ecaed80922 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Tue, 17 Apr 2018 01:16:49 -0400 Subject: [PATCH 15/17] Fix tests and index url fetching code per requests Signed-off-by: Dan Ryan --- pipenv/core.py | 2 +- pipenv/project.py | 19 +++++++----- tests/integration/test_install_uri.py | 12 ++++---- tests/integration/test_project.py | 43 ++++++++++----------------- 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index 39013c0f..0a960668 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1859,7 +1859,7 @@ def do_install( index, extra_indexes = None, None if more_packages and any(more_packages[0].startswith(s) for s in index_indicators): line, index = split_argument(' '.join(line), short='i', long_='index') - line, extra_indexes = split_argumetn(line, long_='extra-index-url') + line, extra_indexes = split_argument(line, long_='extra-index-url') package_names = line.split() package_name = package_names[0] if len(package_names) > 1: diff --git a/pipenv/project.py b/pipenv/project.py index 99bac0a3..4cdcdab3 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -650,17 +650,20 @@ class Project(object): def get_source(self, name=None, url=None): def find_source(sources, name=None, url=None): - if url: + source = None + if name: + source = [s for s in sources if s.get('name') == name] + elif url: source = [s for s in sources if s.get('url') in url] - else: - source = [s for s in sources if s.get('name') == name] - return first(source) + if source: + return first(source) found_source = find_source(self.sources, name=name, url=url) - if not found_source: - found_source = find_source(self.pipfile_sources, name=name, url=url) - if found_source: - return found_source + if found_source: + return found_source + found_source = find_source(self.pipfile_sources, name=name, url=url) + if found_source: + return found_source raise SourceNotFound(name or url) def destroy_lockfile(self): diff --git a/tests/integration/test_install_uri.py b/tests/integration/test_install_uri.py index 5133e507..b664718d 100644 --- a/tests/integration/test_install_uri.py +++ b/tests/integration/test_install_uri.py @@ -102,20 +102,20 @@ def test_install_named_index_alias(PipenvInstance, pypi): with open(p.pipfile_path, 'w') as f: contents = """ [[source]] -url = "{0}" -verify_ssl = false -name = "testindex" +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" [[source]] url = "https://test.pypi.org/simple" -verify_ssl = "true" +verify_ssl = true name = "testpypi" [packages] -six = * +six = "*" [dev-packages] - """.format(os.environ['PIPENV_TEST_INDEX']).strip() + """.strip() f.write(contents) c = p.pipenv('install pipenv-test-private-package --index testpypi') assert c.return_code == 0 diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index df241d6e..f3efdd2d 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -2,6 +2,7 @@ import pytest import os from pipenv.project import Project +import unittest @pytest.mark.project @@ -28,7 +29,7 @@ pytz = "*" six = {{version = "*", index = "pypi"}} [dev-packages] - """.format(os.environ.get('PIPENV_TEST_INDEX')).strip() + """.format(os.environ['PIPENV_TEST_INDEX']).strip() f.write(contents) c = p.pipenv('lock') assert c.return_code == 0 @@ -39,21 +40,15 @@ six = {{version = "*", index = "pypi"}} ] for src in sources: name, url = src - source = [s for s in project.sources if s.get('name') == name] - assert source[0]['name'] == name - assert source[0]['url'] == url - source = project.get_source(name=name) - assert source['name'] == name - assert source['url'] == url - source = project.get_source(url=url) - assert source['name'] == name - assert source['url'] == url - source = project.find_source(name) - assert source['name'] == name - assert source['url'] == url - source = project.find_source(url) + source = [s for s in project.pipfile_sources if s.get('name') == name] + assert source + source = source[0] assert source['name'] == name assert source['url'] == url + assert unittest.assertDictEqual(source, project.get_source(name=name)) + assert unittest.assertDictEqual(source, project.get_source(url=url)) + assert unittest.assertDictEqual(source, project.find_source(name)) + assert unittest.assertDictEqual(source, project.find_source(url)) @pytest.mark.project @@ -80,7 +75,7 @@ pytz = "*" six = {{version = "*", index = "pypi"}} [dev-packages] - """.format(os.environ.get('PIPENV_TEST_INDEX')).strip() + """.format(os.environ['PIPENV_TEST_INDEX']).strip() f.write(contents) project = Project() sources = [ @@ -90,17 +85,11 @@ six = {{version = "*", index = "pypi"}} for src in sources: name, url = src source = [s for s in project.pipfile_sources if s.get('name') == name] - assert source[0]['name'] == name - assert source[0]['url'] == url - source = project.get_source(name=name) - assert source['name'] == name - assert source['url'] == url - source = project.get_source(url=url) - assert source['name'] == name - assert source['url'] == url - source = project.find_source(name) - assert source['name'] == name - assert source['url'] == url - source = project.find_source(url) + assert source + source = source[0] assert source['name'] == name assert source['url'] == url + assert unittest.assertDictEqual(source, project.get_source(name=name)) + assert unittest.assertDictEqual(source, project.get_source(url=url)) + assert unittest.assertDictEqual(source, project.find_source(name)) + assert unittest.assertDictEqual(source, project.find_source(url)) From 31a4a75c9813196cfb6ee5dc0e2d68fba52d0ff9 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Tue, 17 Apr 2018 01:27:28 -0400 Subject: [PATCH 16/17] Sort items before comparison Signed-off-by: Dan Ryan --- tests/integration/test_project.py | 66 ++++++------------------------- 1 file changed, 11 insertions(+), 55 deletions(-) diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index f3efdd2d..4e8fe44a 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -2,16 +2,13 @@ import pytest import os from pipenv.project import Project -import unittest @pytest.mark.project @pytest.mark.sources -def test_get_cached_source(PipenvInstance, pypi): +@pytest.mark.parametrize('lock_first', [True, False]) +def test_get_source(PipenvInstance, pypi, lock_first): with PipenvInstance(pypi=pypi, chdir=True) as p: - - # Make sure unparseable packages don't wind up in the pipfile - # Escape $ for shell input with open(p.pipfile_path, 'w') as f: contents = """ [[source]] @@ -31,8 +28,11 @@ six = {{version = "*", index = "pypi"}} [dev-packages] """.format(os.environ['PIPENV_TEST_INDEX']).strip() f.write(contents) - c = p.pipenv('lock') - assert c.return_code == 0 + + if lock_first: + # force source to be cached + c = p.pipenv('lock') + assert c.return_code == 0 project = Project() sources = [ ['pypi', 'https://pypi.python.org/simple'], @@ -45,51 +45,7 @@ six = {{version = "*", index = "pypi"}} source = source[0] assert source['name'] == name assert source['url'] == url - assert unittest.assertDictEqual(source, project.get_source(name=name)) - assert unittest.assertDictEqual(source, project.get_source(url=url)) - assert unittest.assertDictEqual(source, project.find_source(name)) - assert unittest.assertDictEqual(source, project.find_source(url)) - - -@pytest.mark.project -@pytest.mark.sources -def test_get_uncached_source(PipenvInstance, pypi): - with PipenvInstance(pypi=pypi, chdir=True) as p: - - # Make sure unparseable packages don't wind up in the pipfile - # Escape $ for shell input - with open(p.pipfile_path, 'w') as f: - contents = """ -[[source]] -url = "{0}" -verify_ssl = false -name = "testindex" - -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = "true" -name = "pypi" - -[packages] -pytz = "*" -six = {{version = "*", index = "pypi"}} - -[dev-packages] - """.format(os.environ['PIPENV_TEST_INDEX']).strip() - f.write(contents) - project = Project() - sources = [ - ['pypi', 'https://pypi.python.org/simple'], - ['testindex', os.environ.get('PIPENV_TEST_INDEX')] - ] - for src in sources: - name, url = src - source = [s for s in project.pipfile_sources if s.get('name') == name] - assert source - source = source[0] - assert source['name'] == name - assert source['url'] == url - assert unittest.assertDictEqual(source, project.get_source(name=name)) - assert unittest.assertDictEqual(source, project.get_source(url=url)) - assert unittest.assertDictEqual(source, project.find_source(name)) - assert unittest.assertDictEqual(source, project.find_source(url)) + assert sorted(source.items()) == sorted(project.get_source(name=name).items()) + assert sorted(source.items()) == sorted(project.get_source(url=url).items()) + assert sorted(source.items()) == sorted(project.find_source(name).items()) + assert sorted(source.items()) == sorted(project.find_source(url).items()) From 88db0fe4eda3852e108f7b96d52da5fce7411eec Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Tue, 17 Apr 2018 12:28:59 -0400 Subject: [PATCH 17/17] Use `startswith` for url comparison in project Signed-off-by: Dan Ryan --- pipenv/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipenv/project.py b/pipenv/project.py index 4cdcdab3..effaa17e 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -654,7 +654,7 @@ class Project(object): if name: source = [s for s in sources if s.get('name') == name] elif url: - source = [s for s in sources if s.get('url') in url] + source = [s for s in sources if url.startswith(s.get('url'))] if source: return first(source)