Skip to content

Commit

Permalink
fix: lock all extra markers in dependencies if package is required by…
Browse files Browse the repository at this point in the history
… multiple extras (python-poetry#9700)

Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com>
  • Loading branch information
dimbleby and radoering authored Sep 29, 2024
1 parent 65ccfb2 commit cfe41d6
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 10 deletions.
18 changes: 8 additions & 10 deletions src/poetry/puzzle/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,14 @@ def _solve(self) -> tuple[list[Package], list[int]]:
if _package.name == dep.name:
continue

try:
index = _package.requires.index(dep)
except ValueError:
_package.add_dependency(dep)
else:
_dep = _package.requires[index]
if _dep.marker != dep.marker:
# marker of feature package is more accurate
# because it includes relevant extras
_dep.marker = dep.marker
# Avoid duplication.
if any(
_dep == dep and _dep.marker == dep.marker
for _dep in _package.requires
):
continue

_package.add_dependency(dep)
else:
final_packages.append(package)
depths.append(results[package])
Expand Down
57 changes: 57 additions & 0 deletions tests/puzzle/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,63 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency(
assert ops[0].package.marker.is_any()


def test_solver_locks_all_extras_when_multiple_extras_require_same_dependency(
solver: Solver,
repo: Repository,
package: ProjectPackage,
) -> None:
"""
- root depends on A[extra-b1] and C
- C depends on A[extra-b2]
- B is required by both extras
-> the locked dependency A on B must have both extra markers
"""
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.0")
package_c = get_package("C", "1.0")

dep_b1 = get_dependency("B", "*", optional=True)
dep_b1.marker = parse_marker("extra == 'extra-b1'")

dep_b2 = get_dependency("B", "*", optional=True)
dep_b2.marker = parse_marker("extra == 'extra-b2'")

package_a.extras = {
canonicalize_name("extra-b1"): [dep_b1],
canonicalize_name("extra-b2"): [dep_b2],
}
package_a.add_dependency(dep_b1)
package_a.add_dependency(dep_b2)

package.add_dependency(
get_dependency("A", {"version": "*", "extras": ["extra-b1"]})
)
package.add_dependency(get_dependency("C", "*"))
package_c.add_dependency(
get_dependency("A", {"version": "*", "extras": ["extra-b2"]})
)

repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)

transaction = solver.solve()

expected = [
{"job": "install", "package": package_b},
{"job": "install", "package": package_a},
{"job": "install", "package": package_c},
]

ops = check_solver_result(transaction, expected)
locked_a_requires = ops[1].package.requires
assert len(locked_a_requires) == 2
assert {str(r.marker) for r in locked_a_requires} == {
'extra == "extra-b1"',
'extra == "extra-b2"',
}


@pytest.mark.parametrize("enabled_extra", ["one", "two", None])
def test_solver_returns_extras_only_requested_nested(
solver: Solver,
Expand Down

0 comments on commit cfe41d6

Please sign in to comment.