From 84c776dcf0a5cb6de20d3f6270006d442ad4ea40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Wed, 7 Feb 2024 20:00:14 +0100 Subject: [PATCH] better approach --- src/poetry/puzzle/provider.py | 17 ++++++++-- tests/puzzle/test_provider.py | 60 ++++++++++++++++++++++++++++++----- tests/puzzle/test_solver.py | 2 +- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 47c4c128afb..b759af225d7 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -134,6 +134,7 @@ def __init__( self._locked: dict[NormalizedName, list[DependencyPackage]] = defaultdict(list) self._use_latest: Collection[NormalizedName] = [] + self._explicit_sources: dict[str, str] = {} for package in locked or []: self._locked[package.name].append( DependencyPackage(package.to_dependency(), package) @@ -487,9 +488,7 @@ def complete_package( package.pretty_name, package.version, extras=list(dependency.extras), - repository_name=( - dependency.source_name or package.source_reference - ), + repository_name=dependency.source_name, ), ) except PackageNotFound as e: @@ -684,6 +683,16 @@ def fmt_warning(d: Dependency) -> str: for dep in clean_dependencies: package.add_dependency(dep) + if self._locked and package.is_root(): + # At this point all duplicates have been eliminated via overrides + # so that explicit sources are unambiguous. + # Clear _explicit_sources because it might be filled + # from a previous override. + self._explicit_sources.clear() + for dep in clean_dependencies: + if dep.source_name: + self._explicit_sources[dep.name] = dep.source_name + return dependency_package def get_locked(self, dependency: Dependency) -> DependencyPackage | None: @@ -694,6 +703,8 @@ def get_locked(self, dependency: Dependency) -> DependencyPackage | None: for dependency_package in locked: package = dependency_package.package if package.satisfies(dependency): + if explicit_source := self._explicit_sources.get(dependency.name): + dependency.source_name = explicit_source return DependencyPackage(dependency, package) return None diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index 71f43a0a96c..1e1b03acc42 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -21,6 +21,7 @@ from poetry.packages import DependencyPackage from poetry.puzzle.provider import IncompatibleConstraintsError from poetry.puzzle.provider import Provider +from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.repository import Repository from poetry.repositories.repository_pool import Priority from poetry.repositories.repository_pool import RepositoryPool @@ -785,21 +786,64 @@ def test_complete_package_fetches_optional_vcs_dependency_only_if_requested( def test_complete_package_finds_locked_package_in_explicit_source( - pool: RepositoryPool, provider: Provider + root: ProjectPackage, pool: RepositoryPool ) -> None: package = Package("a", "1.0", source_reference="explicit") explicit_repo = Repository("explicit") explicit_repo.add_package(package) pool.add_repository(explicit_repo, priority=Priority.EXPLICIT) - dependency = package.to_dependency() - # complete_package() must succeed even if the dependency does not have an explicit - # source. This can be the case if the dependency is a transitive dependency and - # there is a direct dependency with an explicit source. - dependency.source_name = None + root_dependency = get_dependency("a", ">0") + root_dependency.source_name = "explicit" + root.add_dependency(root_dependency) + locked_package = Package("a", "1.0", source_reference="explicit") + provider = Provider(root, pool, NullIO(), locked=[locked_package]) + provider.complete_package(DependencyPackage(root.to_dependency(), root)) - # must not fail - provider.complete_package(DependencyPackage(dependency, package)) + # transitive dependency without explicit source + dependency = get_dependency("a", ">=1") + + locked = provider.get_locked(dependency) + assert locked is not None + provider.complete_package(locked) # must not fail + + +def test_complete_package_finds_locked_package_in_other_source( + root: ProjectPackage, repository: Repository, pool: RepositoryPool +) -> None: + package = Package("a", "1.0") + repository.add_package(package) + explicit_repo = Repository("explicit") + pool.add_repository(explicit_repo) + + root_dependency = get_dependency("a", ">0") # no explicit source + root.add_dependency(root_dependency) + locked_package = Package("a", "1.0", source_reference="explicit") # explicit source + provider = Provider(root, pool, NullIO(), locked=[locked_package]) + provider.complete_package(DependencyPackage(root.to_dependency(), root)) + + # transitive dependency without explicit source + dependency = get_dependency("a", ">=1") + + locked = provider.get_locked(dependency) + assert locked is not None + provider.complete_package(locked) # must not fail + + +def test_complete_package_raises_packagenotfound_if_locked_source_not_available( + root: ProjectPackage, pool: RepositoryPool, provider: Provider +) -> None: + locked_package = Package("a", "1.0", source_reference="outdated") + provider = Provider(root, pool, NullIO(), locked=[locked_package]) + provider.complete_package(DependencyPackage(root.to_dependency(), root)) + + # transitive dependency without explicit source + dependency = get_dependency("a", ">=1") + + locked = provider.get_locked(dependency) + assert locked is not None + with pytest.raises(PackageNotFound): + provider.complete_package(locked) def test_source_dependency_is_satisfied_by_direct_origin( diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 6de5a69128a..dd77ff01cca 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -3826,7 +3826,7 @@ def test_solver_should_not_update_same_version_packages_if_installed_has_no_sour "1.0.0", source_type="legacy", source_url="https://foo.bar", - source_reference="repo", + source_reference="custom", ) repo.add_package(foo)