diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 162c6602335..663a0ce6090 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -18,6 +18,7 @@ from poetry.core.packages import URLDependency from poetry.core.packages import VCSDependency 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.version.markers import MarkerUnion from poetry.inspection.info import PackageInfo @@ -80,12 +81,15 @@ def set_overrides(self, overrides): @contextmanager def use_environment(self, env): # type: (Env) -> Provider original_env = self._env + original_python_constraint = self._python_constraint self._env = env + self._python_constraint = Version.parse(env.marker_env["python_full_version"]) yield self self._env = original_env + self._python_constraint = original_python_constraint def search_for(self, dependency): # type: (Dependency) -> List[Package] """ @@ -380,9 +384,7 @@ def incompatibilities_for( else: dependencies = package.requires - if not package.python_constraint.allows_all( - self._package.python_constraint - ): + if not package.python_constraint.allows_all(self._python_constraint): transitive_python_constraint = get_python_constraint_from_marker( package.dependency.transitive_marker ) @@ -392,7 +394,7 @@ def incompatibilities_for( difference = transitive_python_constraint.difference(intersection) if ( transitive_python_constraint.is_any() - or self._package.python_constraint.intersect( + or self._python_constraint.intersect( package.dependency.python_constraint ).is_empty() or intersection.is_empty() @@ -402,7 +404,7 @@ def incompatibilities_for( Incompatibility( [Term(package.to_dependency(), True)], PythonCause( - package.python_versions, self._package.python_versions + package.python_versions, str(self._python_constraint) ), ) ] @@ -411,7 +413,7 @@ def incompatibilities_for( dep for dep in dependencies 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)) ] @@ -477,7 +479,7 @@ def complete_package( _dependencies = [ r 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 (not self._env or r.marker.validate(self._env.marker_env)) ] diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index a404971f0fe..4f569a201c5 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -4,6 +4,7 @@ from typing import Any from typing import Dict from typing import List +from typing import Optional from clikit.io import ConsoleIO @@ -33,14 +34,19 @@ def __init__( installed, # type: Repository locked, # type: Repository io, # type: ConsoleIO - remove_untracked=False, # type: bool + remove_untracked=False, # type: bool, + provider=None, # type: Optional[Provider] ): self._package = package self._pool = pool self._installed = installed self._locked = locked 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._remove_untracked = remove_untracked diff --git a/poetry/utils/env.py b/poetry/utils/env.py index a40b00baede..d81fe6637c1 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1238,6 +1238,7 @@ def get_marker_env(self): # type: () -> Dict[str, Any] marker_env["python_implementation"] = self._python_implementation marker_env["version_info"] = self._version_info 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 return marker_env diff --git a/tests/mixology/version_solver/conftest.py b/tests/mixology/version_solver/conftest.py index 0b605d31f6b..b31634b85db 100644 --- a/tests/mixology/version_solver/conftest.py +++ b/tests/mixology/version_solver/conftest.py @@ -3,11 +3,17 @@ from clikit.io import NullIO 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 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 def repo(): return Repository() diff --git a/tests/mixology/version_solver/test_python_constraint.py b/tests/mixology/version_solver/test_python_constraint.py index a05dcd9c444..8229079a7a2 100644 --- a/tests/mixology/version_solver/test_python_constraint.py +++ b/tests/mixology/version_solver/test_python_constraint.py @@ -3,12 +3,12 @@ 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", "*") 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: - foo requires Python <3.5, so it will not be satisfied for Python >=3.6,<4.0 diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index d1b9f96c332..c64438cca3f 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -7,10 +7,12 @@ from poetry.core.version.markers import parse_marker from poetry.puzzle import Solver from poetry.puzzle.exceptions import SolverProblemError +from poetry.puzzle.provider import Provider as BaseProvider from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository from poetry.utils._compat import Path +from poetry.utils.env import MockEnv from tests.helpers import get_dependency from tests.helpers import get_package from tests.repositories.test_legacy_repository import ( @@ -19,6 +21,12 @@ 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() def io(): return NullIO() @@ -51,7 +59,9 @@ def pool(repo): @pytest.fixture() 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): @@ -295,7 +305,7 @@ def test_solver_sets_categories(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("B") @@ -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): - package.python_versions = "^3.4" + solver.provider.set_package_python_versions("^3.4") package.add_dependency("A") package.add_dependency("B") @@ -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): - package.python_versions = "~3.4" + solver.provider.set_package_python_versions("~3.4") package.extras["foo"] = [get_dependency("B")] package.add_dependency("A", {"version": "*", "python": "^3.4"}) package.add_dependency("B", {"version": "*", "optional": True}) @@ -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( solver, repo, package ): - package.python_versions = "^3.5" + solver.provider.set_package_python_versions("^3.5") package.add_dependency("A") package_a = get_package("A", "1.0") @@ -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( solver, repo, package ): - package.python_versions = "^3.5" + solver.provider.set_package_python_versions("^3.5") package.add_dependency("A") 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 def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible( 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_a = get_package("A", "1.0.0") @@ -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( 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("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 def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python( 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_a = get_package("A", "1.0.0") @@ -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( 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_a101 = get_package("A", "1.0.1") @@ -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( 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("B", "^1.0") @@ -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( 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("B", "^2.0") @@ -1729,7 +1739,7 @@ def test_solver_discards_packages_with_empty_markers( def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( 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": "^2.0", "python": "^3.5"}, category="dev") @@ -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( 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"]}) requests = get_package("requests", "2.22.0") @@ -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( 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_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 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( - ops, [{"job": "install", "package": foo, "skipped": True}], + ops, [{"job": "install", "package": b}, {"job": "install", "package": a}], )