diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index ed7b0b934b0..6e9dbf38f43 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -59,6 +59,18 @@ logger = logging.getLogger(__name__) +class IncompatibleConstraintsError(Exception): + """ + Exception when there are duplicate dependencies with incompatible constraints. + """ + + def __init__(self, package: Package, *dependencies: Dependency) -> None: + constraints = "\n".join(dep.to_pep_508() for dep in dependencies) + super().__init__( + f"Incompatible constraints in requirements of {package}:\n{constraints}" + ) + + class Indicator(ProgressIndicator): CONTEXT: str | None = None @@ -740,7 +752,7 @@ def fmt_warning(d: Dependency) -> str: f"Different requirements found for {warnings}." ) - deps = self._handle_any_marker_dependencies(deps) + deps = self._handle_any_marker_dependencies(package, deps) overrides = [] overrides_marker_intersection: BaseMarker = AnyMarker() @@ -975,7 +987,7 @@ def _merge_dependencies_by_marker( return deps def _handle_any_marker_dependencies( - self, dependencies: list[Dependency] + self, package: Package, dependencies: list[Dependency] ) -> list[Dependency]: """ We need to check if one of the duplicate dependencies @@ -999,11 +1011,16 @@ def _handle_any_marker_dependencies( any_markers_dependencies = [d for d in dependencies if d.marker.is_any()] other_markers_dependencies = [d for d in dependencies if not d.marker.is_any()] - for dep_any in any_markers_dependencies: + if any_markers_dependencies: for dep_other in other_markers_dependencies: - dep_other.constraint = dep_other.constraint.intersect( - dep_any.constraint - ) + new_constraint = dep_other.constraint + for dep_any in any_markers_dependencies: + new_constraint = new_constraint.intersect(dep_any.constraint) + if new_constraint.is_empty(): + raise IncompatibleConstraintsError( + package, dep_other, *any_markers_dependencies + ) + dep_other.constraint = new_constraint marker = other_markers_dependencies[0].marker for other_dep in other_markers_dependencies[1:]: diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 7d05ffae884..04f2dd70b35 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -1,5 +1,7 @@ from __future__ import annotations +import re + from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -19,6 +21,7 @@ from poetry.packages import DependencyPackage from poetry.puzzle import Solver from poetry.puzzle.exceptions import SolverProblemError +from poetry.puzzle.provider import IncompatibleConstraintsError from poetry.repositories.repository import Repository from poetry.repositories.repository_pool import RepositoryPool from poetry.utils.env import MockEnv @@ -1480,6 +1483,27 @@ def test_solver_duplicate_dependencies_different_constraints_merge_no_markers( ) +def test_solver_duplicate_dependencies_different_constraints_conflict( + solver: Solver, repo: Repository, package: ProjectPackage +) -> None: + package.add_dependency(Factory.create_dependency("A", ">=1.1")) + package.add_dependency( + Factory.create_dependency("A", {"version": "<1.1", "python": "3.10"}) + ) + + repo.add_package(get_package("A", "1.0")) + repo.add_package(get_package("A", "1.1")) + repo.add_package(get_package("A", "1.2")) + + expectation = ( + "Incompatible constraints in requirements of root (1.0):\n" + 'A (<1.1) ; python_version == "3.10"\n' + "A (>=1.1)" + ) + with pytest.raises(IncompatibleConstraintsError, match=re.escape(expectation)): + solver.solve() + + def test_solver_duplicate_dependencies_different_constraints_discard_no_markers1( solver: Solver, repo: Repository, package: ProjectPackage ) -> None: