Commit 7d414af6 by Randy Döring Committed by GitHub

feat(locker): `poetry lock` works if an invalid/incompatible lock file exists (#6753)

After having created a lock file 2.0, running `poetry lock` with poetry
1.2.1 results in the following output:

```
The lock file is not compatible with the current version of Poetry.
Upgrade Poetry to be able to read the lock file or, alternatively, regenerate the lock file with the `poetry lock` command.
```

Ironically, the error message proposes to run `poetry lock` which
results in this error message.

Further, it doesn't make sense that `poetry lock` fails because it
creates a new lock file from scratch (in contrast to `poetry lock
--no-update`).

Running `poetry lock` is now also possible if there is a broken lock file.

Resolves: #1196
parent 94a5ce40
......@@ -12,6 +12,7 @@ repos:
- id: check-case-conflict
- id: check-json
- id: check-toml
exclude: tests/fixtures/invalid_lock/poetry\.lock
- id: check-yaml
- id: pretty-format-json
args: [--autofix, --no-ensure-ascii, --no-sort-keys]
......
......@@ -218,7 +218,7 @@ class Installer:
locked_repository = Repository("poetry-locked")
if self._update:
if self._locker.is_locked() and not self._lock:
if not self._lock and self._locker.is_locked():
locked_repository = self._locker.locked_repository()
# If no packages have been whitelisted (The ones we want to update),
......
......@@ -73,10 +73,7 @@ class Locker:
"""
Checks whether the locker has been locked (lockfile found).
"""
if not self._lock.exists():
return False
return "package" in self.lock_data
return self._lock.exists()
def is_fresh(self) -> bool:
"""
......@@ -256,12 +253,18 @@ class Locker:
"content-hash": self._content_hash,
}
if not self.is_locked() or lock != self.lock_data:
do_write = True
if self.is_locked():
try:
lock_data = self.lock_data
except RuntimeError:
# incompatible, invalid or no lock file
pass
else:
do_write = lock != lock_data
if do_write:
self._write_lock_data(lock)
return True
return False
return do_write
def _write_lock_data(self, data: TOMLDocument) -> None:
self.lock.write(data)
......
......@@ -67,6 +67,20 @@ def poetry_with_old_lockfile(
return _project_factory("old_lock", project_factory, fixture_dir)
@pytest.fixture
def poetry_with_incompatible_lockfile(
project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
) -> Poetry:
return _project_factory("incompatible_lock", project_factory, fixture_dir)
@pytest.fixture
def poetry_with_invalid_lockfile(
project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
) -> Poetry:
return _project_factory("invalid_lock", project_factory, fixture_dir)
def test_lock_check_outdated(
command_tester_factory: CommandTesterFactory,
poetry_with_outdated_lockfile: Poetry,
......@@ -150,3 +164,60 @@ def test_lock_no_update(
for package in packages:
assert locked_repository.find_packages(package.to_dependency())
@pytest.mark.parametrize("is_no_update", [False, True])
def test_lock_with_incompatible_lockfile(
command_tester_factory: CommandTesterFactory,
poetry_with_incompatible_lockfile: Poetry,
repo: TestRepository,
is_no_update: bool,
) -> None:
repo.add_package(get_package("sampleproject", "1.3.1"))
locker = Locker(
lock=poetry_with_incompatible_lockfile.pyproject.file.path.parent
/ "poetry.lock",
local_config=poetry_with_incompatible_lockfile.locker._local_config,
)
poetry_with_incompatible_lockfile.set_locker(locker)
tester = command_tester_factory("lock", poetry=poetry_with_incompatible_lockfile)
if is_no_update:
# 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
@pytest.mark.parametrize("is_no_update", [False, True])
def test_lock_with_invalid_lockfile(
command_tester_factory: CommandTesterFactory,
poetry_with_invalid_lockfile: Poetry,
repo: TestRepository,
is_no_update: bool,
) -> None:
repo.add_package(get_package("sampleproject", "1.3.1"))
locker = Locker(
lock=poetry_with_invalid_lockfile.pyproject.file.path.parent / "poetry.lock",
local_config=poetry_with_invalid_lockfile.locker._local_config,
)
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:
# still possible because lock file is not required
status_code = tester.execute()
assert status_code == 0
[metadata]
lock-version = "999.0"
[tool.poetry]
name = "foobar"
version = "0.1.0"
description = ""
authors = ["Poetry Developer <developer@python-poetry.org>"]
[tool.poetry.dependencies]
python = "^3.8"
sampleproject = ">=1.3.1"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "foobar"
version = "0.1.0"
description = ""
authors = ["Poetry Developer <developer@python-poetry.org>"]
[tool.poetry.dependencies]
python = "^3.8"
sampleproject = ">=1.3.1"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment