Skip to content

Commit

Permalink
Merge pull request #3198 from s-t-e-v-e-n-k/issue3189
Browse files Browse the repository at this point in the history
Duplicate distributions with distinct extras
  • Loading branch information
dstufft committed Nov 23, 2015
2 parents e609509 + dc8e7f0 commit ef73f43
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 2 deletions.
8 changes: 7 additions & 1 deletion pip/req/req_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ def add_requirement(self, install_req, parent_req_name=None):
except KeyError:
existing_req = None
if (parent_req_name is None and existing_req and not
existing_req.constraint):
existing_req.constraint and
existing_req.extras == install_req.extras):
raise InstallationError(
'Double requirement given: %s (already in %s, name=%r)'
% (install_req, existing_req, name))
Expand All @@ -267,6 +268,11 @@ def add_requirement(self, install_req, parent_req_name=None):
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
existing_req.extras = tuple(
sorted(set(existing_req.extras).union(
set(install_req.extras))))
logger.debug("Setting %s extras to: %s" % (
existing_req, existing_req.extras))
# And now we need to scan this.
result = [existing_req]
# Canonicalise to the already-added object for the backref
Expand Down
1 change: 1 addition & 0 deletions tests/data/packages/LocalExtras-0.0.2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/LocalExtras-0.0.2.egg-info
Empty file.
30 changes: 30 additions & 0 deletions tests/data/packages/LocalExtras-0.0.2/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
from setuptools import setup, find_packages


def path_to_url(path):
"""
Convert a path to URI. The path will be made absolute and
will not have quoted path parts.
"""
path = os.path.normpath(os.path.abspath(path))
drive, path = os.path.splitdrive(path)
filepath = path.split(os.path.sep)
url = '/'.join(filepath)
if drive:
return 'file:///' + drive + url
return 'file://' +url


HERE = os.path.dirname(__file__)
DEP_PATH = os.path.join(HERE, '..', '..', 'indexes', 'simple', 'simple')
DEP_URL = path_to_url(DEP_PATH)

setup(
name='LocalExtras',
version='0.0.2',
packages=find_packages(),
install_requires=['simple==1.0'],
extras_require={ 'bar': ['simple==2.0'], 'baz': ['singlemodule'] },
dependency_links=[DEP_URL]
)
2 changes: 1 addition & 1 deletion tests/data/packages/LocalExtras/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ def path_to_url(path):
name='LocalExtras',
version='0.0.1',
packages=find_packages(),
extras_require={ 'bar': ['simple'] },
extras_require={ 'bar': ['simple'], 'baz': ['singlemodule'] },
dependency_links=[DEP_URL]
)
92 changes: 92 additions & 0 deletions tests/functional/test_install_reqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,95 @@ def test_double_install_spurious_hash_mismatch(script, tmpdir):
result = script.pip_install_local(
'-r', reqs_file.abspath, expect_error=False)
assert 'Successfully installed simple-1.0' in str(result)


def test_install_with_extras_from_constraints(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"file://%s#egg=LocalExtras[bar]" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras')
assert script.site_packages / 'simple' in result.files_created


def test_install_with_extras_from_install(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"file://%s#egg=LocalExtras" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]')
assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_with_extras_joined(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"file://%s#egg=LocalExtras[bar]" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]'
)
assert script.site_packages / 'simple' in result.files_created
assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_with_extras_editable_joined(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"-e file://%s#egg=LocalExtras[bar]" % to_install
)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]')
assert script.site_packages / 'simple' in result.files_created
assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_distribution_full_union(script, data):
to_install = data.packages.join("LocalExtras")
result = script.pip_install_local(
to_install, to_install + "[bar]", to_install + "[baz]")
assert 'Running setup.py install for LocalExtras' in result.stdout
assert script.site_packages / 'simple' in result.files_created
assert script.site_packages / 'singlemodule.py' in result.files_created


def test_install_distribution_duplicate_extras(script, data):
to_install = data.packages.join("LocalExtras")
package_name = to_install + "[bar]"
with pytest.raises(AssertionError):
result = script.pip_install_local(package_name, package_name)
assert 'Double requirement given: %s' % package_name in result.stderr


def test_install_distribution_union_with_constraints(script, data):
to_install = data.packages.join("LocalExtras")
script.scratch_path.join("constraints.txt").write(
"%s[bar]" % to_install)
result = script.pip_install_local(
'-c', script.scratch_path / 'constraints.txt', to_install + '[baz]')
assert 'Running setup.py install for LocalExtras' in result.stdout
assert script.site_packages / 'singlemodule.py' in result.files_created


def test_install_distribution_union_with_versions(script, data):
to_install_001 = data.packages.join("LocalExtras")
to_install_002 = data.packages.join("LocalExtras-0.0.2")
result = script.pip_install_local(
to_install_001 + "[bar]", to_install_002 + "[baz]")
assert ("Successfully installed LocalExtras-0.0.1 simple-3.0 " +
"singlemodule-0.0.1" in result.stdout)


@pytest.mark.xfail
def test_install_distribution_union_conflicting_extras(script, data):
# LocalExtras requires simple==1.0, LocalExtras[bar] requires simple==2.0;
# without a resolver, pip does not detect the conflict between simple==1.0
# and simple==2.0. Once a resolver is added, this conflict should be
# detected.
to_install = data.packages.join("LocalExtras-0.0.2")
result = script.pip_install_local(to_install, to_install + "[bar]",
expect_error=True)
assert 'installed' not in result.stdout
assert "Conflict" in result.stderr

0 comments on commit ef73f43

Please sign in to comment.