From 04833bc42afc7ace61a471045cfface68141a7b6 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 2 Oct 2020 01:18:30 +0200 Subject: [PATCH] lock: allow for no-update refresh of lock files Relates-to: #3028 --- poetry/console/commands/lock.py | 10 +- poetry/installation/installer.py | 26 +++- .../fixtures/old-lock-refresh.test | 119 ++++++++++++++++++ tests/installation/test_installer.py | 38 ++++++ 4 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 tests/installation/fixtures/old-lock-refresh.test diff --git a/poetry/console/commands/lock.py b/poetry/console/commands/lock.py index ddedb055d87..4157c02c5cc 100644 --- a/poetry/console/commands/lock.py +++ b/poetry/console/commands/lock.py @@ -1,3 +1,5 @@ +from cleo import option + from .installer_command import InstallerCommand @@ -6,6 +8,12 @@ class LockCommand(InstallerCommand): name = "lock" description = "Locks the project dependencies." + options = [ + option( + "no-update", None, "Do not update locked versions, only refresh lock file." + ), + ] + help = """ The lock command reads the pyproject.toml file from the current directory, processes it, and locks the dependencies in the poetry.lock @@ -21,6 +29,6 @@ def handle(self): self.poetry.config.get("experimental.new-installer", False) ) - self._installer.lock() + self._installer.lock(update=not self.option("no-update")) return self._installer.run() diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index f0c9a62d65d..6956ae85fde 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -85,6 +85,10 @@ def set_locker(self, locker): # type: (Locker) -> Installer return self def run(self): + # Check if refresh + if not self._update and self._lock and self._locker.is_locked(): + return self._do_refresh() + # Force update if there is no lock file present if not self._update and not self._locker.is_locked(): self._update = True @@ -137,11 +141,11 @@ def update(self, update=True): # type: (bool) -> Installer return self - def lock(self): # type: () -> Installer + def lock(self, update=True): # type: (bool) -> Installer """ Prepare the installer for locking only. """ - self.update() + self.update(update=update) self.execute_operations(False) self._lock = True @@ -173,6 +177,20 @@ def use_executor(self, use_executor=True): # type: (bool) -> Installer return self + def _do_refresh(self): + # Checking extras + for extra in self._extras: + if extra not in self._package.extras: + raise ValueError("Extra [{}] is not specified.".format(extra)) + + ops = self._get_operations_from_lock(self._locker.locked_repository(True)) + local_repo = Repository() + self._populate_local_repo(local_repo, ops) + + self._write_lock_file(local_repo, force=True) + + return 0 + def _do_install(self, local_repo): from poetry.puzzle import Solver @@ -285,8 +303,8 @@ def _do_install(self, local_repo): # Execute operations return self._execute(ops) - def _write_lock_file(self, repo): # type: (Repository) -> None - if self._update and self._write_lock: + def _write_lock_file(self, repo, force=True): # type: (Repository, bool) -> None + if force or (self._update and self._write_lock): updated_lock = self._locker.set_lock_data(self._package, repo.packages) if updated_lock: diff --git a/tests/installation/fixtures/old-lock-refresh.test b/tests/installation/fixtures/old-lock-refresh.test new file mode 100644 index 00000000000..40e46ebdd3c --- /dev/null +++ b/tests/installation/fixtures/old-lock-refresh.test @@ -0,0 +1,119 @@ +[[package]] +name = "attrs" +version = "17.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface", "sphinx", "zope.interface"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface"] + +[[package]] +name = "colorama" +version = "0.3.9" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "funcsigs" +version = "1.0.2" +description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "more-itertools" +version = "4.1.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.0.0,<2.0.0" + +[[package]] +name = "pluggy" +version = "0.6.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "py" +version = "1.5.3" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pytest" +version = "3.5.0" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +attrs = ">=17.4.0" +colorama = "*" +funcsigs = {version = "*", markers = "python_version < \"3.0\""} +more-itertools = ">=4.0.0" +pluggy = ">=0.5,<0.7" +py = ">=1.5.0" +six = ">=1.10.0" + +[[package]] +name = "six" +version = "1.11.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = "*" + +[metadata] +lock-version = "1.1" +python-versions = "*" +content-hash = "123456789" + +[metadata.files] +attrs = [ + {file = "attrs-17.4.0-py2.py3-none-any.whl", hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"}, + {file = "attrs-17.4.0.tar.gz", hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"}, +] +colorama = [ + {file = "colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"}, + {file = "colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"}, +] +funcsigs = [ + {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"}, + {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"}, +] +more-itertools = [ + {file = "more-itertools-4.1.0.tar.gz", hash = "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"}, + {file = "more_itertools-4.1.0-py2-none-any.whl", hash = "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e"}, + {file = "more_itertools-4.1.0-py3-none-any.whl", hash = "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea"}, +] +pluggy = [ + {file = "pluggy-0.6.0.tar.gz", hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"}, +] +py = [ + {file = "py-1.5.3-py2.py3-none-any.whl", hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"}, + {file = "py-1.5.3.tar.gz", hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"}, +] +pytest = [ + {file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"}, + {file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"}, +] +six = [ + {file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"}, + {file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"}, +] diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 077f6dcab40..95f98dad185 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1772,6 +1772,44 @@ def test_installer_uses_prereleases_if_they_are_compatible( assert 2 == installer.executor.installations_count +def test_installer_can_refresh_old_lock_files(locker, package, repo, installed, config): + pool = Pool() + pool.add_repository(repo) + + locker.locked(True) + locker.mock_lock_data(fixture("old-lock")) + + for pkg in locker.locked_repository(with_dev_reqs=True).packages: + dependency = Factory.create_dependency( + name=pkg.name, + constraint=pkg.to_dependency().constraint, + category=pkg.category, + ) + package.add_dependency(dependency) + repo.add_package(pkg) + + installer = Installer( + NullIO(), + MockEnv(), + package, + locker, + pool, + config, + installed=None, + executor=Executor(MockEnv(), pool, config, NullIO()), + ) + installer.use_executor() + + installer.lock(update=False) + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 0 == installer.executor.removals_count + + assert locker.written_data == fixture("old-lock-refresh") + + def test_installer_can_handle_old_lock_files( installer, locker, package, repo, installed, config ):