Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use proper main python constraint when resolving for installation #2625

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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