diff --git a/CHANGELOG.md b/CHANGELOG.md index a75fb98fb..50394ee44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- Fix resolver when toplevel requirements are also in pinned + subdependency (#450) + # 0.180.6 - Exclude irrelevant pip constraints (#417) diff --git a/pip_requ/resolver.py b/pip_requ/resolver.py index b64cc4144..4196dd308 100644 --- a/pip_requ/resolver.py +++ b/pip_requ/resolver.py @@ -3,6 +3,7 @@ unicode_literals) import os +import copy from functools import partial from itertools import chain, count @@ -157,7 +158,8 @@ def _group_constraints(self, constraints): continue ireqs = iter(ireqs) - combined_ireq = next(ireqs) + # deepcopy the accumulator so as to not modify the self.our_constraints invariant + combined_ireq = copy.deepcopy(next(ireqs)) combined_ireq.comes_from = None for ireq in ireqs: # NOTE we may be losing some info on dropped reqs here diff --git a/tests/conftest.py b/tests/conftest.py index a0d4f1047..9ca7da94b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ from pip_requ.repositories.base import BaseRepository from pip_requ.resolver import Resolver from pip_requ.utils import as_tuple, key_from_req, make_install_requirement +from pip_requ.exceptions import NoCandidateFound class FakeRepository(BaseRepository): @@ -31,7 +32,10 @@ def find_best_match(self, ireq, prereleases=False): if ireq.editable: return ireq - versions = ireq.specifier.filter(self.index[key_from_req(ireq.req)], prereleases=prereleases) + versions = list(ireq.specifier.filter(self.index[key_from_req(ireq.req)], + prereleases=prereleases)) + if not versions: + raise NoCandidateFound(ireq, self.index[key_from_req(ireq.req)]) best_version = max(versions, key=Version) return make_install_requirement(key_from_req(ireq.req), best_version, ireq.extras, constraint=ireq.constraint) diff --git a/tests/fixtures/fake-index.json b/tests/fixtures/fake-index.json index c04e574e9..f316688c9 100644 --- a/tests/fixtures/fake-index.json +++ b/tests/fixtures/fake-index.json @@ -4,20 +4,32 @@ }, "amqp": { "1.4.9": {"": []}, - "2.0.2": {"": ["vine>=1.1.1"]} + "2.0.2": {"": ["vine>=1.1.1"]}, + "2.1.4": {"": ["vine>=1.1.3"]} }, "arrow": { "0.5.0": {"": ["python-dateutil"]}, "0.5.4": {"": ["python-dateutil"]} }, "billiard": { - "3.3.0.23": {"": []} + "3.3.0.23": {"": []}, + "3.5.0.2": {"": []} }, "celery": { + "3.1.18": {"": [ + "kombu<3.1,>=3.0.25", + "pytz>dev", + "billiard<3.4,>=3.3.0.20" + ]}, "3.1.23": {"": [ - "kombu>=3.0.34", + "kombu>=3.0.34,<4", "pytz>dev", "billiard>=3.3.0.23" + ]}, + "4.0.2": {"": [ + "kombu<5.0,>=4.0.2", + "pytz>dev", + "billiard<3.6.0,>=3.5.0.2" ]} }, "click": { @@ -29,6 +41,11 @@ "1.7.7": {"": []}, "1.8": {"": []} }, + "fake-piptools-test-with-pinned-deps": { + "0.1": {"": [ + "celery==3.1.18" + ]} + }, "flask": { "0.10.1": {"": [ "Jinja2>=2.4", @@ -76,6 +93,9 @@ "3.0.35": {"": [ "anyjson>=0.3.3", "amqp>=1.4.9,<2.0" + ]}, + "4.0.2": {"": [ + "amqp<3.0,>=2.1.4" ]} }, "librabbitmq": { @@ -113,7 +133,8 @@ "3.2.2": {"": []} }, "vine": { - "1.1.1": {"": []} + "1.1.1": {"": []}, + "1.1.3": {"": []} }, "werkzeug": { "0.6": {"": []}, diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 9b8c850c9..c48f28503 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -69,15 +69,31 @@ # We must remove child dependencies from result if parent is removed (e.g. vine from amqp>=2.0) # See: GH-370 - (['celery', 'librabbitmq'], + # because of upated dependencies in the test index, we need to pin celery + # in order to reproduce vine removal (because it was readded in later releases) + (['celery<=3.1.23', 'librabbitmq'], [ - 'amqp==1.4.9', - 'anyjson==0.3.3', - 'billiard==3.3.0.23', - 'celery==3.1.23', - 'kombu==3.0.35', - 'librabbitmq==1.6.1', - 'pytz==2016.4'] + 'amqp==1.4.9', + 'anyjson==0.3.3', + 'billiard==3.5.0.2', + 'celery==3.1.23', + 'kombu==3.0.35', + 'librabbitmq==1.6.1', + 'pytz==2016.4'] + ), + + # Support specifying loose top-level requirements that could also appear as + # pinned subdependencies. + (['billiard', 'celery', + 'fake-piptools-test-with-pinned-deps'], + [ + 'amqp==1.4.9', + 'anyjson==0.3.3', + 'billiard==3.3.0.23', + 'celery==3.1.18', # this is pinned from test subdependency + 'fake-piptools-test-with-pinned-deps==0.1', + 'kombu==3.0.35', + 'pytz==2016.4'] ), # We shouldn't include irrelevant pip constraints