diff --git a/.github/workflows/.tests-matrix.yaml b/.github/workflows/.tests-matrix.yaml index ee732b9cebf..670afb6a65d 100644 --- a/.github/workflows/.tests-matrix.yaml +++ b/.github/workflows/.tests-matrix.yaml @@ -112,6 +112,7 @@ jobs: # if the plugin still supports a wider range than Poetry itself. run: | perl -pi -e 's/^python =.*$/python = "~'"${PYTHON_VERSION}"'"/' pyproject.toml + poetry remove --lock poetry-core # use whatever poetry uses poetry add --lock --group dev ../poetry env: PYTHON_VERSION: ${{ inputs.python-version }} diff --git a/poetry.lock b/poetry.lock index a872ed85907..dde6e80f629 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1067,7 +1067,7 @@ develop = false type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "HEAD" -resolved_reference = "eccf08b5703f20c39282aa8be335d125aa36faab" +resolved_reference = "91542fc8d7220a1830cc73b85bb99f31ea4d5153" [[package]] name = "pre-commit" diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 6f8a4444bc2..0c51ff1c754 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -453,26 +453,70 @@ class Preference: LOCKED = 3 DEFAULT = 4 - def _get_min(dependency: Dependency) -> tuple[bool, int, int]: + def _get_min(dependency: Dependency) -> tuple[int, int, bool, int]: + """ + Returns a tuple of: + - preference: see Preference class + - num_deps_upper_bound: a dependency with an upper bound is more likely to + cause conflicts -> a package with more dependencies + with upper bounds should be chosen first + - has_deps: a package with dependencies should be chosen first because + a package without dependencies is less likely to cause conflicts + - num_packages: see explanation above + """ # Direct origin dependencies must be handled first: we don't want to resolve # a regular dependency for some package only to find later that we had a # direct-origin dependency. if dependency.is_direct_origin(): - return False, Preference.DIRECT_ORIGIN, -1 - - is_specific_marker = not dependency.marker.is_any() + return Preference.DIRECT_ORIGIN, 0, False, 0 use_latest = dependency.name in self._provider.use_latest if not use_latest: locked = self._provider.get_locked(dependency) if locked: - return is_specific_marker, Preference.LOCKED, -1 + return Preference.LOCKED, 0, False, 0 - num_packages = len( - self._dependency_cache.search_for( - dependency, self._solution.decision_level - ) + packages = self._dependency_cache.search_for( + dependency, self._solution.decision_level ) + num_packages = len(packages) + if packages: + package = packages[0].package + if package.is_root(): + relevant_dependencies = package.all_requires + else: + if not package.is_direct_origin(): + # We have to get the package from the pool, + # otherwise `requires` will be empty. + # + # We might need `package.source_reference` as fallback + # for transitive dependencies without a source + # if there is a top-level dependency + # for the same package with an explicit source. + for repo in (dependency.source_name, package.source_reference): + try: + package = self._provider.get_package_from_pool( + package.pretty_name, + package.version, + repository_name=repo, + ) + except Exception: + pass + else: + break + + relevant_dependencies = [ + r + for r in package.requires + if not r.in_extras or r.in_extras[0] in dependency.extras + ] + has_deps = bool(relevant_dependencies) + num_deps_upper_bound = sum( + 1 for d in relevant_dependencies if d.constraint.has_upper_bound() + ) + else: + has_deps = False + num_deps_upper_bound = 0 if num_packages < 2: preference = Preference.NO_CHOICE @@ -480,7 +524,7 @@ def _get_min(dependency: Dependency) -> tuple[bool, int, int]: preference = Preference.USE_LATEST else: preference = Preference.DEFAULT - return is_specific_marker, preference, -num_packages + return preference, -num_deps_upper_bound, not has_deps, -num_packages return min(unsatisfied, key=_get_min) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 6617a1be888..64fc67545d3 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -149,7 +149,7 @@ def __init__( reverse=True, ) - self._get_package_from_pool = functools.cache(self._pool.package) + self.get_package_from_pool = functools.cache(self._pool.package) @property def pool(self) -> RepositoryPool: @@ -469,7 +469,7 @@ def complete_package( else: dependency_package = DependencyPackage( dependency, - self._get_package_from_pool( + self.get_package_from_pool( package.pretty_name, package.version, repository_name=dependency.source_name, diff --git a/tests/mixology/version_solver/test_unsolvable.py b/tests/mixology/version_solver/test_unsolvable.py index ce5f6456a28..5008f0358df 100644 --- a/tests/mixology/version_solver/test_unsolvable.py +++ b/tests/mixology/version_solver/test_unsolvable.py @@ -71,9 +71,9 @@ def test_disjoint_constraints( add_to_repo(repo, "shared", "4.0.0") error = """\ -Because bar (1.0.0) depends on shared (>3.0.0) - and foo (1.0.0) depends on shared (<=2.0.0),\ - bar (1.0.0) is incompatible with foo (1.0.0). +Because foo (1.0.0) depends on shared (<=2.0.0) + and bar (1.0.0) depends on shared (>3.0.0),\ + foo (1.0.0) is incompatible with bar (1.0.0). So, because myapp depends on both foo (1.0.0) and bar (1.0.0), version solving failed.\ """