Skip to content

Commit

Permalink
Use the proper main Python constraint when resolving for installation (
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater authored Jul 10, 2020
1 parent feef53b commit d628947
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 28 deletions.
16 changes: 9 additions & 7 deletions poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
"""
Expand Down Expand Up @@ -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
)
Expand All @@ -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()
Expand All @@ -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)
),
)
]
Expand All @@ -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))
]

Expand Down Expand Up @@ -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))
]
Expand Down
10 changes: 8 additions & 2 deletions poetry/puzzle/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion tests/mixology/version_solver/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions tests/mixology/version_solver/test_python_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 46 additions & 16 deletions tests/puzzle/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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()
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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")

Expand All @@ -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})
Expand Down Expand Up @@ -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")
Expand All @@ -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")

Expand Down Expand Up @@ -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")
Expand All @@ -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"})

Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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")

Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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}],
)


Expand Down

0 comments on commit d628947

Please sign in to comment.