Skip to content

Commit b28339d

Browse files
dimblebyradoering
andauthored
Use tomli for reading the lock file (#6562)
Co-authored-by: Randy Döring <[email protected]>
1 parent 3a961df commit b28339d

File tree

9 files changed

+76
-48
lines changed

9 files changed

+76
-48
lines changed

poetry.lock

+10-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ generate-setup-file = false
4848
python = "^3.7"
4949

5050
poetry-core = "^1.3.2"
51-
poetry-plugin-export = "^1.1.2"
51+
poetry-plugin-export = "^1.2.0"
5252
"backports.cached-property" = { version = "^1.0.2", python = "<3.8" }
5353
cachecontrol = { version = "^0.12.9", extras = ["filecache"] }
5454
cleo = "^1.0.0a5"
@@ -67,6 +67,7 @@ platformdirs = "^2.5.2"
6767
requests = "^2.18"
6868
requests-toolbelt = ">=0.9.1,<0.11.0"
6969
shellingham = "^1.5"
70+
tomli = { version = "^2.0.1", python = "<3.11" }
7071
# exclude 0.11.2 and 0.11.3 due to https://github.com/sdispater/tomlkit/issues/225
7172
tomlkit = ">=0.11.1,<1.0.0,!=0.11.2,!=0.11.3"
7273
trove-classifiers = "^2022.5.19"

src/poetry/console/commands/add.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ def handle(self) -> int:
241241

242242
# Refresh the locker
243243
self.poetry.set_locker(
244-
self.poetry.locker.__class__(self.poetry.locker.lock.path, poetry_content)
244+
self.poetry.locker.__class__(self.poetry.locker.lock, poetry_content)
245245
)
246246
self.installer.set_locker(self.poetry.locker)
247247

src/poetry/console/commands/remove.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def handle(self) -> int:
103103

104104
# Refresh the locker
105105
self.poetry.set_locker(
106-
self.poetry.locker.__class__(self.poetry.locker.lock.path, poetry_content)
106+
self.poetry.locker.__class__(self.poetry.locker.lock, poetry_content)
107107
)
108108
self.installer.set_locker(self.poetry.locker)
109109

src/poetry/packages/locker.py

+21-22
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@
2424
from tomlkit import document
2525
from tomlkit import inline_table
2626
from tomlkit import table
27-
from tomlkit.exceptions import TOMLKitError
27+
28+
from poetry.utils._compat import tomllib
2829

2930

3031
if TYPE_CHECKING:
3132
from poetry.core.packages.directory_dependency import DirectoryDependency
3233
from poetry.core.packages.file_dependency import FileDependency
3334
from poetry.core.packages.url_dependency import URLDependency
3435
from poetry.core.packages.vcs_dependency import VCSDependency
35-
from tomlkit.items import Table
3636
from tomlkit.toml_document import TOMLDocument
3737

3838
from poetry.repositories.lockfile_repository import LockfileRepository
@@ -53,17 +53,17 @@ class Locker:
5353
_relevant_keys = [*_legacy_keys, "group"]
5454

5555
def __init__(self, lock: str | Path, local_config: dict[str, Any]) -> None:
56-
self._lock = TOMLFile(lock)
56+
self._lock = lock if isinstance(lock, Path) else Path(lock)
5757
self._local_config = local_config
58-
self._lock_data: TOMLDocument | None = None
58+
self._lock_data: dict[str, Any] | None = None
5959
self._content_hash = self._get_content_hash()
6060

6161
@property
62-
def lock(self) -> TOMLFile:
62+
def lock(self) -> Path:
6363
return self._lock
6464

6565
@property
66-
def lock_data(self) -> TOMLDocument:
66+
def lock_data(self) -> dict[str, Any]:
6767
if self._lock_data is None:
6868
self._lock_data = self._get_lock_data()
6969

@@ -79,7 +79,8 @@ def is_fresh(self) -> bool:
7979
"""
8080
Checks whether the lock file is still up to date with the current hash.
8181
"""
82-
lock = self._lock.read()
82+
with self.lock.open("rb") as f:
83+
lock = tomllib.load(f)
8384
metadata = lock.get("metadata", {})
8485

8586
if "content-hash" in metadata:
@@ -111,7 +112,7 @@ def locked_repository(self) -> LockfileRepository:
111112
source_type = source.get("type")
112113
url = source.get("url")
113114
if source_type in ["directory", "file"]:
114-
url = self._lock.path.parent.joinpath(url).resolve().as_posix()
115+
url = self.lock.parent.joinpath(url).resolve().as_posix()
115116

116117
name = info["name"]
117118
package = Package(
@@ -196,7 +197,7 @@ def locked_repository(self) -> LockfileRepository:
196197
package.marker = parse_marker(split_dep[1].strip())
197198

198199
for dep_name, constraint in info.get("dependencies", {}).items():
199-
root_dir = self._lock.path.parent
200+
root_dir = self.lock.parent
200201
if package.source_type == "directory":
201202
# root dir should be the source of the package relative to the lock
202203
# path
@@ -267,11 +268,8 @@ def set_lock_data(self, root: Package, packages: list[Package]) -> bool:
267268
return do_write
268269

269270
def _write_lock_data(self, data: TOMLDocument) -> None:
270-
self.lock.write(data)
271-
272-
# Checking lock file data consistency
273-
if data != self.lock.read():
274-
raise RuntimeError("Inconsistent lock file data.")
271+
lockfile = TOMLFile(self.lock)
272+
lockfile.write(data)
275273

276274
self._lock_data = None
277275

@@ -292,16 +290,17 @@ def _get_content_hash(self) -> str:
292290

293291
return sha256(json.dumps(relevant_content, sort_keys=True).encode()).hexdigest()
294292

295-
def _get_lock_data(self) -> TOMLDocument:
296-
if not self._lock.exists():
293+
def _get_lock_data(self) -> dict[str, Any]:
294+
if not self.lock.exists():
297295
raise RuntimeError("No lockfile found. Unable to read locked packages")
298296

299-
try:
300-
lock_data: TOMLDocument = self._lock.read()
301-
except TOMLKitError as e:
302-
raise RuntimeError(f"Unable to read the lock file ({e}).")
297+
with self.lock.open("rb") as f:
298+
try:
299+
lock_data = tomllib.load(f)
300+
except tomllib.TOMLDecodeError as e:
301+
raise RuntimeError(f"Unable to read the lock file ({e}).")
303302

304-
metadata = cast("Table", lock_data["metadata"])
303+
metadata = lock_data["metadata"]
305304
lock_version = Version.parse(metadata.get("lock-version", "1.0"))
306305
current_version = Version.parse(self._VERSION)
307306
accepted_versions = parse_constraint(self._READ_VERSION_RANGE)
@@ -441,7 +440,7 @@ def _dump_package(self, package: Package) -> dict[str, Any]:
441440
url = Path(
442441
os.path.relpath(
443442
Path(url).resolve(),
444-
Path(self._lock.path.parent).resolve(),
443+
Path(self.lock.parent).resolve(),
445444
)
446445
).as_posix()
447446

src/poetry/utils/_compat.py

+9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77

88
# TODO: use try/except ImportError when
99
# https://github.com/python/mypy/issues/1393 is fixed
10+
11+
if sys.version_info < (3, 11):
12+
# compatibility for python <3.11
13+
import tomli as tomllib
14+
else:
15+
import tomllib # nopycln: import
16+
17+
1018
if sys.version_info < (3, 10):
1119
# compatibility for python <3.10
1220
import importlib_metadata as metadata
@@ -67,4 +75,5 @@ def list_to_shell_command(cmd: list[str]) -> str:
6775
"list_to_shell_command",
6876
"metadata",
6977
"to_str",
78+
"tomllib",
7079
]

tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ def _factory(
412412
poetry = Factory().create_poetry(project_dir)
413413

414414
locker = TestLocker(
415-
poetry.locker.lock.path, locker_config or poetry.locker._local_config
415+
poetry.locker.lock, locker_config or poetry.locker._local_config
416416
)
417417
locker.write()
418418

tests/helpers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def reset_poetry(self) -> None:
181181
self._poetry.set_pool(poetry.pool)
182182
self._poetry.set_config(poetry.config)
183183
self._poetry.set_locker(
184-
TestLocker(poetry.locker.lock.path, self._poetry.local_config)
184+
TestLocker(poetry.locker.lock, self._poetry.local_config)
185185
)
186186

187187

tests/packages/test_locker.py

+30-11
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ def test_locker_properly_loads_extras(locker: Locker):
231231
content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77"
232232
""" # noqa: E800
233233

234-
locker.lock.write(tomlkit.parse(content))
234+
data = tomlkit.parse(content)
235+
with open(locker.lock, "w", encoding="utf-8") as f:
236+
f.write(data.as_string())
235237

236238
packages = locker.locked_repository().packages
237239

@@ -294,7 +296,9 @@ def test_locker_properly_loads_nested_extras(locker: Locker):
294296
content-hash = "123456789"
295297
""" # noqa: E800
296298

297-
locker.lock.write(tomlkit.parse(content))
299+
data = tomlkit.parse(content)
300+
with open(locker.lock, "w", encoding="utf-8") as f:
301+
f.write(data.as_string())
298302

299303
repository = locker.locked_repository()
300304
assert len(repository.packages) == 3
@@ -359,7 +363,9 @@ def test_locker_properly_loads_extras_legacy(locker: Locker):
359363
content-hash = "123456789"
360364
""" # noqa: E800
361365

362-
locker.lock.write(tomlkit.parse(content))
366+
data = tomlkit.parse(content)
367+
with open(locker.lock, "w", encoding="utf-8") as f:
368+
f.write(data.as_string())
363369

364370
repository = locker.locked_repository()
365371
assert len(repository.packages) == 2
@@ -399,7 +405,9 @@ def test_locker_properly_loads_subdir(locker: Locker) -> None:
399405
python-versions = "*"
400406
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
401407
"""
402-
locker.lock.write(tomlkit.parse(content))
408+
data = tomlkit.parse(content)
409+
with open(locker.lock, "w", encoding="utf-8") as f:
410+
f.write(data.as_string())
403411

404412
repository = locker.locked_repository()
405413
assert len(repository.packages) == 1
@@ -495,7 +503,9 @@ def test_locker_properly_assigns_metadata_files(locker: Locker) -> None:
495503
{file = "demo-1.0-py3-none-any.whl", hash = "sha256"},
496504
]
497505
"""
498-
locker.lock.write(tomlkit.parse(content))
506+
data = tomlkit.parse(content)
507+
with open(locker.lock, "w", encoding="utf-8") as f:
508+
f.write(data.as_string())
499509

500510
repository = locker.locked_repository()
501511
assert len(repository.packages) == 5
@@ -687,7 +697,9 @@ def test_locker_should_emit_warnings_if_lock_version_is_newer_but_allowed(
687697
"""
688698
caplog.set_level(logging.WARNING, logger="poetry.packages.locker")
689699

690-
locker.lock.write(tomlkit.parse(content))
700+
data = tomlkit.parse(content)
701+
with open(locker.lock, "w", encoding="utf-8") as f:
702+
f.write(data.as_string())
691703

692704
_ = locker.lock_data
693705

@@ -717,7 +729,9 @@ def test_locker_should_raise_an_error_if_lock_version_is_newer_and_not_allowed(
717729
""" # noqa: E800
718730
caplog.set_level(logging.WARNING, logger="poetry.packages.locker")
719731

720-
locker.lock.write(tomlkit.parse(content))
732+
data = tomlkit.parse(content)
733+
with open(locker.lock, "w", encoding="utf-8") as f:
734+
f.write(data.as_string())
721735

722736
with pytest.raises(RuntimeError, match="^The lock file is not compatible"):
723737
_ = locker.lock_data
@@ -775,7 +789,9 @@ def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatibl
775789
"""
776790
caplog.set_level(logging.WARNING, logger="poetry.packages.locker")
777791

778-
locker.lock.write(tomlkit.parse(content))
792+
data = tomlkit.parse(content)
793+
with open(locker.lock, "w", encoding="utf-8") as f:
794+
f.write(data.as_string())
779795

780796
_ = locker.lock_data
781797

@@ -970,7 +986,10 @@ def test_locked_repository_uses_root_dir_of_package(
970986
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
971987
""" # noqa: E800
972988

973-
locker.lock.write(tomlkit.parse(content))
989+
data = tomlkit.parse(content)
990+
with open(locker.lock, "w", encoding="utf-8") as f:
991+
f.write(data.as_string())
992+
974993
create_dependency_patch = mocker.patch(
975994
"poetry.factory.Factory.create_dependency", autospec=True
976995
)
@@ -983,7 +1002,7 @@ def test_locked_repository_uses_root_dir_of_package(
9831002
root_dir = call_kwargs["root_dir"]
9841003
assert root_dir.match("*/lib/libA")
9851004
# relative_to raises an exception if not relative - is_relative_to comes in py3.9
986-
assert root_dir.relative_to(locker.lock.path.parent.resolve()) is not None
1005+
assert root_dir.relative_to(locker.lock.parent.resolve()) is not None
9871006

9881007

9891008
@pytest.mark.parametrize(
@@ -1017,7 +1036,7 @@ def test_content_hash_with_legacy_is_compatible(
10171036
relevant_content[key] = local_config.get(key)
10181037

10191038
locker = locker.__class__(
1020-
lock=locker.lock.path,
1039+
lock=locker.lock,
10211040
local_config=local_config,
10221041
)
10231042

0 commit comments

Comments
 (0)