Skip to content

Commit

Permalink
lock: make --no-update the default behavior and introduce `--regene…
Browse files Browse the repository at this point in the history
…rate` for the previous default behavior
  • Loading branch information
radoering committed Sep 15, 2024
1 parent 119a3d7 commit 7a8890a
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 34 deletions.
6 changes: 4 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,9 @@ poetry search requests pendulum
This command locks (without installing) the dependencies specified in `pyproject.toml`.

{{% note %}}
By default, this will lock all dependencies to the latest available compatible versions. To only refresh the lock file, use the `--no-update` option.
By default, packages that have already been added to the lock file before will not be updated.
To update all dependencies to the latest available compatible versions, use `poetry update --lock`
or `poetry lock --regenerate`, which normally produce the same result.
This command is also available as a pre-commit hook. See [pre-commit hooks]({{< relref "pre-commit-hooks#poetry-lock">}}) for more information.
{{% /note %}}

Expand All @@ -719,7 +721,7 @@ poetry lock
### Options

* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml`. (**Deprecated**) Use `poetry check --lock` instead.
* `--no-update`: Do not update locked versions, only refresh lock file.
* `--regenerate`: Ignore existing lock file and overwrite it with a new lock file created from scratch.

## version

Expand Down
9 changes: 7 additions & 2 deletions src/poetry/console/commands/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ class LockCommand(InstallerCommand):

options: ClassVar[list[Option]] = [
option(
"no-update", None, "Do not update locked versions, only refresh lock file."
"regenerate",
None,
"Ignore existing lock file"
" and overwrite it with a new lock file created from scratch.",
),
option(
"check",
Expand All @@ -34,6 +37,8 @@ class LockCommand(InstallerCommand):
current directory, processes it, and locks the dependencies in the\
<comment>poetry.lock</>
file.
By default, packages that have already been added to the lock file before
will not be updated.
<info>poetry lock</info>
"""
Expand All @@ -57,6 +62,6 @@ def handle(self) -> int:
)
return 1

self.installer.lock(update=not self.option("no-update"))
self.installer.lock(update=self.option("regenerate"))

return self.installer.run()
59 changes: 29 additions & 30 deletions tests/console/commands/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def test_lock_check_up_to_date_legacy(
assert status_code == 0


def test_lock_no_update(
def test_lock_does_not_update_if_not_necessary(
command_tester_factory: CommandTesterFactory,
poetry_with_old_lockfile: Poetry,
repo: TestRepository,
Expand All @@ -156,7 +156,7 @@ def test_lock_no_update(
)

tester = command_tester_factory("lock", poetry=poetry_with_old_lockfile)
tester.execute("--no-update")
tester.execute()

locker = Locker(
lock=poetry_with_old_lockfile.pyproject.file.path.parent / "poetry.lock",
Expand All @@ -172,10 +172,12 @@ def test_lock_no_update(
assert locked_repository.find_packages(package.to_dependency())


def test_lock_no_update_path_dependencies(
@pytest.mark.parametrize("regenerate", [True, False])
def test_lock_always_updates_path_dependencies(
command_tester_factory: CommandTesterFactory,
poetry_with_nested_path_deps_old_lockfile: Poetry,
repo: TestRepository,
regenerate: bool,
) -> None:
"""
The lock file contains a variant of the directory dependency "quix" that does
Expand All @@ -195,14 +197,14 @@ def test_lock_no_update_path_dependencies(
tester = command_tester_factory(
"lock", poetry=poetry_with_nested_path_deps_old_lockfile
)
tester.execute("--no-update")
tester.execute("--regenerate" if regenerate else "")

packages = locker.locked_repository().packages

assert {p.name for p in packages} == {"quix", "sampleproject"}


@pytest.mark.parametrize("update", [True, False])
@pytest.mark.parametrize("regenerate", [True, False])
@pytest.mark.parametrize(
"project", ["missing_directory_dependency", "missing_file_dependency"]
)
Expand All @@ -211,26 +213,26 @@ def test_lock_path_dependency_does_not_exist(
project_factory: ProjectFactory,
fixture_dir: FixtureDirGetter,
project: str,
update: bool,
regenerate: bool,
) -> None:
poetry = _project_factory(project, project_factory, fixture_dir)
locker = Locker(
lock=poetry.pyproject.file.path.parent / "poetry.lock",
pyproject_data=poetry.locker._pyproject_data,
)
poetry.set_locker(locker)
options = "" if update else "--no-update"
options = "--regenerate" if regenerate else ""

tester = command_tester_factory("lock", poetry=poetry)
if update or "directory" in project:
if regenerate or "directory" in project:
# directory dependencies are always updated
with pytest.raises(ValueError, match="does not exist"):
tester.execute(options)
else:
tester.execute(options)


@pytest.mark.parametrize("update", [True, False])
@pytest.mark.parametrize("regenerate", [True, False])
@pytest.mark.parametrize(
"project", ["deleted_directory_dependency", "deleted_file_dependency"]
)
Expand All @@ -239,7 +241,7 @@ def test_lock_path_dependency_deleted_from_pyproject(
project_factory: ProjectFactory,
fixture_dir: FixtureDirGetter,
project: str,
update: bool,
regenerate: bool,
) -> None:
poetry = _project_factory(project, project_factory, fixture_dir)
locker = Locker(
Expand All @@ -249,22 +251,19 @@ def test_lock_path_dependency_deleted_from_pyproject(
poetry.set_locker(locker)

tester = command_tester_factory("lock", poetry=poetry)
if update:
tester.execute("")
else:
tester.execute("--no-update")
tester.execute("--regenerate" if regenerate else "")

packages = locker.locked_repository().packages

assert {p.name for p in packages} == set()


@pytest.mark.parametrize("is_no_update", [False, True])
@pytest.mark.parametrize("regenerate", [True, False])
def test_lock_with_incompatible_lockfile(
command_tester_factory: CommandTesterFactory,
poetry_with_incompatible_lockfile: Poetry,
repo: TestRepository,
is_no_update: bool,
regenerate: bool,
) -> None:
repo.add_package(get_package("sampleproject", "1.3.1"))

Expand All @@ -276,26 +275,26 @@ def test_lock_with_incompatible_lockfile(
poetry_with_incompatible_lockfile.set_locker(locker)

tester = command_tester_factory("lock", poetry=poetry_with_incompatible_lockfile)
if is_no_update:
if regenerate:
# still possible because lock file is not required
status_code = tester.execute("--regenerate")
assert status_code == 0
else:
# not possible because of incompatible lock file
expected = (
"(?s)lock file is not compatible .*"
" regenerate the lock file with the `poetry lock` command"
)
with pytest.raises(RuntimeError, match=expected):
tester.execute("--no-update")
else:
# still possible because lock file is not required
status_code = tester.execute()
assert status_code == 0
tester.execute()


@pytest.mark.parametrize("is_no_update", [False, True])
@pytest.mark.parametrize("regenerate", [True, False])
def test_lock_with_invalid_lockfile(
command_tester_factory: CommandTesterFactory,
poetry_with_invalid_lockfile: Poetry,
repo: TestRepository,
is_no_update: bool,
regenerate: bool,
) -> None:
repo.add_package(get_package("sampleproject", "1.3.1"))

Expand All @@ -306,11 +305,11 @@ def test_lock_with_invalid_lockfile(
poetry_with_invalid_lockfile.set_locker(locker)

tester = command_tester_factory("lock", poetry=poetry_with_invalid_lockfile)
if is_no_update:
# not possible because of broken lock file
with pytest.raises(RuntimeError, match="Unable to read the lock file"):
tester.execute("--no-update")
else:
if regenerate:
# still possible because lock file is not required
status_code = tester.execute()
status_code = tester.execute("--regenerate")
assert status_code == 0
else:
# not possible because of broken lock file
with pytest.raises(RuntimeError, match="Unable to read the lock file"):
tester.execute()

0 comments on commit 7a8890a

Please sign in to comment.