From 6d02c2fb5f95b2652acf39adacb8bac510b6c000 Mon Sep 17 00:00:00 2001 From: John Tyree Date: Wed, 27 Apr 2016 18:23:30 -0400 Subject: [PATCH 1/9] TST: add scenario tests for provides --- simplesat/tests/conflict_by_provides.yaml | 23 +++++++++++++++++++++++ simplesat/tests/multiple_provides.yaml | 16 ++++++++++++++++ simplesat/tests/simple_provides.yaml | 11 +++++++++++ simplesat/tests/test_scenarios_policy.py | 9 +++++++++ 4 files changed, 59 insertions(+) create mode 100644 simplesat/tests/conflict_by_provides.yaml create mode 100644 simplesat/tests/multiple_provides.yaml create mode 100644 simplesat/tests/simple_provides.yaml diff --git a/simplesat/tests/conflict_by_provides.yaml b/simplesat/tests/conflict_by_provides.yaml new file mode 100644 index 0000000..14bf188 --- /dev/null +++ b/simplesat/tests/conflict_by_provides.yaml @@ -0,0 +1,23 @@ + +packages: + - zumba 1.8.1-1; provides (dance) + - ballet 3.2.0-1; provides (dance) + - white_tornado 6.6.6-1; conflicts (dance) + +request: + - operation: "install" + requirement: "ballet" + - operation: "install" + requirement: "white_tornado" + + +failure: + requirements: ['ballet', 'white_tornado'] + raw: | + Conflicting requirements: + Requirements: 'ballet' + Install command rule (+ballet-3.2.0-1) + Requirements: 'white_tornado' <- 'dance' + white_tornado-6.6.6-1 conflicts with +ballet-3.2.0-1 + Requirements: 'white_tornado' + Install command rule (+white_tornado-6.6.6-1) diff --git a/simplesat/tests/multiple_provides.yaml b/simplesat/tests/multiple_provides.yaml new file mode 100644 index 0000000..3cac4c6 --- /dev/null +++ b/simplesat/tests/multiple_provides.yaml @@ -0,0 +1,16 @@ + +packages: + - meta 1.0-1; + - pil 1.0-1; provides (imaging) + - pillow 2.0-1; provides (imaging); conflicts (pil) + - pilfer 3.0-1; provides (imaging); conflicts (pil, pillow); install_requires (meta == 1.0-1) + +request: + - operation: "install" + requirement: "imaging" + +transaction: + - kind: install + package: meta 1.0-1 + - kind: install + package: pilfer 3.0-1 diff --git a/simplesat/tests/simple_provides.yaml b/simplesat/tests/simple_provides.yaml new file mode 100644 index 0000000..c8b29f2 --- /dev/null +++ b/simplesat/tests/simple_provides.yaml @@ -0,0 +1,11 @@ + +packages: + - zumba 1.8.1-1; provides (dance) + +request: + - operation: "install" + requirement: "dance" + +transaction: + - kind: "install" + package: "zumba 1.8.1-1" diff --git a/simplesat/tests/test_scenarios_policy.py b/simplesat/tests/test_scenarios_policy.py index 8a11d3b..3c9c0d1 100644 --- a/simplesat/tests/test_scenarios_policy.py +++ b/simplesat/tests/test_scenarios_policy.py @@ -154,6 +154,15 @@ def test_three_way_conflict(self): def test_conflicting_single_package_dependencies(self): self._check_solution("broken_metadata_self_conflict.yaml") + def test_simple_provides(self): + self._check_solution("simple_provides.yaml") + + def test_multiple_provides(self): + self._check_solution("multiple_provides.yaml") + + def test_conflict_by_provides(self): + self._check_solution("conflict_by_provides.yaml") + class TestInstallSet(ScenarioTestAssistant, TestCase): From e6b0dda664736cddab0beddc181526ffeb89102c Mon Sep 17 00:00:00 2001 From: John Tyree Date: Wed, 27 Apr 2016 15:51:10 -0400 Subject: [PATCH 2/9] ENH: support equality check on package metadata --- simplesat/package.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/simplesat/package.py b/simplesat/package.py index b6ab4fd..c2a74f5 100644 --- a/simplesat/package.py +++ b/simplesat/package.py @@ -124,10 +124,16 @@ def __hash__(self): return self._hash def __eq__(self, other): - return self._key == other._key + try: + return self._key == other._key + except AttributeError: + return NotImplemented def __ne__(self, other): - return self._key != other._key + try: + return self._key != other._key + except AttributeError: + return NotImplemented class RepositoryPackageMetadata(object): @@ -173,7 +179,13 @@ def __hash__(self): return self._hash def __eq__(self, other): - return self._key == other._key + try: + return self._key == other._key + except AttributeError: + return NotImplemented def __ne__(self, other): - return self._key != other._key + try: + return self._key != other._key + except AttributeError: + return NotImplemented From 2d94753979ece622362dc8bf4100855c4fccf63d Mon Sep 17 00:00:00 2001 From: John Tyree Date: Wed, 27 Apr 2016 15:54:34 -0400 Subject: [PATCH 3/9] ENH: Add provides to PackageMetadata --- simplesat/package.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/simplesat/package.py b/simplesat/package.py index c2a74f5..f829a8d 100644 --- a/simplesat/package.py +++ b/simplesat/package.py @@ -8,6 +8,7 @@ class ConstraintKinds(enum.Enum): install_requires = 'install_requires' conflicts = 'conflicts' + provides = 'provides' class IRepositoryInfo(six.with_metaclass(abc.ABCMeta)): @@ -62,7 +63,8 @@ def _from_pretty_string(cls, s): parser = PrettyPackageStringParser(EnpkgVersion.from_string) return parser.parse_to_package(s) - def __init__(self, name, version, install_requires=None, conflicts=None): + def __init__(self, name, version, install_requires=None, conflicts=None, + provides=None): """ Return a new PackageMetdata object. Parameters @@ -85,15 +87,24 @@ def __init__(self, name, version, install_requires=None, conflicts=None): (("MKL", ((">= 10.1", "< 11"),)), ("nose", (("*",),)), ("six", (("> 1.2", "<= 1.2.3"), (">= 1.2.5-2",))) - conflicts : tuple(tuple(str, tuple(tuple(str)))) A tuple of tuples mapping distribution names to disjunctions of conjunctions of version constraints. This works the same way as install_requires, but instead denotes packages that must *not* be installed with this package. + provides : iterable of package names + The packages that are provided by this distribution. Useful when + this does not match the package name. + + For example, a package ``foo`` is abandoned by its maintainer and a + fork ``bar`` is created to continue development. If ``bar`` is + intended to be a transparent replacement for ``foo``, then ``bar`` + `provides` ``foo``. + """ self._name = name + self._provides = tuple(provides or ()) self._version = version self._install_requires = install_requires or () self._conflicts = conflicts or () @@ -104,6 +115,12 @@ def __init__(self, name, version, install_requires=None, conflicts=None): def name(self): return self._name + @property + def provides(self): + constraint_str = "*" + this_pkg = ((self._name, ((constraint_str,),)),) + return this_pkg + self._provides + @property def version(self): return self._version @@ -153,6 +170,10 @@ def __init__(self, package, repository_info): def name(self): return self._package.name + @property + def provides(self): + return self._package.provides + @property def version(self): return self._package.version From 56b816627717c48adc62b44794473e28fe48870a Mon Sep 17 00:00:00 2001 From: John Tyree Date: Wed, 27 Apr 2016 16:00:48 -0400 Subject: [PATCH 4/9] ENH: support parsing provides clauses --- simplesat/constraints/package_parser.py | 18 ++- .../constraints/tests/test_package_parser.py | 111 ++++++++++++++++-- 2 files changed, 114 insertions(+), 15 deletions(-) diff --git a/simplesat/constraints/package_parser.py b/simplesat/constraints/package_parser.py index 99a40db..f850300 100644 --- a/simplesat/constraints/package_parser.py +++ b/simplesat/constraints/package_parser.py @@ -19,6 +19,7 @@ 'depends': ConstraintKinds.install_requires, 'install_requires': ConstraintKinds.install_requires, 'conflicts': ConstraintKinds.conflicts, + 'provides': ConstraintKinds.provides, } @@ -32,7 +33,7 @@ def parse(self, pretty_string): Pretty package strings are of the form:: - numpy 1.8.1-1; install_requires (MKL == 10.3, nose ^= 1.3.4); conflicts (numeric) # noqa + numpy 1.8.1-1; install_requires (MKL == 10.3, nose ^= 1.3.4); conflicts (numeric); provides (numeric) # noqa """ pretty_string = pretty_string.strip() pkg = {} @@ -91,22 +92,21 @@ def parse_to_package(self, package_string): return PackageMetadata(distribution, version, **pkg_dict) -def constraints_to_pretty_strings(install_requires): +def constraints_to_pretty_strings(constraint_tuples): """ Convert a sequence of constraint tuples as used in PackageMetadata to a list of pretty constraint strings. Parameters ---------- - install_requires : seq - Sequence of constraint tuples, e.g. ("MKL", (("< 11", ">= 10.1"),)) + constraint_tuples : tuple of constraint + Sequence of constraint tuples, e.g. (("MKL", (("< 11", ">= 10.1"),)),) """ flat_strings = [ "{} {}".format(dist, constraint_string).strip() - for dist, disjunction in install_requires + for dist, disjunction in constraint_tuples for conjunction in disjunction for constraint_string in conjunction ] - return flat_strings @@ -116,8 +116,14 @@ def package_to_pretty_string(package): constraint_kinds = ( (ConstraintKinds.install_requires, package.install_requires), (ConstraintKinds.conflicts, package.conflicts), + (ConstraintKinds.provides, package.provides), ) for constraint_kind, constraints in constraint_kinds: + # FIXME: perhaps 'provides' just shouldn't include the package name + if constraint_kind == ConstraintKinds.provides: + constraints = tuple((dist, disjunction) + for dist, disjunction in constraints + if dist != package.name) if len(constraints) > 0: string = ', '.join(constraints_to_pretty_strings(constraints)) template += "; {} ({})".format(constraint_kind.value, string) diff --git a/simplesat/constraints/tests/test_package_parser.py b/simplesat/constraints/tests/test_package_parser.py index b379abc..f72ec96 100644 --- a/simplesat/constraints/tests/test_package_parser.py +++ b/simplesat/constraints/tests/test_package_parser.py @@ -226,17 +226,21 @@ def test_complicated(self): # Given package_string = '; '.join(( - "bokeh 0.2.0-3", + "bokeh_git 0.2.0-3", "install_requires (zope *, numpy ^= 1.8.0, requests >= 0.2)", - "conflicts (requests ^= 0.2.5, requests > 0.4, bokeh_git)", + "conflicts (requests ^= 0.2.5, requests > 0.4, bokeh)", + "provides (webplot ^= 0.1, bokeh)", )) r_install_requires = ( ("numpy", (("^= 1.8.0",),)), ("requests", ((">= 0.2",),)), ("zope", (("*",),))) r_conflicts = ( - ("bokeh_git", (('',),)), + ("bokeh", (('',),)), ("requests", (("^= 0.2.5", "> 0.4"),))) + r_provides = ( + ("bokeh", (('',),)), + ("webplot", (("^= 0.1",),))) # When package = parse(package_string) @@ -244,12 +248,14 @@ def test_complicated(self): version = package['version'] install_requires = package['install_requires'] conflicts = package['conflicts'] + provides = package['provides'] # Then - self.assertEqual(name, "bokeh") + self.assertEqual(name, "bokeh_git") self.assertEqual(version, V("0.2.0-3")) self.assertEqual(install_requires, r_install_requires) self.assertEqual(conflicts, r_conflicts) + self.assertEqual(provides, r_provides) class TestPackagePrettyString(unittest.TestCase): @@ -315,6 +321,31 @@ def test_conflicts(self): # Then self.assertEqual(pretty_string, r_pretty_string) + def test_provides(self): + # Given + provides = ((u"dance", ((u">= 10.3-1",),)),) + package = PackageMetadata(u"zumba", V("1.8.1-1"), provides=provides) + + r_pretty_string = u"zumba 1.8.1-1; provides (dance >= 10.3-1)" + + # When + pretty_string = package_to_pretty_string(package) + + # Then + self.assertEqual(pretty_string, r_pretty_string) + + # Given + provides = (("cardio", (("*",),)),) + package = PackageMetadata(u"zumba", V("1.8.1-1"), provides=provides) + + r_pretty_string = "zumba 1.8.1-1; provides (cardio *)" + + # When + pretty_string = package_to_pretty_string(package) + + # Then + self.assertEqual(pretty_string, r_pretty_string) + def test_complicated(self): # Given install_requires = ( @@ -322,17 +353,21 @@ def test_complicated(self): ("requests", ((">= 0.2",),)), ("zope", (("*",),))) conflicts = ( - ("bokeh_git", (('',),)), + ("bokeh", (('',),)), ("requests", ((">= 0.2.5", "< 0.4"),))) + provides = ( + ("bokeh", (('*',),)),) package = PackageMetadata( - u"bokeh", V("0.2.0-3"), + u"bokeh_git", V("0.2.0-3"), install_requires=install_requires, - conflicts=conflicts) + conflicts=conflicts, + provides=provides) r_pretty_string = '; '.join(( - "bokeh 0.2.0-3", + "bokeh_git 0.2.0-3", "install_requires (numpy ^= 1.8.0, requests >= 0.2, zope *)", - "conflicts (bokeh_git, requests >= 0.2.5, requests < 0.4)", + "conflicts (bokeh, requests >= 0.2.5, requests < 0.4)", + "provides (bokeh *)", )) # When @@ -370,6 +405,64 @@ def test_with_depends(self): self.assertEqual(package.version, V('1.8.1')) self.assertEqual(package.install_requires, (("MKL", (("^= 10.3",),)),)) + def test_with_conflicts(self): + # Given + s = u"numpy 1.8.1; conflicts (MKL <= 10.3)" + parser = PrettyPackageStringParser(V) + + # When + package = parser.parse_to_package(s) + + # Then + self.assertEqual(package.name, "numpy") + self.assertEqual(package.version, V('1.8.1')) + self.assertEqual(package.conflicts, (("MKL", (("<= 10.3",),)),)) + + def test_with_provides(self): + # Given + s = u"numpy 1.8.1-4; provides (numeric)" + parser = PrettyPackageStringParser(V) + + # When + package = parser.parse_to_package(s) + + # Then + self.assertEqual(package.name, "numpy") + self.assertEqual(package.version, V('1.8.1-4')) + self.assertEqual(package.provides, + (('numpy', (('*',),)), + ("numeric", (("",),)))) + + def test_complicated(self): + # Given + install_requires = ( + ("numpy", (("^= 1.8.0",),)), + ("requests", ((">= 0.2",),)), + ("zope", (("*",),))) + conflicts = ( + ("bokeh", (('',),)), + ("requests", ((">= 0.2.5", "< 0.4"),))) + provides = ( + ("bokeh", (('*',),)),) + expected = PackageMetadata( + u"bokeh_git", V("0.2.0-3"), + install_requires=install_requires, + conflicts=conflicts, + provides=provides) + parser = PrettyPackageStringParser(V) + + # When + s = '; '.join(( + "bokeh_git 0.2.0-3", + "install_requires (numpy ^= 1.8.0, requests >= 0.2, zope *)", + "conflicts (bokeh, requests >= 0.2.5, requests < 0.4)", + "provides (bokeh *)", + )) + result = parser.parse_to_package(s) + + # Then + self.assertEqual(result, expected) + class TestParseScaryPackages(unittest.TestCase): From 34a6d7d96d683585e3259100aa858735325ed316 Mon Sep 17 00:00:00 2001 From: John Tyree Date: Wed, 27 Apr 2016 16:40:16 -0400 Subject: [PATCH 5/9] ENH: use provides metadata in what_provides --- simplesat/dependency_solver.py | 11 +++++++---- simplesat/pool.py | 9 +++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/simplesat/dependency_solver.py b/simplesat/dependency_solver.py index 06014d8..95b4eb8 100644 --- a/simplesat/dependency_solver.py +++ b/simplesat/dependency_solver.py @@ -296,13 +296,16 @@ def _connected_packages(solution, root_ids, pool): # Our strategy is as follows: # ... -> pkg.install_requires -> pkg names -> ids -> _id_to_package -> ... - def get_name(pkg_id): - return pool.id_to_package(abs(pkg_id)).name + def get_names(pkg_id): + provides = pool.id_to_package(abs(pkg_id)).provides + return tuple(name for name, _ in provides) - root_names = {get_name(pkg_id) for pkg_id in root_ids} + root_names = {name for pkg_id in root_ids for name in get_names(pkg_id)} solution_name_to_id = { - get_name(pkg_id): pkg_id for pkg_id in solution + name: pkg_id + for pkg_id in solution + for name in get_names(pkg_id) if pkg_id > 0 } diff --git a/simplesat/pool.py b/simplesat/pool.py index 747c8fc..b0a6c49 100644 --- a/simplesat/pool.py +++ b/simplesat/pool.py @@ -1,7 +1,9 @@ from __future__ import absolute_import from .utils import DefaultOrderedDict -from simplesat.constraints import modify_requirement +from simplesat.constraints import ( + ConstraintModifiers, Requirement, modify_requirement +) class Pool(object): @@ -47,7 +49,10 @@ def add_repository(self, repository): self._id += 1 self._id_to_package_[current_id] = package self._package_to_id_[package] = current_id - self._packages_by_name_[package.name].append(package) + for constraints in package.provides: + req = Requirement.from_constraints(constraints) + assert not req.has_any_version_constraint + self._packages_by_name_[req.name].append(package) def what_provides(self, requirement, use_modifiers=True): """ Computes the list of packages fulfilling the given From c2624324fc05f1b89980aeebd362e4f6f92dc2f1 Mon Sep 17 00:00:00 2001 From: John Tyree Date: Wed, 27 Apr 2016 16:58:29 -0400 Subject: [PATCH 6/9] BUG: there is no implicit obsolescence --- simplesat/rules_generator.py | 10 ++----- simplesat/tests/test_rules_generator.py | 38 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/simplesat/rules_generator.py b/simplesat/rules_generator.py index 17d5365..e900d49 100644 --- a/simplesat/rules_generator.py +++ b/simplesat/rules_generator.py @@ -22,7 +22,6 @@ class RuleType(enum.Enum): package_requires = 7 package_conflicts = 8 package_same_name = 10 - package_implicit_obsoletes = 11 package_installed = 12 package_broken = 100 @@ -364,7 +363,7 @@ def _add_conflicts_rules(self, package, requirements): Create rules for each of the known conflicts with `package`. """ - # Conflicts due to implicit obsoletion or same-name + # Conflicts due to same-name pkg_requirement = ConflictRequirement._from_string(package.name) obsolete_providers = self._pool.what_provides(pkg_requirement) # We add our new requirement to the stack of requirements we've @@ -374,11 +373,8 @@ def _add_conflicts_rules(self, package, requirements): if requirements is not None else None) for provider in obsolete_providers: - if provider != package: - if provider.name == package.name: - reason = RuleType.package_same_name - else: - reason = RuleType.package_implicit_obsoletes + if provider != package and provider.name == package.name: + reason = RuleType.package_same_name rule = self._create_conflicts_rule( package, provider, reason, combined_requirements) self._add_rule(rule, "package") diff --git a/simplesat/tests/test_rules_generator.py b/simplesat/tests/test_rules_generator.py index e5934fb..e3341d1 100644 --- a/simplesat/tests/test_rules_generator.py +++ b/simplesat/tests/test_rules_generator.py @@ -59,6 +59,44 @@ def test_prefer_installed(self): # Then self.assertIs(package, installed_repo_package) + def test_provides(self): + yaml = u""" + packages: + - A 1.0-1; provides (X) + - B 1.0-1; provides (X) + - C 1.0-1; provides (X) + - X 1.0-1 + - Z 1.0; depends (X) + + request: + - operation: "install" + requirement: "Z" + """ + + scenario = Scenario.from_yaml(io.StringIO(yaml)) + + # When + repos = list(scenario.remote_repositories) + repos.append(scenario.installed_repository) + pool = Pool(repos) + installed_package_ids = { + pool.package_id(p): p for p in scenario.installed_repository} + rules_generator = RulesGenerator( + pool, scenario.request, + installed_package_ids=installed_package_ids) + rules = list(rules_generator.iter_rules()) + + # Then + self.assertEqual(len(rules), 2) + + # Given/When + conflict = rules[0] + r_literals = (-5, 1, 2, 3, 4) + + # Then + self.assertEqual(conflict.reason, RuleType.package_requires) + self.assertEqual(conflict.literals, r_literals) + def test_conflicts(self): # Given yaml = u""" From f639e0f0ef2eb41b270e9e6fc3240bf985abeebf Mon Sep 17 00:00:00 2001 From: John Tyree Date: Thu, 28 Apr 2016 14:39:39 -0400 Subject: [PATCH 7/9] MAINT: fix provides in PackageMetadata docstring --- simplesat/package.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/simplesat/package.py b/simplesat/package.py index f829a8d..8544c70 100644 --- a/simplesat/package.py +++ b/simplesat/package.py @@ -77,7 +77,7 @@ def __init__(self, name, version, install_requires=None, conflicts=None, A tuple of tuples mapping distribution names to disjunctions of conjunctions of version constraints. - For example, a consider a package that depends on the following: + For example, consider a package that depends on the following: - nose - six (> 1.2, <= 1.2.3), or >= 1.2.5-2 Written as intervals, (1.2, 1.2.3] or [1.2.5-2, \infty) @@ -93,15 +93,17 @@ def __init__(self, name, version, install_requires=None, conflicts=None, This works the same way as install_requires, but instead denotes packages that must *not* be installed with this package. - provides : iterable of package names - The packages that are provided by this distribution. Useful when - this does not match the package name. + provides : tuple(tuple(str, tuple(tuple(str)))) + A tuple of tuples mapping package and virtual package names to + disjunctions of conjunctions of version constraints. - For example, a package ``foo`` is abandoned by its maintainer and a - fork ``bar`` is created to continue development. If ``bar`` is - intended to be a transparent replacement for ``foo``, then ``bar`` - `provides` ``foo``. + For example, consider a package ``numpy-nomkl`` which should be a + drop-in replacement for the normal ``numpy`` or ``numeric`` + packages. ``numpy-nomkl`` would have the following `provides`: + (("numpy", (("*",),)), ("numeric", (("*",),))) + At this time, no version constraint is permitted for names + specified in `provides`. """ self._name = name self._provides = tuple(provides or ()) From 552fb3518ef404782c7514645dbedd9589097048 Mon Sep 17 00:00:00 2001 From: John Tyree Date: Thu, 28 Apr 2016 14:41:15 -0400 Subject: [PATCH 8/9] TST: fix bad variable name in test --- simplesat/tests/test_rules_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simplesat/tests/test_rules_generator.py b/simplesat/tests/test_rules_generator.py index e3341d1..eea69dc 100644 --- a/simplesat/tests/test_rules_generator.py +++ b/simplesat/tests/test_rules_generator.py @@ -90,12 +90,12 @@ def test_provides(self): self.assertEqual(len(rules), 2) # Given/When - conflict = rules[0] + rule = rules[0] r_literals = (-5, 1, 2, 3, 4) # Then - self.assertEqual(conflict.reason, RuleType.package_requires) - self.assertEqual(conflict.literals, r_literals) + self.assertEqual(rule.reason, RuleType.package_requires) + self.assertEqual(rule.literals, r_literals) def test_conflicts(self): # Given From b10936053f77743813ffb57875a723606496d88c Mon Sep 17 00:00:00 2001 From: John Tyree Date: Fri, 29 Apr 2016 11:30:19 -0400 Subject: [PATCH 9/9] MAINT: fix exception when bad provides metadata Raise InvalidCosntraint instead of AssertionError --- simplesat/pool.py | 6 +++++- simplesat/tests/test_pool.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/simplesat/pool.py b/simplesat/pool.py index b0a6c49..5f58a61 100644 --- a/simplesat/pool.py +++ b/simplesat/pool.py @@ -4,6 +4,7 @@ from simplesat.constraints import ( ConstraintModifiers, Requirement, modify_requirement ) +from simplesat.errors import InvalidConstraint class Pool(object): @@ -51,7 +52,10 @@ def add_repository(self, repository): self._package_to_id_[package] = current_id for constraints in package.provides: req = Requirement.from_constraints(constraints) - assert not req.has_any_version_constraint + if req.has_any_version_constraint: + msg = ('Version constraints are not supported for' + ' package.provides metadata: {}') + raise InvalidConstraint(msg.format(req)) self._packages_by_name_[req.name].append(package) def what_provides(self, requirement, use_modifiers=True): diff --git a/simplesat/tests/test_pool.py b/simplesat/tests/test_pool.py index 3703f91..b261a14 100644 --- a/simplesat/tests/test_pool.py +++ b/simplesat/tests/test_pool.py @@ -1,10 +1,12 @@ import unittest +import re import six from okonomiyaki.versions import EnpkgVersion from simplesat.constraints import PrettyPackageStringParser, InstallRequirement +from simplesat.errors import InvalidConstraint from simplesat.repository import Repository from simplesat.request import Request @@ -144,3 +146,33 @@ def test_modification_what_provides(self): # Then self.assertEqual(result, expected) + + def test_reject_version_constraint_on_provides_metadata(self): + + # Given + constraints = ( + u"A > 1.8", + u"A >= 1.8", + u"A <= 1.8", + u"A < 1.8", + u"A ^= 1.8", + u"A == 1.8-1", + ) + + # When + for constraint in constraints: + repository = Repository(self.packages_from_definition( + u"numpy 1.9.2-1; provides ({})".format(constraint))) + constraint_re = re.escape(constraint) + + # Then + with self.assertRaisesRegexp(InvalidConstraint, constraint_re): + Pool([repository]) + + def test_accept_anyversion_constraint_on_provides_metadata(self): + # When + repository = Repository(self.packages_from_definition( + u"numpy 1.9.2-1; provides (A, B *)")) + + # Then + Pool([repository])