Skip to content

Commit

Permalink
better approach
Browse files Browse the repository at this point in the history
  • Loading branch information
radoering committed Feb 11, 2024
1 parent 654112e commit 2f5306f
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
17 changes: 14 additions & 3 deletions src/poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -491,9 +492,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:
Expand Down Expand Up @@ -692,6 +691,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:
Expand All @@ -702,6 +711,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

Expand Down
60 changes: 52 additions & 8 deletions tests/puzzle/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion tests/puzzle/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down

0 comments on commit 2f5306f

Please sign in to comment.