Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider: allow fallback to installed packages #5704

Merged
merged 1 commit into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 61 additions & 9 deletions src/poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from poetry.packages import DependencyPackage
from poetry.packages.package_collection import PackageCollection
from poetry.puzzle.exceptions import OverrideNeeded
from poetry.repositories.exceptions import PackageNotFound
from poetry.utils.helpers import download_file
from poetry.vcs.git import Git

Expand All @@ -46,10 +47,12 @@

from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.specification import PackageSpecification
from poetry.core.semver.version_constraint import VersionConstraint
from poetry.core.version.markers import BaseMarker

from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.utils.env import Env


Expand Down Expand Up @@ -124,6 +127,7 @@ def __init__(
pool: Pool,
io: Any,
env: Env | None = None,
installed: Repository | None = None,
) -> None:
self._package = package
self._pool = pool
Expand All @@ -136,6 +140,7 @@ def __init__(
self._deferred_cache: dict[Dependency, Package] = {}
self._load_deferred = True
self._source_root: Path | None = None
self._installed = installed

@property
def pool(self) -> Pool:
Expand Down Expand Up @@ -185,6 +190,36 @@ def validate_package_for_dependency(
f" package's name: {package.name}"
)

def search_for_installed_packages(
self,
specification: PackageSpecification,
) -> list[Package]:
"""
Search for installed packages, when available, that provides the given
specification.

This is useful when dealing with packages that are under development, not
published on package sources and/or only available via system installations.
"""
if not self._installed:
return []

logger.debug(
"Falling back to installed packages to discover metadata for <c2>%s</>",
specification.complete_name,
)
packages = [
package
for package in self._installed.packages
if package.provides(specification)
]
logger.debug(
"Found <c2>%d</> compatible packages for <c2>%s</>",
len(packages),
specification.complete_name,
)
return packages

def search_for(
self,
dependency: (
Expand Down Expand Up @@ -227,6 +262,9 @@ def search_for(
reverse=True,
)

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

return PackageCollection(dependency, packages)

def search_for_vcs(self, dependency: VCSDependency) -> list[Package]:
Expand Down Expand Up @@ -478,15 +516,29 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage:
"url",
"git",
}:
package = DependencyPackage(
package.dependency,
self._pool.package(
package.name,
package.version.text,
extras=list(package.dependency.extras),
repository=package.dependency.source_name,
),
)
try:
package = DependencyPackage(
package.dependency,
self._pool.package(
package.name,
package.version.text,
extras=list(package.dependency.extras),
repository=package.dependency.source_name,
),
)
except PackageNotFound as e:
try:
package = next(
DependencyPackage(
package.dependency,
pkg,
)
for pkg in self.search_for_installed_packages(
package.dependency
)
)
except StopIteration:
raise e from e
requires = package.requires
else:
requires = package.requires
Expand Down
4 changes: 3 additions & 1 deletion src/poetry/puzzle/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def __init__(
self._io = io

if provider is None:
provider = Provider(self._package, self._pool, self._io)
provider = Provider(
self._package, self._pool, self._io, installed=installed
)

self._provider = provider
self._overrides: list[dict[DependencyPackage, dict[str, Dependency]]] = []
Expand Down
37 changes: 36 additions & 1 deletion tests/puzzle/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ def solver(
io: NullIO,
) -> Solver:
return Solver(
package, pool, installed, locked, io, provider=Provider(package, pool, io)
package,
pool,
installed,
locked,
io,
provider=Provider(package, pool, io, installed=installed),
)


Expand Down Expand Up @@ -174,6 +179,36 @@ def test_install_non_existing_package_fail(
solver.solve()


def test_install_unpublished_package_does_not_fail(
installed: InstalledRepository,
solver: Solver,
repo: Repository,
package: ProjectPackage,
):
package.add_dependency(Factory.create_dependency("B", "1"))

package_a = get_package("A", "1.0")
package_b = get_package("B", "1")
package_b.add_dependency(Factory.create_dependency("A", "1.0"))

repo.add_package(package_a)
installed.add_package(package_b)

transaction = solver.solve()

check_solver_result(
transaction,
[
{"job": "install", "package": package_a},
{
"job": "install",
"package": package_b,
"skipped": True, # already installed
},
],
)


def test_solver_with_deps(solver: Solver, repo: Repository, package: ProjectPackage):
package.add_dependency(Factory.create_dependency("A", "*"))

Expand Down