Skip to content

Commit ea4c56f

Browse files
committed
use tomli and tomliw to read and write lockfile
1 parent c061ac5 commit ea4c56f

File tree

8 files changed

+2908
-925
lines changed

8 files changed

+2908
-925
lines changed

poetry.lock

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

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ platformdirs = "^2.5.2"
6565
requests = "^2.18"
6666
requests-toolbelt = "^0.9.1"
6767
shellingham = "^1.5"
68+
tomli = "^2.0.1"
69+
tomli-w = "^1.0.0"
6870
# exclude 0.11.2 and 0.11.3 due to https://github.com/sdispater/tomlkit/issues/225
6971
tomlkit = ">=0.11.1,<1.0.0,!=0.11.2,!=0.11.3"
7072
trove-classifiers = "^2022.5.19"

src/poetry/console/commands/add.py

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

234234
# Refresh the locker
235235
self.poetry.set_locker(
236-
self.poetry.locker.__class__(self.poetry.locker.lock.path, poetry_content)
236+
self.poetry.locker.__class__(self.poetry.locker.lock, poetry_content)
237237
)
238238
self.installer.set_locker(self.poetry.locker)
239239

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

+33-51
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,22 @@
1111
from typing import Any
1212
from typing import cast
1313

14+
import tomli
15+
import tomli_w
16+
1417
from poetry.core.packages.dependency import Dependency
1518
from poetry.core.packages.package import Package
1619
from poetry.core.semver.helpers import parse_constraint
1720
from poetry.core.semver.version import Version
18-
from poetry.core.toml.file import TOMLFile
1921
from poetry.core.version.markers import parse_marker
2022
from poetry.core.version.requirements import InvalidRequirement
21-
from tomlkit import array
22-
from tomlkit import comment
23-
from tomlkit import document
24-
from tomlkit import inline_table
25-
from tomlkit import item
26-
from tomlkit import table
27-
from tomlkit.exceptions import TOMLKitError
28-
from tomlkit.items import Array
2923

3024

3125
if TYPE_CHECKING:
3226
from poetry.core.packages.directory_dependency import DirectoryDependency
3327
from poetry.core.packages.file_dependency import FileDependency
3428
from poetry.core.packages.url_dependency import URLDependency
3529
from poetry.core.packages.vcs_dependency import VCSDependency
36-
from tomlkit.items import Table
37-
from tomlkit.toml_document import TOMLDocument
3830

3931
from poetry.repositories.lockfile_repository import LockfileRepository
4032

@@ -54,17 +46,17 @@ class Locker:
5446
_relevant_keys = [*_legacy_keys, "group"]
5547

5648
def __init__(self, lock: str | Path, local_config: dict[str, Any]) -> None:
57-
self._lock = TOMLFile(lock)
49+
self._lock = lock if isinstance(lock, Path) else Path(lock)
5850
self._local_config = local_config
59-
self._lock_data: TOMLDocument | None = None
51+
self._lock_data: dict[str, Any] | None = None
6052
self._content_hash = self._get_content_hash()
6153

6254
@property
63-
def lock(self) -> TOMLFile:
55+
def lock(self) -> Path:
6456
return self._lock
6557

6658
@property
67-
def lock_data(self) -> TOMLDocument:
59+
def lock_data(self) -> dict[str, Any]:
6860
if self._lock_data is None:
6961
self._lock_data = self._get_lock_data()
7062

@@ -74,7 +66,7 @@ def is_locked(self) -> bool:
7466
"""
7567
Checks whether the locker has been locked (lockfile found).
7668
"""
77-
if not self._lock.exists():
69+
if not self.lock.exists():
7870
return False
7971

8072
return "package" in self.lock_data
@@ -83,7 +75,8 @@ def is_fresh(self) -> bool:
8375
"""
8476
Checks whether the lock file is still up to date with the current hash.
8577
"""
86-
lock = self._lock.read()
78+
with self.lock.open("rb") as f:
79+
lock = tomli.load(f)
8780
metadata = lock.get("metadata", {})
8881

8982
if "content-hash" in metadata:
@@ -115,7 +108,7 @@ def locked_repository(self) -> LockfileRepository:
115108
source_type = source.get("type")
116109
url = source.get("url")
117110
if source_type in ["directory", "file"]:
118-
url = self._lock.path.parent.joinpath(url).resolve().as_posix()
111+
url = self.lock.parent.joinpath(url).resolve().as_posix()
119112

120113
name = info["name"]
121114
package = Package(
@@ -199,7 +192,7 @@ def locked_repository(self) -> LockfileRepository:
199192
package.marker = parse_marker(split_dep[1].strip())
200193

201194
for dep_name, constraint in info.get("dependencies", {}).items():
202-
root_dir = self._lock.path.parent
195+
root_dir = self.lock.parent
203196
if package.source_type == "directory":
204197
# root dir should be the source of the package relative to the lock
205198
# path
@@ -226,27 +219,21 @@ def locked_repository(self) -> LockfileRepository:
226219
return repository
227220

228221
def set_lock_data(self, root: Package, packages: list[Package]) -> bool:
229-
files: dict[str, Any] = table()
222+
files: dict[str, Any] = {}
230223
package_specs = self._lock_packages(packages)
231224
# Retrieving hashes
232225
for package in package_specs:
233226
if package["name"] not in files:
234227
files[package["name"]] = []
235228

236229
for f in package["files"]:
237-
file_metadata = inline_table()
230+
file_metadata = {}
238231
for k, v in sorted(f.items()):
239232
file_metadata[k] = v
240233

241234
files[package["name"]].append(file_metadata)
242235

243-
if files[package["name"]]:
244-
package_files = item(files[package["name"]])
245-
assert isinstance(package_files, Array)
246-
files[package["name"]] = package_files.multiline(True)
247-
248-
lock = document()
249-
lock.add(comment(GENERATED_COMMENT))
236+
lock: dict[str, Any] = {}
250237
lock["package"] = package_specs
251238

252239
if root.extras:
@@ -268,12 +255,10 @@ def set_lock_data(self, root: Package, packages: list[Package]) -> bool:
268255

269256
return False
270257

271-
def _write_lock_data(self, data: TOMLDocument) -> None:
272-
self.lock.write(data)
273-
274-
# Checking lock file data consistency
275-
if data != self.lock.read():
276-
raise RuntimeError("Inconsistent lock file data.")
258+
def _write_lock_data(self, data: dict[str, Any]) -> None:
259+
with self.lock.open("wb") as f:
260+
f.write(f"# {GENERATED_COMMENT}\n\n".encode())
261+
tomli_w.dump(data, f)
277262

278263
self._lock_data = None
279264

@@ -294,16 +279,17 @@ def _get_content_hash(self) -> str:
294279

295280
return sha256(json.dumps(relevant_content, sort_keys=True).encode()).hexdigest()
296281

297-
def _get_lock_data(self) -> TOMLDocument:
298-
if not self._lock.exists():
282+
def _get_lock_data(self) -> dict[str, Any]:
283+
if not self.lock.exists():
299284
raise RuntimeError("No lockfile found. Unable to read locked packages")
300285

301-
try:
302-
lock_data: TOMLDocument = self._lock.read()
303-
except TOMLKitError as e:
304-
raise RuntimeError(f"Unable to read the lock file ({e}).")
286+
with self.lock.open("rb") as f:
287+
try:
288+
lock_data = tomli.load(f)
289+
except tomli.TOMLDecodeError as e:
290+
raise RuntimeError(f"Unable to read the lock file ({e}).")
305291

306-
metadata = cast("Table", lock_data["metadata"])
292+
metadata = lock_data["metadata"]
307293
lock_version = Version.parse(metadata.get("lock-version", "1.0"))
308294
current_version = Version.parse(self._VERSION)
309295
accepted_versions = parse_constraint(self._READ_VERSION_RANGE)
@@ -354,7 +340,7 @@ def _dump_package(self, package: Package) -> dict[str, Any]:
354340
if dependency.pretty_name not in dependencies:
355341
dependencies[dependency.pretty_name] = []
356342

357-
constraint = inline_table()
343+
constraint: dict[str, Any] = {}
358344

359345
if dependency.is_directory():
360346
dependency = cast("DirectoryDependency", dependency)
@@ -420,14 +406,10 @@ def _dump_package(self, package: Package) -> dict[str, Any]:
420406
}
421407

422408
if dependencies:
423-
data["dependencies"] = table()
424-
for k, constraints in dependencies.items():
425-
if len(constraints) == 1:
426-
data["dependencies"][k] = constraints[0]
427-
else:
428-
data["dependencies"][k] = array().multiline(True)
429-
for constraint in constraints:
430-
data["dependencies"][k].append(constraint)
409+
data["dependencies"] = {
410+
name: constraints[0] if len(constraints) == 1 else constraints
411+
for name, constraints in dependencies.items()
412+
}
431413

432414
if package.extras:
433415
extras = {}
@@ -443,7 +425,7 @@ def _dump_package(self, package: Package) -> dict[str, Any]:
443425
url = Path(
444426
os.path.relpath(
445427
Path(url).resolve(),
446-
Path(self._lock.path.parent).resolve(),
428+
Path(self.lock.parent).resolve(),
447429
)
448430
).as_posix()
449431

tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ def _factory(
416416
poetry = Factory().create_poetry(project_dir)
417417

418418
locker = TestLocker(
419-
poetry.locker.lock.path, locker_config or poetry.locker._local_config
419+
poetry.locker.lock, locker_config or poetry.locker._local_config
420420
)
421421
locker.write()
422422

tests/helpers.py

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

188188

0 commit comments

Comments
 (0)