Skip to content

Commit

Permalink
fix considered python constraint for overrides (#10157)
Browse files Browse the repository at this point in the history
Previously, the Python constraint of the root package has always been used to check for incompatibilities, even if the Python constraint had effectively been restricted by overrides before. This could result in false conflicts à la "The current project's supported Python range is not compatible with some of the required packages Python requirement."
  • Loading branch information
radoering authored Feb 8, 2025
1 parent 57c201c commit 9d66222
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 12 deletions.
38 changes: 27 additions & 11 deletions src/poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def __init__(
self._direct_origin = DirectOrigin(self._pool.artifact_cache)
self._io = io
self._env: Env | None = None
self._python_constraint = package.python_constraint
self._package_python_constraint = package.python_constraint
self._is_debugging: bool = self._io.is_debug() or self._io.is_very_verbose()
self._overrides: dict[Package, dict[str, Dependency]] = {}
self._deferred_cache: dict[Dependency, Package] = {}
Expand Down Expand Up @@ -159,11 +159,29 @@ def pool(self) -> RepositoryPool:
def use_latest(self) -> Collection[NormalizedName]:
return self._use_latest

@functools.cached_property
def _overrides_marker_intersection(self) -> BaseMarker:
overrides_marker_intersection: BaseMarker = AnyMarker()
for dep_overrides in self._overrides.values():
for dep in dep_overrides.values():
overrides_marker_intersection = overrides_marker_intersection.intersect(
dep.marker
)
return overrides_marker_intersection

@functools.cached_property
def _python_constraint(self) -> VersionConstraint:
return self._package_python_constraint.intersect(
get_python_constraint_from_marker(self._overrides_marker_intersection)
)

def is_debugging(self) -> bool:
return self._is_debugging

def set_overrides(self, overrides: dict[Package, dict[str, Dependency]]) -> None:
self._overrides = overrides
self.__dict__.pop("_python_constraint", None)
self.__dict__.pop("_overrides_marker_intersection", None)

def load_deferred(self, load_deferred: bool) -> None:
self._load_deferred = load_deferred
Expand All @@ -180,16 +198,18 @@ def use_source_root(self, source_root: Path) -> Iterator[Provider]:

@contextmanager
def use_environment(self, env: Env) -> Iterator[Provider]:
original_python_constraint = self._python_constraint
original_python_constraint = self._package_python_constraint

self._env = env
self._python_constraint = Version.parse(env.marker_env["python_full_version"])
self._package_python_constraint = Version.parse(
env.marker_env["python_full_version"]
)

try:
yield self
finally:
self._env = None
self._python_constraint = original_python_constraint
self._package_python_constraint = original_python_constraint

@contextmanager
def use_latest_for(self, names: Collection[NormalizedName]) -> Iterator[Provider]:
Expand Down Expand Up @@ -629,16 +649,12 @@ def complete_package(
continue

# Sort out irrelevant requirements
overrides_marker_intersection: BaseMarker = AnyMarker()
for dep_overrides in self._overrides.values():
for dep in dep_overrides.values():
overrides_marker_intersection = (
overrides_marker_intersection.intersect(dep.marker)
)
deps = [
dep
for dep in deps
if not overrides_marker_intersection.intersect(dep.marker).is_empty()
if not self._overrides_marker_intersection.intersect(
dep.marker
).is_empty()
]
if len(deps) < 2:
if not deps or (len(deps) == 1 and deps[0].constraint.is_empty()):
Expand Down
68 changes: 67 additions & 1 deletion tests/puzzle/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

def set_package_python_versions(provider: Provider, python_versions: str) -> None:
provider._package.python_versions = python_versions
provider._python_constraint = provider._package.python_constraint
provider._package_python_constraint = provider._package.python_constraint


def check_solver_result(
Expand Down Expand Up @@ -4445,6 +4445,72 @@ def patched_choose_next(unsatisfied: list[Dependency]) -> Dependency:
)


@pytest.mark.parametrize("numpy_before_pandas", [False, True])
@pytest.mark.parametrize("conflict", [False, True])
def test_solver_should_not_raise_errors_for_irrelevant_transitive_python_constraints2(
solver: Solver,
repo: Repository,
package: ProjectPackage,
mocker: MockerFixture,
numpy_before_pandas: bool,
conflict: bool,
) -> None:
"""This time with overrides."""
package.python_versions = ">=3.6.2, <3.10"
set_package_python_versions(solver.provider, ">=3.6.2, <3.10")
package.add_dependency(Factory.create_dependency("pandas", ">=1"))
package.add_dependency(
Factory.create_dependency("numpy", {"version": ">=1.20.0", "python": ">=3.7"})
)
package.add_dependency(
Factory.create_dependency("numpy", {"version": "*", "python": "<3.7"})
)

pandas = get_package("pandas", "1.1.5")
pandas.add_dependency(Factory.create_dependency("numpy", ">=1.15"))

numpy_19 = get_package("numpy", "1.19")
numpy_19.python_versions = ">=3.6"
numpy_20 = get_package("numpy", "1.20")
numpy_20.python_versions = ">=3.8" if conflict else ">=3.7"

repo.add_package(pandas)
repo.add_package(numpy_19)
repo.add_package(numpy_20)

def patched_choose_next(unsatisfied: list[Dependency]) -> Dependency:
order = (
("root", "pandas", "numpy")
if numpy_before_pandas
else ("root", "numpy", "pandas")
)
for preferred in order:
for dep in unsatisfied:
if dep.name == preferred:
return dep
raise RuntimeError

mocker.patch(
"poetry.mixology.version_solver.VersionSolver._choose_next",
side_effect=patched_choose_next,
)

if conflict:
with pytest.raises(SolverProblemError):
solver.solve()
else:
transaction = solver.solve()

check_solver_result(
transaction,
[
{"job": "install", "package": numpy_19},
{"job": "install", "package": numpy_20},
{"job": "install", "package": pandas},
],
)


@pytest.mark.parametrize("is_locked", [False, True])
def test_solver_keeps_multiple_locked_dependencies_for_same_package(
package: ProjectPackage,
Expand Down

0 comments on commit 9d66222

Please sign in to comment.