Skip to content

Commit 750bb9f

Browse files
committed
revert python-poetry#5770, provide new fix
1 parent 0686427 commit 750bb9f

File tree

4 files changed

+75
-29
lines changed

4 files changed

+75
-29
lines changed

src/poetry/mixology/partial_solution.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,6 @@ def _register(self, assignment: Assignment) -> None:
146146
"""
147147
name = assignment.dependency.complete_name
148148
old_positive = self._positive.get(name)
149-
if old_positive is None and assignment.dependency.features:
150-
old_positive_without_features = self._positive.get(
151-
assignment.dependency.name
152-
)
153-
if old_positive_without_features is not None:
154-
dep = old_positive_without_features.dependency.with_features(
155-
assignment.dependency.features
156-
)
157-
old_positive = Term(dep, is_positive=True)
158149
if old_positive is not None:
159150
value = old_positive.intersect(assignment)
160151
assert value is not None

src/poetry/mixology/version_solver.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,12 @@ def _choose_package_version(self) -> str | None:
376376
# Prefer packages with as few remaining versions as possible,
377377
# so that if a conflict is necessary it's forced quickly.
378378
def _get_min(dependency: Dependency) -> tuple[bool, int]:
379+
# Direct origin dependencies must be handled first: we don't want to resolve
380+
# a regular dependency for some package only to find later that we had a
381+
# direct-origin dependency.
382+
if dependency.is_direct_origin():
383+
return False, -1
384+
379385
if dependency.name in self._use_latest:
380386
# If we're forced to use the latest version of a package, it effectively
381387
# only has one version to choose from.
@@ -385,16 +391,6 @@ def _get_min(dependency: Dependency) -> tuple[bool, int]:
385391
if locked:
386392
return not dependency.marker.is_any(), 1
387393

388-
# VCS, URL, File or Directory dependencies
389-
# represent a single version
390-
if (
391-
dependency.is_vcs()
392-
or dependency.is_url()
393-
or dependency.is_file()
394-
or dependency.is_directory()
395-
):
396-
return not dependency.marker.is_any(), 1
397-
398394
try:
399395
return (
400396
not dependency.marker.is_any(),

src/poetry/puzzle/provider.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ def __init__(
140140
self._load_deferred = True
141141
self._source_root: Path | None = None
142142
self._installed_packages = installed if installed is not None else []
143+
self._direct_origin_packages: dict[str, Package] = {}
143144

144145
@property
145146
def pool(self) -> Pool:
@@ -268,18 +269,33 @@ def search_for(self, dependency: Dependency) -> list[DependencyPackage]:
268269
return PackageCollection(dependency, [self._package])
269270

270271
if dependency.is_direct_origin():
271-
packages = [self.search_for_direct_origin_dependency(dependency)]
272+
package = self.search_for_direct_origin_dependency(dependency)
273+
self._direct_origin_packages[dependency.name] = package
274+
return PackageCollection(dependency, [package])
272275

273-
else:
274-
packages = self._pool.find_packages(dependency)
275-
276-
packages.sort(
277-
key=lambda p: (
278-
not p.is_prerelease() and not dependency.allows_prereleases(),
279-
p.version,
280-
),
281-
reverse=True,
276+
# If we've previously found a direct-origin package that meets this dependency,
277+
# use it.
278+
#
279+
# We rely on the VersionSolver resolving direct-origin dependencies first.
280+
direct_origin_package = self._direct_origin_packages.get(dependency.name)
281+
if direct_origin_package is not None:
282+
package = direct_origin_package.with_features(dependency.features)
283+
packages = (
284+
[package]
285+
if package.satisfies(dependency, ignore_source_type=True)
286+
else []
282287
)
288+
return PackageCollection(dependency, packages)
289+
290+
packages = self._pool.find_packages(dependency)
291+
292+
packages.sort(
293+
key=lambda p: (
294+
not p.is_prerelease() and not dependency.allows_prereleases(),
295+
p.version,
296+
),
297+
reverse=True,
298+
)
283299

284300
if not packages:
285301
packages = self.search_for_installed_packages(dependency)

tests/puzzle/test_solver.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3596,3 +3596,46 @@ def test_solver_direct_origin_dependency_with_extras_requested_by_other_package(
35963596
assert op.package.version.text == "0.1.2"
35973597
assert op.package.source_type == "directory"
35983598
assert op.package.source_url == path
3599+
3600+
3601+
def test_solver_incompatible_dependency_with_and_without_extras(
3602+
solver: Solver, repo: Repository, package: ProjectPackage
3603+
):
3604+
"""
3605+
The solver first encounters a requirement for google-auth and then later an
3606+
incompatible requirement for google-auth[aiohttp].
3607+
3608+
Testcase derived from https://github.com/python-poetry/poetry/issues/6054.
3609+
"""
3610+
# Incompatible requirements from foo and bar.
3611+
foo = get_package("foo", "1.0.0")
3612+
foo.add_dependency(Factory.create_dependency("google-auth", {"version": "^1"}))
3613+
3614+
bar = get_package("bar", "1.0.0")
3615+
bar.add_dependency(
3616+
Factory.create_dependency(
3617+
"google-auth", {"version": "^2", "extras": ["aiohttp"]}
3618+
)
3619+
)
3620+
3621+
baz = get_package("bar", "1.0.0") # required by google-auth[aiohttp]
3622+
3623+
google_auth = get_package("google-auth", "1.2.3")
3624+
google_auth.extras = {"aiohttp": [get_dependency("baz", "^1.0")]}
3625+
3626+
google_auth2 = get_package("google-auth", "2.3.4")
3627+
google_auth2.extras = {"aiohttp": [get_dependency("baz", "^1.0")]}
3628+
3629+
repo.add_package(foo)
3630+
repo.add_package(bar)
3631+
repo.add_package(baz)
3632+
repo.add_package(google_auth)
3633+
repo.add_package(google_auth2)
3634+
3635+
package.add_dependency(Factory.create_dependency("foo", "^1"))
3636+
package.add_dependency(Factory.create_dependency("bar", "^1"))
3637+
3638+
# The puzzle in the original issue report does have a solution after backtracking,
3639+
# but this testcase is sufficient to demonstrate the bug and fix.
3640+
with pytest.raises(SolverProblemError):
3641+
solver.solve()

0 commit comments

Comments
 (0)