Skip to content

Commit

Permalink
Reuse locked package info for vcs/file/url deps
Browse files Browse the repository at this point in the history
Prior to this change, when add/remove/lock commands were executed,
poetry would re-inspect all locked vcs/file/url dependencies. This is
sub-optimal and not required. This change enables reuse of locked
package info.
  • Loading branch information
abn committed Jul 25, 2020
1 parent 4769f72 commit 6fc2895
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 5 deletions.
2 changes: 1 addition & 1 deletion poetry/installation/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def _do_install(self, local_repo):

locked_repository = Repository()
if self._update:
if self._locker.is_locked() and not self._lock:
if self._locker.is_locked():
locked_repository = self._locker.locked_repository(True)

# If no packages have been whitelisted (The ones we want to update),
Expand Down
20 changes: 18 additions & 2 deletions poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from poetry.packages.package_collection import PackageCollection
from poetry.puzzle.exceptions import OverrideNeeded
from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.utils._compat import OrderedDict
from poetry.utils._compat import Path
from poetry.utils._compat import urlparse
Expand All @@ -55,8 +56,8 @@ class Provider:
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"}

def __init__(
self, package, pool, io, env=None
): # type: (Package, Pool, Any, Optional[Env]) -> None
self, package, pool, io, env=None, locked=None
): # type: (Package, Pool, Any, Optional[Env], Optional[Repository]) -> None
self._package = package
self._pool = pool
self._io = io
Expand All @@ -67,6 +68,11 @@ def __init__(
self._in_progress = False
self._overrides = {}
self._deferred_cache = {}
self._locked_packages = {}

if locked:
for package in locked.packages:
self._locked_packages[package.name] = package

@property
def pool(self): # type: () -> Pool
Expand Down Expand Up @@ -164,6 +170,9 @@ def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package]
if dependency in self._deferred_cache:
return [self._deferred_cache[dependency]]

if dependency.name in self._locked_packages:
return [self._locked_packages[dependency.name]]

package = self.get_package_from_vcs(
dependency.vcs,
dependency.source,
Expand Down Expand Up @@ -223,6 +232,8 @@ def search_for_file(self, dependency): # type: (FileDependency) -> List[Package
dependency, _package = self._deferred_cache[dependency]

package = _package.clone()
elif dependency.name in self._locked_packages:
package = self._locked_packages[dependency.name].clone()
else:
package = self.get_package_from_file(dependency.full_path)

Expand Down Expand Up @@ -279,6 +290,8 @@ def search_for_directory(
dependency, _package = self._deferred_cache[dependency]

package = _package.clone()
elif dependency.name in self._locked_packages:
package = self._locked_packages[dependency.name].clone()
else:
package = self.get_package_from_directory(
dependency.full_path, name=dependency.name
Expand Down Expand Up @@ -329,6 +342,9 @@ def search_for_url(self, dependency): # type: (URLDependency) -> List[Package]
if dependency in self._deferred_cache:
return [self._deferred_cache[dependency]]

if dependency.name in self._locked_packages:
return [self._locked_packages[dependency.name]]

package = self.get_package_from_url(dependency.url)

if dependency.name != package.name:
Expand Down
4 changes: 3 additions & 1 deletion poetry/puzzle/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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, locked=self._locked
)

self._provider = provider
self._overrides = []
Expand Down
67 changes: 66 additions & 1 deletion tests/installation/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from poetry.installation.executor import Executor as BaseExecutor
from poetry.installation.noop_installer import NoopInstaller
from poetry.packages import Locker as BaseLocker
from poetry.puzzle.provider import Provider
from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository
Expand All @@ -27,7 +28,7 @@
from tests.repositories.test_pypi_repository import MockRepository


fixtures_dir = Path("tests/fixtures")
fixtures_dir = Path(__file__).parent.parent / "fixtures"


class Installer(BaseInstaller):
Expand Down Expand Up @@ -1757,3 +1758,67 @@ def test_installer_can_handle_old_lock_files(

# colorama will be added
assert 8 == installer.executor.installations_count


def installer_add_with_existing_demo_package(
package, locker, repo, installer, lock_data, demo_constraint
):
locker.locked(True)
locker.mock_lock_data(lock_data)

repo.add_package(get_package("A", "1.0"))

package.add_dependency("demo", demo_constraint)
package.add_dependency("A", "^1.0")

installer.update(True)
installer.run()


def test_installer_reuse_lock_data_file(mocker, installer, locker, repo, package):
spy = mocker.spy(Provider, "get_package_from_file")
data = fixture("with-reusable-package-info-git")
constraint = {
"file": str(fixtures_dir / "distributions/demo-0.1.0-py2.py3-none-any.whl")
}
installer_add_with_existing_demo_package(
package=package,
locker=locker,
repo=repo,
installer=installer,
lock_data=data,
demo_constraint=constraint,
)
spy.assert_not_called()


def test_installer_reuse_lock_data_git(mocker, installer, locker, repo, package):
spy = mocker.spy(Provider, "get_package_from_vcs")
data = fixture("with-reusable-package-info-git")
constraint = {"git": "https://github.com/demo/demo.git"}
installer_add_with_existing_demo_package(
package=package,
locker=locker,
repo=repo,
installer=installer,
lock_data=data,
demo_constraint=constraint,
)
spy.assert_not_called()


def test_installer_reuse_lock_data_url(mocker, installer, locker, repo, package):
spy = mocker.spy(Provider, "get_package_from_url")
data = fixture("with-reusable-package-info-url")
constraint = {
"url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
}
installer_add_with_existing_demo_package(
package=package,
locker=locker,
repo=repo,
installer=installer,
lock_data=data,
demo_constraint=constraint,
)
spy.assert_not_called()

0 comments on commit 6fc2895

Please sign in to comment.