Commit d6289470 by Sébastien Eustace Committed by GitHub

Use the proper main Python constraint when resolving for installation (#2625)

parent feef53be
...@@ -18,6 +18,7 @@ from poetry.core.packages import Package ...@@ -18,6 +18,7 @@ from poetry.core.packages import Package
from poetry.core.packages import URLDependency from poetry.core.packages import URLDependency
from poetry.core.packages import VCSDependency from poetry.core.packages import VCSDependency
from poetry.core.packages.utils.utils import get_python_constraint_from_marker from poetry.core.packages.utils.utils import get_python_constraint_from_marker
from poetry.core.semver.version import Version
from poetry.core.vcs.git import Git from poetry.core.vcs.git import Git
from poetry.core.version.markers import MarkerUnion from poetry.core.version.markers import MarkerUnion
from poetry.inspection.info import PackageInfo from poetry.inspection.info import PackageInfo
...@@ -80,12 +81,15 @@ class Provider: ...@@ -80,12 +81,15 @@ class Provider:
@contextmanager @contextmanager
def use_environment(self, env): # type: (Env) -> Provider def use_environment(self, env): # type: (Env) -> Provider
original_env = self._env original_env = self._env
original_python_constraint = self._python_constraint
self._env = env self._env = env
self._python_constraint = Version.parse(env.marker_env["python_full_version"])
yield self yield self
self._env = original_env self._env = original_env
self._python_constraint = original_python_constraint
def search_for(self, dependency): # type: (Dependency) -> List[Package] def search_for(self, dependency): # type: (Dependency) -> List[Package]
""" """
...@@ -380,9 +384,7 @@ class Provider: ...@@ -380,9 +384,7 @@ class Provider:
else: else:
dependencies = package.requires dependencies = package.requires
if not package.python_constraint.allows_all( if not package.python_constraint.allows_all(self._python_constraint):
self._package.python_constraint
):
transitive_python_constraint = get_python_constraint_from_marker( transitive_python_constraint = get_python_constraint_from_marker(
package.dependency.transitive_marker package.dependency.transitive_marker
) )
...@@ -392,7 +394,7 @@ class Provider: ...@@ -392,7 +394,7 @@ class Provider:
difference = transitive_python_constraint.difference(intersection) difference = transitive_python_constraint.difference(intersection)
if ( if (
transitive_python_constraint.is_any() transitive_python_constraint.is_any()
or self._package.python_constraint.intersect( or self._python_constraint.intersect(
package.dependency.python_constraint package.dependency.python_constraint
).is_empty() ).is_empty()
or intersection.is_empty() or intersection.is_empty()
...@@ -402,7 +404,7 @@ class Provider: ...@@ -402,7 +404,7 @@ class Provider:
Incompatibility( Incompatibility(
[Term(package.to_dependency(), True)], [Term(package.to_dependency(), True)],
PythonCause( PythonCause(
package.python_versions, self._package.python_versions package.python_versions, str(self._python_constraint)
), ),
) )
] ]
...@@ -411,7 +413,7 @@ class Provider: ...@@ -411,7 +413,7 @@ class Provider:
dep dep
for dep in dependencies for dep in dependencies
if dep.name not in self.UNSAFE_PACKAGES if dep.name not in self.UNSAFE_PACKAGES
and self._package.python_constraint.allows_any(dep.python_constraint) and self._python_constraint.allows_any(dep.python_constraint)
and (not self._env or dep.marker.validate(self._env.marker_env)) and (not self._env or dep.marker.validate(self._env.marker_env))
] ]
...@@ -477,7 +479,7 @@ class Provider: ...@@ -477,7 +479,7 @@ class Provider:
_dependencies = [ _dependencies = [
r r
for r in requires for r in requires
if self._package.python_constraint.allows_any(r.python_constraint) if self._python_constraint.allows_any(r.python_constraint)
and r.name not in self.UNSAFE_PACKAGES and r.name not in self.UNSAFE_PACKAGES
and (not self._env or r.marker.validate(self._env.marker_env)) and (not self._env or r.marker.validate(self._env.marker_env))
] ]
......
...@@ -4,6 +4,7 @@ from contextlib import contextmanager ...@@ -4,6 +4,7 @@ from contextlib import contextmanager
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Optional
from clikit.io import ConsoleIO from clikit.io import ConsoleIO
...@@ -33,14 +34,19 @@ class Solver: ...@@ -33,14 +34,19 @@ class Solver:
installed, # type: Repository installed, # type: Repository
locked, # type: Repository locked, # type: Repository
io, # type: ConsoleIO io, # type: ConsoleIO
remove_untracked=False, # type: bool remove_untracked=False, # type: bool,
provider=None, # type: Optional[Provider]
): ):
self._package = package self._package = package
self._pool = pool self._pool = pool
self._installed = installed self._installed = installed
self._locked = locked self._locked = locked
self._io = io self._io = io
self._provider = Provider(self._package, self._pool, self._io)
if provider is None:
provider = Provider(self._package, self._pool, self._io)
self._provider = provider
self._overrides = [] self._overrides = []
self._remove_untracked = remove_untracked self._remove_untracked = remove_untracked
......
...@@ -1238,6 +1238,7 @@ class MockEnv(NullEnv): ...@@ -1238,6 +1238,7 @@ class MockEnv(NullEnv):
marker_env["python_implementation"] = self._python_implementation marker_env["python_implementation"] = self._python_implementation
marker_env["version_info"] = self._version_info marker_env["version_info"] = self._version_info
marker_env["python_version"] = ".".join(str(v) for v in self._version_info[:2]) marker_env["python_version"] = ".".join(str(v) for v in self._version_info[:2])
marker_env["python_full_version"] = ".".join(str(v) for v in self._version_info)
marker_env["sys_platform"] = self._platform marker_env["sys_platform"] = self._platform
return marker_env return marker_env
......
...@@ -3,11 +3,17 @@ import pytest ...@@ -3,11 +3,17 @@ import pytest
from clikit.io import NullIO from clikit.io import NullIO
from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.project_package import ProjectPackage
from poetry.puzzle.provider import Provider from poetry.puzzle.provider import Provider as BaseProvider
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
class Provider(BaseProvider):
def set_package_python_versions(self, python_versions):
self._package.python_versions = python_versions
self._python_constraint = self._package.python_constraint
@pytest.fixture @pytest.fixture
def repo(): def repo():
return Repository() return Repository()
......
...@@ -3,12 +3,12 @@ from ..helpers import check_solver_result ...@@ -3,12 +3,12 @@ from ..helpers import check_solver_result
def test_dependency_does_not_match_root_python_constraint(root, provider, repo): def test_dependency_does_not_match_root_python_constraint(root, provider, repo):
root.python_versions = "^3.6" provider.set_package_python_versions("^3.6")
root.add_dependency("foo", "*") root.add_dependency("foo", "*")
add_to_repo(repo, "foo", "1.0.0", python="<3.5") add_to_repo(repo, "foo", "1.0.0", python="<3.5")
error = """The current project's Python requirement (^3.6) \ error = """The current project's Python requirement (>=3.6,<4.0) \
is not compatible with some of the required packages Python requirement: is not compatible with some of the required packages Python requirement:
- foo requires Python <3.5, so it will not be satisfied for Python >=3.6,<4.0 - foo requires Python <3.5, so it will not be satisfied for Python >=3.6,<4.0
......
...@@ -7,10 +7,12 @@ from poetry.core.packages import dependency_from_pep_508 ...@@ -7,10 +7,12 @@ from poetry.core.packages import dependency_from_pep_508
from poetry.core.version.markers import parse_marker from poetry.core.version.markers import parse_marker
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.puzzle.exceptions import SolverProblemError from poetry.puzzle.exceptions import SolverProblemError
from poetry.puzzle.provider import Provider as BaseProvider
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
from tests.helpers import get_dependency from tests.helpers import get_dependency
from tests.helpers import get_package from tests.helpers import get_package
from tests.repositories.test_legacy_repository import ( from tests.repositories.test_legacy_repository import (
...@@ -19,6 +21,12 @@ from tests.repositories.test_legacy_repository import ( ...@@ -19,6 +21,12 @@ from tests.repositories.test_legacy_repository import (
from tests.repositories.test_pypi_repository import MockRepository as MockPyPIRepository from tests.repositories.test_pypi_repository import MockRepository as MockPyPIRepository
class Provider(BaseProvider):
def set_package_python_versions(self, python_versions):
self._package.python_versions = python_versions
self._python_constraint = self._package.python_constraint
@pytest.fixture() @pytest.fixture()
def io(): def io():
return NullIO() return NullIO()
...@@ -51,7 +59,9 @@ def pool(repo): ...@@ -51,7 +59,9 @@ def pool(repo):
@pytest.fixture() @pytest.fixture()
def solver(package, pool, installed, locked, io): def solver(package, pool, installed, locked, io):
return Solver(package, pool, installed, locked, io) return Solver(
package, pool, installed, locked, io, provider=Provider(package, pool, io)
)
def check_solver_result(ops, expected): def check_solver_result(ops, expected):
...@@ -295,7 +305,7 @@ def test_solver_sets_categories(solver, repo, package): ...@@ -295,7 +305,7 @@ def test_solver_sets_categories(solver, repo, package):
def test_solver_respects_root_package_python_versions(solver, repo, package): def test_solver_respects_root_package_python_versions(solver, repo, package):
package.python_versions = "~3.4" solver.provider.set_package_python_versions("~3.4")
package.add_dependency("A") package.add_dependency("A")
package.add_dependency("B") package.add_dependency("B")
...@@ -326,7 +336,7 @@ def test_solver_respects_root_package_python_versions(solver, repo, package): ...@@ -326,7 +336,7 @@ def test_solver_respects_root_package_python_versions(solver, repo, package):
def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package): def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package):
package.python_versions = "^3.4" solver.provider.set_package_python_versions("^3.4")
package.add_dependency("A") package.add_dependency("A")
package.add_dependency("B") package.add_dependency("B")
...@@ -346,7 +356,7 @@ def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package): ...@@ -346,7 +356,7 @@ def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package):
def test_solver_solves_optional_and_compatible_packages(solver, repo, package): def test_solver_solves_optional_and_compatible_packages(solver, repo, package):
package.python_versions = "~3.4" solver.provider.set_package_python_versions("~3.4")
package.extras["foo"] = [get_dependency("B")] package.extras["foo"] = [get_dependency("B")]
package.add_dependency("A", {"version": "*", "python": "^3.4"}) package.add_dependency("A", {"version": "*", "python": "^3.4"})
package.add_dependency("B", {"version": "*", "optional": True}) package.add_dependency("B", {"version": "*", "optional": True})
...@@ -563,7 +573,7 @@ def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package ...@@ -563,7 +573,7 @@ def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package
def test_solver_sub_dependencies_with_not_supported_python_version( def test_solver_sub_dependencies_with_not_supported_python_version(
solver, repo, package solver, repo, package
): ):
package.python_versions = "^3.5" solver.provider.set_package_python_versions("^3.5")
package.add_dependency("A") package.add_dependency("A")
package_a = get_package("A", "1.0") package_a = get_package("A", "1.0")
...@@ -583,7 +593,7 @@ def test_solver_sub_dependencies_with_not_supported_python_version( ...@@ -583,7 +593,7 @@ def test_solver_sub_dependencies_with_not_supported_python_version(
def test_solver_with_dependency_in_both_main_and_dev_dependencies( def test_solver_with_dependency_in_both_main_and_dev_dependencies(
solver, repo, package solver, repo, package
): ):
package.python_versions = "^3.5" solver.provider.set_package_python_versions("^3.5")
package.add_dependency("A") package.add_dependency("A")
package.add_dependency("A", {"version": "*", "extras": ["foo"]}, category="dev") package.add_dependency("A", {"version": "*", "extras": ["foo"]}, category="dev")
...@@ -962,7 +972,7 @@ def test_solver_can_resolve_git_dependencies_with_ref(solver, repo, package, ref ...@@ -962,7 +972,7 @@ def test_solver_can_resolve_git_dependencies_with_ref(solver, repo, package, ref
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible( def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.4" solver.provider.set_package_python_versions("~2.7 || ^3.4")
package.add_dependency("A", {"version": "^1.0", "python": "^3.6"}) package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
package_a = get_package("A", "1.0.0") package_a = get_package("A", "1.0.0")
...@@ -978,7 +988,7 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir ...@@ -978,7 +988,7 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible_multiple( def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible_multiple(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.4" solver.provider.set_package_python_versions("~2.7 || ^3.4")
package.add_dependency("A", {"version": "^1.0", "python": "^3.6"}) package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
package.add_dependency("B", {"version": "^1.0", "python": "^3.5.3"}) package.add_dependency("B", {"version": "^1.0", "python": "^3.5.3"})
...@@ -1006,7 +1016,7 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir ...@@ -1006,7 +1016,7 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir
def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python( def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.4" solver.provider.set_package_python_versions("~2.7 || ^3.4")
package.add_dependency("A", {"version": "^1.0", "python": "^3.5"}) package.add_dependency("A", {"version": "^1.0", "python": "^3.5"})
package_a = get_package("A", "1.0.0") package_a = get_package("A", "1.0.0")
...@@ -1021,7 +1031,7 @@ def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_wit ...@@ -1021,7 +1031,7 @@ def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_wit
def test_solver_finds_compatible_package_for_dependency_python_not_fully_compatible_with_package_python( def test_solver_finds_compatible_package_for_dependency_python_not_fully_compatible_with_package_python(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.4" solver.provider.set_package_python_versions("~2.7 || ^3.4")
package.add_dependency("A", {"version": "^1.0", "python": "^3.5"}) package.add_dependency("A", {"version": "^1.0", "python": "^3.5"})
package_a101 = get_package("A", "1.0.1") package_a101 = get_package("A", "1.0.1")
...@@ -1077,7 +1087,7 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl ...@@ -1077,7 +1087,7 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl
def test_solver_does_not_raise_conflict_for_locked_conditional_dependencies( def test_solver_does_not_raise_conflict_for_locked_conditional_dependencies(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.4" solver.provider.set_package_python_versions("~2.7 || ^3.4")
package.add_dependency("A", {"version": "^1.0", "python": "^3.6"}) package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
package.add_dependency("B", "^1.0") package.add_dependency("B", "^1.0")
...@@ -1161,7 +1171,7 @@ def test_solver_should_not_resolve_prerelease_version_if_not_requested( ...@@ -1161,7 +1171,7 @@ def test_solver_should_not_resolve_prerelease_version_if_not_requested(
def test_solver_ignores_dependencies_with_incompatible_python_full_version_marker( def test_solver_ignores_dependencies_with_incompatible_python_full_version_marker(
solver, repo, package solver, repo, package
): ):
package.python_versions = "^3.6" solver.provider.set_package_python_versions("^3.6")
package.add_dependency("A", "^1.0") package.add_dependency("A", "^1.0")
package.add_dependency("B", "^2.0") package.add_dependency("B", "^2.0")
...@@ -1729,7 +1739,7 @@ def test_solver_discards_packages_with_empty_markers( ...@@ -1729,7 +1739,7 @@ def test_solver_discards_packages_with_empty_markers(
def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.5" solver.provider.set_package_python_versions("~2.7 || ^3.5")
package.add_dependency("A", {"version": "^1.0", "python": "~2.7"}, category="dev") package.add_dependency("A", {"version": "^1.0", "python": "~2.7"}, category="dev")
package.add_dependency("A", {"version": "^2.0", "python": "^3.5"}, category="dev") package.add_dependency("A", {"version": "^2.0", "python": "^3.5"}, category="dev")
...@@ -1753,7 +1763,7 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( ...@@ -1753,7 +1763,7 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies(
def test_solver_does_not_loop_indefinitely_on_duplicate_constraints_with_extras( def test_solver_does_not_loop_indefinitely_on_duplicate_constraints_with_extras(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.5" solver.provider.set_package_python_versions("~2.7 || ^3.5")
package.add_dependency("requests", {"version": "^2.22.0", "extras": ["security"]}) package.add_dependency("requests", {"version": "^2.22.0", "extras": ["security"]})
requests = get_package("requests", "2.22.0") requests = get_package("requests", "2.22.0")
...@@ -1825,7 +1835,7 @@ def test_ignore_python_constraint_no_overlap_dependencies(solver, repo, package) ...@@ -1825,7 +1835,7 @@ def test_ignore_python_constraint_no_overlap_dependencies(solver, repo, package)
def test_solver_should_not_go_into_an_infinite_loop_on_duplicate_dependencies( def test_solver_should_not_go_into_an_infinite_loop_on_duplicate_dependencies(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.5" solver.provider.set_package_python_versions("~2.7 || ^3.5")
package.add_dependency("A", "^1.0") package.add_dependency("A", "^1.0")
package_a = get_package("A", "1.0.0") package_a = get_package("A", "1.0.0")
...@@ -2000,8 +2010,28 @@ def test_solver_should_not_update_same_version_packages_if_installed_has_no_sour ...@@ -2000,8 +2010,28 @@ def test_solver_should_not_update_same_version_packages_if_installed_has_no_sour
ops = solver.solve() ops = solver.solve()
check_solver_result(ops, [{"job": "install", "package": foo, "skipped": True}])
def test_solver_should_use_the_python_constraint_from_the_environment_if_available(
solver, repo, package, installed
):
solver.provider.set_package_python_versions("~2.7 || ^3.5")
package.add_dependency("A", "^1.0")
a = get_package("A", "1.0.0")
a.add_dependency("B", {"version": "^1.0.0", "markers": 'python_version < "3.2"'})
b = get_package("B", "1.0.0")
b.python_versions = ">=2.6, <3"
repo.add_package(a)
repo.add_package(b)
with solver.use_environment(MockEnv((2, 7, 18))):
ops = solver.solve()
check_solver_result( check_solver_result(
ops, [{"job": "install", "package": foo, "skipped": True}], ops, [{"job": "install", "package": b}, {"job": "install", "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