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

lock: make --no-update the default behavior and introduce --regenerate for the previous default behavior #9327

Merged
merged 1 commit into from
Oct 4, 2024
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
6 changes: 4 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,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 @@ -720,7 +722,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()
Loading