Merge pull request #1980 from pypa/1973-extra-index-urls

Add `--extra-index-url` from all extra indexes
This commit is contained in:
Dan Ryan
2018-04-17 14:20:21 -04:00
committed by GitHub
7 changed files with 292 additions and 68 deletions
+81 -54
View File
@@ -18,12 +18,12 @@ import delegator
from .vendor import pexpect
import pipfile
from blindspin import spinner
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,
@@ -46,6 +46,7 @@ from .utils import (
is_star,
TemporaryDirectory,
rmtree,
split_argument,
)
from .import pep508checker, progress
from .environments import (
@@ -775,7 +776,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)
@@ -790,12 +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.
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_argument(dep, short='i', long_='index')
dep, extra_index = split_argument(dep, long_='extra-index-url')
# Install the module.
c = pip_install(
dep,
@@ -806,6 +806,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 +825,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_argument(dep, short='i', long_='index')
dep, extra_index = split_argument(dep, long_='extra-index-url')
# Install the module.
c = pip_install(
dep,
@@ -839,6 +837,7 @@ def do_install_dependencies(
verbose=verbose,
index=index,
requirements_dir=requirements_dir,
extra_indexes=extra_index,
)
# The Installation failed...
if c.return_code != 0:
@@ -1367,6 +1366,7 @@ def pip_install(
pre=False,
selective_upgrade=False,
requirements_dir=None,
extra_indexes=None,
):
import pip9
@@ -1415,51 +1415,63 @@ def pip_install(
src = ''
else:
src = ''
# Try installing for each source in project.sources.
if index:
if not is_valid_url(index):
index = project.find_source(index).get('url')
sources = [{'url': index}]
if 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
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:
sources = project.pipfile_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
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)
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
@@ -1841,6 +1853,19 @@ 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_argument(' '.join(line), short='i', long_='index')
line, extra_indexes = split_argument(line, long_='extra-index-url')
package_names = line.split()
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
@@ -1920,6 +1945,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:
+41 -10
View File
@@ -6,8 +6,8 @@ import re
import sys
import base64
import hashlib
import contoml
from first import first
import pipfile
import pipfile.api
import toml
@@ -611,6 +611,16 @@ class Project(object):
# pipfile is mutated!
self.clear_pipfile_cache()
@property
def pipfile_sources(self):
if 'source' in self.parsed_pipfile:
sources = []
for s in self.parsed_pipfile['source']:
s['url'] = os.path.expandvars(s['url'])
sources.append(s)
return sources
return [DEFAULT_SOURCE]
@property
def sources(self):
if self.lockfile_exists:
@@ -624,15 +634,36 @@ 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):
source = 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 url.startswith(s.get('url'))]
if source:
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
raise SourceNotFound(name or url)
def destroy_lockfile(self):
@@ -668,7 +699,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
):
@@ -678,7 +709,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
+28
View File
@@ -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:
@@ -685,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)
@@ -1291,3 +1295,27 @@ class TemporaryDirectory(object):
def cleanup(self):
if self._finalizer.detach():
rmtree(self.name)
def split_argument(req, short=None, long_=None):
"""Split an argument from a string (finds None if not present).
Uses -short <arg>, --long <arg>, and --long=arg as permutations.
returns string, index
"""
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:
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
+3 -3
View File
@@ -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\""
+28 -1
View File
@@ -1,5 +1,5 @@
import pytest
import os
from flaky import flaky
@@ -92,3 +92,30 @@ 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
@pytest.mark.needs_internet
def test_install_named_index_alias(PipenvInstance, pypi):
with PipenvInstance(pypi=pypi) 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://test.pypi.org/simple"
verify_ssl = true
name = "testpypi"
[packages]
six = "*"
[dev-packages]
""".strip()
f.write(contents)
c = p.pipenv('install pipenv-test-private-package --index testpypi')
assert c.return_code == 0
+60
View File
@@ -150,3 +150,63 @@ 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
@pytest.mark.install # private indexes need to be uncached for resolution
def test_private_index_skip_lock(PipenvInstance):
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://test.pypi.org/simple"
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.install # private indexes need to be uncached for resolution
@pytest.mark.needs_internet
def test_private_index_lock_requirements(PipenvInstance):
# Don't use the local fake pypi
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://test.pypi.org/simple"
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://test.pypi.org/simple' in c.out.strip()
+51
View File
@@ -0,0 +1,51 @@
# -*- coding=utf-8 -*-
import pytest
import os
from pipenv.project import Project
@pytest.mark.project
@pytest.mark.sources
@pytest.mark.parametrize('lock_first', [True, False])
def test_get_source(PipenvInstance, pypi, lock_first):
with PipenvInstance(pypi=pypi, chdir=True) 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['PIPENV_TEST_INDEX']).strip()
f.write(contents)
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'],
['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 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())