Commit 10c49e3f by Sébastien Eustace

Fix dependency resolution for Python-restricted dependencies

parent 10c3d0d5
......@@ -4,10 +4,12 @@ import re
from poetry.version.requirements import Requirement
from .dependency import Dependency
from .dependency_package import DependencyPackage
from .directory_dependency import DirectoryDependency
from .file_dependency import FileDependency
from .locker import Locker
from .package import Package
from .package_collection import PackageCollection
from .project_package import ProjectPackage
from .utils.link import Link
from .utils.utils import convert_markers
......
from .package import Package
class DependencyPackage:
def __init__(self, dependency, package):
self._dependency = dependency
self._package = package
@property
def dependency(self):
return self._dependency
@property
def package(self):
return self._package
def __getattr__(self, name):
return getattr(self._package, name)
def __str__(self):
return str(self._package)
def __repr__(self):
return repr(self._package)
def __hash__(self):
return hash(self._package)
def __eq__(self, other):
if isinstance(other, DependencyPackage):
other = other.package
return self._package == other
from .dependency_package import DependencyPackage
class PackageCollection(list):
def __init__(self, dependency, packages=None):
self._dependency = dependency
if packages is None:
packages = []
super(PackageCollection, self).__init__()
for package in packages:
self.append(package)
def append(self, package):
if not isinstance(package, DependencyPackage):
package = DependencyPackage(self._dependency, package)
return super(PackageCollection, self).append(package)
......@@ -12,9 +12,11 @@ from tempfile import mkdtemp
from typing import List
from poetry.packages import Dependency
from poetry.packages import DependencyPackage
from poetry.packages import DirectoryDependency
from poetry.packages import FileDependency
from poetry.packages import Package
from poetry.packages import PackageCollection
from poetry.packages import VCSDependency
from poetry.packages import dependency_from_pep_508
......@@ -101,10 +103,10 @@ class Provider:
order, so the latest version ought to be last.
"""
if dependency.is_root:
return [self._package]
return PackageCollection(dependency, [self._package])
if dependency in self._search_for:
return self._search_for[dependency]
return PackageCollection(dependency, self._search_for[dependency])
if dependency.is_vcs():
packages = self.search_for_vcs(dependency)
......@@ -132,7 +134,7 @@ class Provider:
self._search_for[dependency] = packages
return self._search_for[dependency]
return PackageCollection(dependency, self._search_for[dependency])
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package]
"""
......@@ -314,6 +316,41 @@ class Provider:
if not package.python_constraint.allows_all(
self._package.python_constraint
):
# The package Python requirement is not compatible
# with the root package python requirement.
#
# However, it should be accepted if it comes from
# a dependency with a compatible Python constraint.
#
# An example of this is:
# - The root package is compatible with Python ~2.7 || ^3.6
# - The root package depends on black for Python ^3.6
# - black is only compatible with Python >=3.6
# - black should be authorized.
#
# In this particular case, we notify the resolver that it needs
# to branch the dependency tree. What this means is if we have
# root Python ~2.7 || ^3.6 and dependency Python >=3.6
# we have to resolve for ^3.6 (>=3.6, <4.0)
if (
not package.dependency.python_constraint.is_any()
and not package.python_constraint.intersect(
package.dependency.python_constraint
).is_empty()
):
self.debug(
"<warning>Found conditional dependency for {} (Python {}).</warning>".format(
package, package.dependency.python_constraint
)
)
raise CompatibilityError(
str(
package.python_constraint.intersect(
package.dependency.python_constraint
)
)
)
return [
Incompatibility(
[Term(package.to_dependency(), True)],
......@@ -354,8 +391,11 @@ class Provider:
return package
if package.source_type not in {"directory", "file", "git"}:
package = self._pool.package(
package = DependencyPackage(
package.dependency,
self._pool.package(
package.name, package.version.text, extras=package.requires_extras
),
)
dependencies = [
......
......@@ -2,6 +2,7 @@ from typing import List
from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage
from poetry.packages.constraints.generic_constraint import GenericConstraint
from poetry.semver import parse_constraint
......@@ -84,7 +85,7 @@ class Solver:
def solve_in_compatibility_mode(self, constraints, use_latest=None):
locked = {}
for package in self._locked.packages:
locked[package.name] = package
locked[package.name] = DependencyPackage(package.to_dependency(), package)
packages = []
depths = []
......@@ -114,7 +115,7 @@ class Solver:
def _solve(self, use_latest=None):
locked = {}
for package in self._locked.packages:
locked[package.name] = package
locked[package.name] = DependencyPackage(package.to_dependency(), package)
try:
result = resolve_version(
......@@ -217,7 +218,9 @@ class Solver:
break
for pkg in packages:
if pkg.name == dependency.name:
if pkg.name == dependency.name and dependency.constraint.allows(
pkg.version
):
# If there is already a child with this name
# we merge the requirements
existing = None
......
......@@ -22,7 +22,7 @@ classifiers = [
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
python = "~2.7.9 || ^3.4"
cleo = "^0.6.7"
requests = "^2.18"
cachy = "^0.2"
......
from poetry.packages import DependencyPackage
from poetry.packages import Package
from poetry.mixology.failure import SolveFailure
from poetry.mixology.version_solver import VersionSolver
......@@ -18,6 +19,9 @@ def add_to_repo(repository, name, version, deps=None, python=None):
def check_solver_result(
root, provider, result=None, error=None, tries=None, locked=None, use_latest=None
):
if locked is not None:
locked = {k: DependencyPackage(l.to_dependency(), l) for k, l in locked.items()}
solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest)
try:
......
......@@ -785,7 +785,7 @@ def test_solver_duplicate_dependencies_same_constraint(solver, repo, package):
)
op = ops[0]
assert op.package.requirements == {"python": ">=2.7,<2.8 || >=3.4"}
assert op.package.requirements == {"python": "2.7 || >=3.4"}
def test_solver_duplicate_dependencies_different_constraints(solver, repo, package):
......@@ -962,3 +962,19 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
{"job": "install", "package": get_package("demo", "0.1.2")},
],
)
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible(
solver, repo, package
):
package.python_versions = "~2.7 || ^3.6"
package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
package_a = get_package("A", "1.0.0")
package_a.python_versions = ">=3.6"
repo.add_package(package_a)
ops = solver.solve()
check_solver_result(ops, [{"job": "install", "package": package_a}])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment