pip 25.3__py3-none-any.whl → 26.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. pip/__init__.py +1 -1
  2. pip/_internal/build_env.py +194 -5
  3. pip/_internal/cli/base_command.py +11 -0
  4. pip/_internal/cli/cmdoptions.py +157 -0
  5. pip/_internal/cli/index_command.py +20 -0
  6. pip/_internal/cli/main.py +11 -6
  7. pip/_internal/cli/main_parser.py +3 -1
  8. pip/_internal/cli/parser.py +93 -33
  9. pip/_internal/cli/progress_bars.py +4 -2
  10. pip/_internal/cli/req_command.py +99 -23
  11. pip/_internal/commands/cache.py +24 -0
  12. pip/_internal/commands/completion.py +2 -1
  13. pip/_internal/commands/download.py +8 -4
  14. pip/_internal/commands/index.py +13 -6
  15. pip/_internal/commands/install.py +36 -29
  16. pip/_internal/commands/list.py +14 -16
  17. pip/_internal/commands/lock.py +16 -8
  18. pip/_internal/commands/wheel.py +8 -13
  19. pip/_internal/exceptions.py +76 -3
  20. pip/_internal/index/collector.py +2 -3
  21. pip/_internal/index/package_finder.py +84 -18
  22. pip/_internal/locations/__init__.py +1 -2
  23. pip/_internal/locations/_sysconfig.py +4 -1
  24. pip/_internal/models/link.py +18 -14
  25. pip/_internal/models/release_control.py +92 -0
  26. pip/_internal/models/selection_prefs.py +6 -3
  27. pip/_internal/network/auth.py +6 -2
  28. pip/_internal/network/download.py +4 -5
  29. pip/_internal/network/session.py +14 -10
  30. pip/_internal/operations/install/wheel.py +1 -2
  31. pip/_internal/operations/prepare.py +2 -3
  32. pip/_internal/req/constructors.py +3 -1
  33. pip/_internal/req/pep723.py +41 -0
  34. pip/_internal/req/req_file.py +10 -1
  35. pip/_internal/resolution/resolvelib/factory.py +12 -1
  36. pip/_internal/resolution/resolvelib/requirements.py +7 -3
  37. pip/_internal/self_outdated_check.py +6 -13
  38. pip/_internal/utils/datetime.py +18 -0
  39. pip/_internal/utils/filesystem.py +40 -1
  40. pip/_internal/utils/logging.py +34 -2
  41. pip/_internal/utils/misc.py +18 -12
  42. pip/_internal/utils/pylock.py +116 -0
  43. pip/_internal/utils/unpacking.py +1 -1
  44. pip/_internal/vcs/versioncontrol.py +3 -1
  45. pip/_vendor/cachecontrol/__init__.py +6 -3
  46. pip/_vendor/cachecontrol/adapter.py +0 -1
  47. pip/_vendor/cachecontrol/controller.py +1 -1
  48. pip/_vendor/cachecontrol/filewrapper.py +3 -1
  49. pip/_vendor/certifi/__init__.py +1 -1
  50. pip/_vendor/certifi/cacert.pem +0 -332
  51. pip/_vendor/idna/LICENSE.md +1 -1
  52. pip/_vendor/idna/codec.py +1 -1
  53. pip/_vendor/idna/core.py +1 -1
  54. pip/_vendor/idna/idnadata.py +72 -6
  55. pip/_vendor/idna/package_data.py +1 -1
  56. pip/_vendor/idna/uts46data.py +891 -731
  57. pip/_vendor/packaging/__init__.py +1 -1
  58. pip/_vendor/packaging/_elffile.py +0 -1
  59. pip/_vendor/packaging/_manylinux.py +36 -36
  60. pip/_vendor/packaging/_musllinux.py +1 -1
  61. pip/_vendor/packaging/_parser.py +22 -10
  62. pip/_vendor/packaging/_structures.py +8 -0
  63. pip/_vendor/packaging/_tokenizer.py +23 -25
  64. pip/_vendor/packaging/licenses/__init__.py +13 -11
  65. pip/_vendor/packaging/licenses/_spdx.py +41 -1
  66. pip/_vendor/packaging/markers.py +64 -38
  67. pip/_vendor/packaging/metadata.py +143 -27
  68. pip/_vendor/packaging/pylock.py +635 -0
  69. pip/_vendor/packaging/requirements.py +5 -10
  70. pip/_vendor/packaging/specifiers.py +219 -170
  71. pip/_vendor/packaging/tags.py +15 -20
  72. pip/_vendor/packaging/utils.py +19 -24
  73. pip/_vendor/packaging/version.py +315 -105
  74. pip/_vendor/platformdirs/version.py +2 -2
  75. pip/_vendor/platformdirs/windows.py +7 -1
  76. pip/_vendor/vendor.txt +5 -5
  77. {pip-25.3.dist-info → pip-26.0.dist-info}/METADATA +2 -2
  78. {pip-25.3.dist-info → pip-26.0.dist-info}/RECORD +103 -100
  79. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/AUTHORS.txt +18 -0
  80. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
  81. pip/_internal/models/pylock.py +0 -188
  82. {pip-25.3.dist-info → pip-26.0.dist-info}/WHEEL +0 -0
  83. {pip-25.3.dist-info → pip-26.0.dist-info}/entry_points.txt +0 -0
  84. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/LICENSE.txt +0 -0
  85. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
  86. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
  87. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +0 -0
  88. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
  89. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
  90. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
  91. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
  92. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
  93. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
  94. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
  95. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
  96. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
  97. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
  98. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
  99. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
  100. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
  101. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
  102. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
  103. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
  104. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
@@ -0,0 +1,635 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import logging
5
+ import re
6
+ from collections.abc import Mapping, Sequence
7
+ from dataclasses import dataclass
8
+ from datetime import datetime
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ Callable,
13
+ Protocol,
14
+ TypeVar,
15
+ )
16
+
17
+ from .markers import Marker
18
+ from .specifiers import SpecifierSet
19
+ from .utils import NormalizedName, is_normalized_name
20
+ from .version import Version
21
+
22
+ if TYPE_CHECKING: # pragma: no cover
23
+ from pathlib import Path
24
+
25
+ from typing_extensions import Self
26
+
27
+ _logger = logging.getLogger(__name__)
28
+
29
+ __all__ = [
30
+ "Package",
31
+ "PackageArchive",
32
+ "PackageDirectory",
33
+ "PackageSdist",
34
+ "PackageVcs",
35
+ "PackageWheel",
36
+ "Pylock",
37
+ "PylockUnsupportedVersionError",
38
+ "PylockValidationError",
39
+ "is_valid_pylock_path",
40
+ ]
41
+
42
+ _T = TypeVar("_T")
43
+ _T2 = TypeVar("_T2")
44
+
45
+
46
+ class _FromMappingProtocol(Protocol): # pragma: no cover
47
+ @classmethod
48
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
49
+
50
+
51
+ _FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
52
+
53
+
54
+ _PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
55
+
56
+
57
+ def is_valid_pylock_path(path: Path) -> bool:
58
+ """Check if the given path is a valid pylock file path."""
59
+ return path.name == "pylock.toml" or bool(_PYLOCK_FILE_NAME_RE.match(path.name))
60
+
61
+
62
+ def _toml_key(key: str) -> str:
63
+ return key.replace("_", "-")
64
+
65
+
66
+ def _toml_value(key: str, value: Any) -> Any: # noqa: ANN401
67
+ if isinstance(value, (Version, Marker, SpecifierSet)):
68
+ return str(value)
69
+ if isinstance(value, Sequence) and key == "environments":
70
+ return [str(v) for v in value]
71
+ return value
72
+
73
+
74
+ def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
75
+ return {
76
+ _toml_key(key): _toml_value(key, value)
77
+ for key, value in data
78
+ if value is not None
79
+ }
80
+
81
+
82
+ def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
83
+ """Get a value from the dictionary and verify it's the expected type."""
84
+ if (value := d.get(key)) is None:
85
+ return None
86
+ if not isinstance(value, expected_type):
87
+ raise PylockValidationError(
88
+ f"Unexpected type {type(value).__name__} "
89
+ f"(expected {expected_type.__name__})",
90
+ context=key,
91
+ )
92
+ return value
93
+
94
+
95
+ def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
96
+ """Get a required value from the dictionary and verify it's the expected type."""
97
+ if (value := _get(d, expected_type, key)) is None:
98
+ raise _PylockRequiredKeyError(key)
99
+ return value
100
+
101
+
102
+ def _get_sequence(
103
+ d: Mapping[str, Any], expected_item_type: type[_T], key: str
104
+ ) -> Sequence[_T] | None:
105
+ """Get a list value from the dictionary and verify it's the expected items type."""
106
+ if (value := _get(d, Sequence, key)) is None: # type: ignore[type-abstract]
107
+ return None
108
+ if isinstance(value, (str, bytes)):
109
+ # special case: str and bytes are Sequences, but we want to reject it
110
+ raise PylockValidationError(
111
+ f"Unexpected type {type(value).__name__} (expected Sequence)",
112
+ context=key,
113
+ )
114
+ for i, item in enumerate(value):
115
+ if not isinstance(item, expected_item_type):
116
+ raise PylockValidationError(
117
+ f"Unexpected type {type(item).__name__} "
118
+ f"(expected {expected_item_type.__name__})",
119
+ context=f"{key}[{i}]",
120
+ )
121
+ return value
122
+
123
+
124
+ def _get_as(
125
+ d: Mapping[str, Any],
126
+ expected_type: type[_T],
127
+ target_type: Callable[[_T], _T2],
128
+ key: str,
129
+ ) -> _T2 | None:
130
+ """Get a value from the dictionary, verify it's the expected type,
131
+ and convert to the target type.
132
+
133
+ This assumes the target_type constructor accepts the value.
134
+ """
135
+ if (value := _get(d, expected_type, key)) is None:
136
+ return None
137
+ try:
138
+ return target_type(value)
139
+ except Exception as e:
140
+ raise PylockValidationError(e, context=key) from e
141
+
142
+
143
+ def _get_required_as(
144
+ d: Mapping[str, Any],
145
+ expected_type: type[_T],
146
+ target_type: Callable[[_T], _T2],
147
+ key: str,
148
+ ) -> _T2:
149
+ """Get a required value from the dict, verify it's the expected type,
150
+ and convert to the target type."""
151
+ if (value := _get_as(d, expected_type, target_type, key)) is None:
152
+ raise _PylockRequiredKeyError(key)
153
+ return value
154
+
155
+
156
+ def _get_sequence_as(
157
+ d: Mapping[str, Any],
158
+ expected_item_type: type[_T],
159
+ target_item_type: Callable[[_T], _T2],
160
+ key: str,
161
+ ) -> list[_T2] | None:
162
+ """Get list value from dictionary and verify expected items type."""
163
+ if (value := _get_sequence(d, expected_item_type, key)) is None:
164
+ return None
165
+ result = []
166
+ try:
167
+ for item in value:
168
+ typed_item = target_item_type(item)
169
+ result.append(typed_item)
170
+ except Exception as e:
171
+ raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
172
+ return result
173
+
174
+
175
+ def _get_object(
176
+ d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
177
+ ) -> _FromMappingProtocolT | None:
178
+ """Get a dictionary value from the dictionary and convert it to a dataclass."""
179
+ if (value := _get(d, Mapping, key)) is None: # type: ignore[type-abstract]
180
+ return None
181
+ try:
182
+ return target_type._from_dict(value)
183
+ except Exception as e:
184
+ raise PylockValidationError(e, context=key) from e
185
+
186
+
187
+ def _get_sequence_of_objects(
188
+ d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
189
+ ) -> list[_FromMappingProtocolT] | None:
190
+ """Get a list value from the dictionary and convert its items to a dataclass."""
191
+ if (value := _get_sequence(d, Mapping, key)) is None: # type: ignore[type-abstract]
192
+ return None
193
+ result: list[_FromMappingProtocolT] = []
194
+ try:
195
+ for item in value:
196
+ typed_item = target_item_type._from_dict(item)
197
+ result.append(typed_item)
198
+ except Exception as e:
199
+ raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
200
+ return result
201
+
202
+
203
+ def _get_required_sequence_of_objects(
204
+ d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
205
+ ) -> Sequence[_FromMappingProtocolT]:
206
+ """Get a required list value from the dictionary and convert its items to a
207
+ dataclass."""
208
+ if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:
209
+ raise _PylockRequiredKeyError(key)
210
+ return result
211
+
212
+
213
+ def _validate_normalized_name(name: str) -> NormalizedName:
214
+ """Validate that a string is a NormalizedName."""
215
+ if not is_normalized_name(name):
216
+ raise PylockValidationError(f"Name {name!r} is not normalized")
217
+ return NormalizedName(name)
218
+
219
+
220
+ def _validate_path_url(path: str | None, url: str | None) -> None:
221
+ if not path and not url:
222
+ raise PylockValidationError("path or url must be provided")
223
+
224
+
225
+ def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:
226
+ if not hashes:
227
+ raise PylockValidationError("At least one hash must be provided")
228
+ if not all(isinstance(hash_val, str) for hash_val in hashes.values()):
229
+ raise PylockValidationError("Hash values must be strings")
230
+ return hashes
231
+
232
+
233
+ class PylockValidationError(Exception):
234
+ """Raised when when input data is not spec-compliant."""
235
+
236
+ context: str | None = None
237
+ message: str
238
+
239
+ def __init__(
240
+ self,
241
+ cause: str | Exception,
242
+ *,
243
+ context: str | None = None,
244
+ ) -> None:
245
+ if isinstance(cause, PylockValidationError):
246
+ if cause.context:
247
+ self.context = (
248
+ f"{context}.{cause.context}" if context else cause.context
249
+ )
250
+ else:
251
+ self.context = context
252
+ self.message = cause.message
253
+ else:
254
+ self.context = context
255
+ self.message = str(cause)
256
+
257
+ def __str__(self) -> str:
258
+ if self.context:
259
+ return f"{self.message} in {self.context!r}"
260
+ return self.message
261
+
262
+
263
+ class _PylockRequiredKeyError(PylockValidationError):
264
+ def __init__(self, key: str) -> None:
265
+ super().__init__("Missing required value", context=key)
266
+
267
+
268
+ class PylockUnsupportedVersionError(PylockValidationError):
269
+ """Raised when encountering an unsupported `lock_version`."""
270
+
271
+
272
+ @dataclass(frozen=True, init=False)
273
+ class PackageVcs:
274
+ type: str
275
+ url: str | None = None
276
+ path: str | None = None
277
+ requested_revision: str | None = None
278
+ commit_id: str # type: ignore[misc]
279
+ subdirectory: str | None = None
280
+
281
+ def __init__(
282
+ self,
283
+ *,
284
+ type: str,
285
+ url: str | None = None,
286
+ path: str | None = None,
287
+ requested_revision: str | None = None,
288
+ commit_id: str,
289
+ subdirectory: str | None = None,
290
+ ) -> None:
291
+ # In Python 3.10+ make dataclass kw_only=True and remove __init__
292
+ object.__setattr__(self, "type", type)
293
+ object.__setattr__(self, "url", url)
294
+ object.__setattr__(self, "path", path)
295
+ object.__setattr__(self, "requested_revision", requested_revision)
296
+ object.__setattr__(self, "commit_id", commit_id)
297
+ object.__setattr__(self, "subdirectory", subdirectory)
298
+
299
+ @classmethod
300
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
301
+ package_vcs = cls(
302
+ type=_get_required(d, str, "type"),
303
+ url=_get(d, str, "url"),
304
+ path=_get(d, str, "path"),
305
+ requested_revision=_get(d, str, "requested-revision"),
306
+ commit_id=_get_required(d, str, "commit-id"),
307
+ subdirectory=_get(d, str, "subdirectory"),
308
+ )
309
+ _validate_path_url(package_vcs.path, package_vcs.url)
310
+ return package_vcs
311
+
312
+
313
+ @dataclass(frozen=True, init=False)
314
+ class PackageDirectory:
315
+ path: str
316
+ editable: bool | None = None
317
+ subdirectory: str | None = None
318
+
319
+ def __init__(
320
+ self,
321
+ *,
322
+ path: str,
323
+ editable: bool | None = None,
324
+ subdirectory: str | None = None,
325
+ ) -> None:
326
+ # In Python 3.10+ make dataclass kw_only=True and remove __init__
327
+ object.__setattr__(self, "path", path)
328
+ object.__setattr__(self, "editable", editable)
329
+ object.__setattr__(self, "subdirectory", subdirectory)
330
+
331
+ @classmethod
332
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
333
+ return cls(
334
+ path=_get_required(d, str, "path"),
335
+ editable=_get(d, bool, "editable"),
336
+ subdirectory=_get(d, str, "subdirectory"),
337
+ )
338
+
339
+
340
+ @dataclass(frozen=True, init=False)
341
+ class PackageArchive:
342
+ url: str | None = None
343
+ path: str | None = None
344
+ size: int | None = None
345
+ upload_time: datetime | None = None
346
+ hashes: Mapping[str, str] # type: ignore[misc]
347
+ subdirectory: str | None = None
348
+
349
+ def __init__(
350
+ self,
351
+ *,
352
+ url: str | None = None,
353
+ path: str | None = None,
354
+ size: int | None = None,
355
+ upload_time: datetime | None = None,
356
+ hashes: Mapping[str, str],
357
+ subdirectory: str | None = None,
358
+ ) -> None:
359
+ # In Python 3.10+ make dataclass kw_only=True and remove __init__
360
+ object.__setattr__(self, "url", url)
361
+ object.__setattr__(self, "path", path)
362
+ object.__setattr__(self, "size", size)
363
+ object.__setattr__(self, "upload_time", upload_time)
364
+ object.__setattr__(self, "hashes", hashes)
365
+ object.__setattr__(self, "subdirectory", subdirectory)
366
+
367
+ @classmethod
368
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
369
+ package_archive = cls(
370
+ url=_get(d, str, "url"),
371
+ path=_get(d, str, "path"),
372
+ size=_get(d, int, "size"),
373
+ upload_time=_get(d, datetime, "upload-time"),
374
+ hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
375
+ subdirectory=_get(d, str, "subdirectory"),
376
+ )
377
+ _validate_path_url(package_archive.path, package_archive.url)
378
+ return package_archive
379
+
380
+
381
+ @dataclass(frozen=True, init=False)
382
+ class PackageSdist:
383
+ name: str | None = None
384
+ upload_time: datetime | None = None
385
+ url: str | None = None
386
+ path: str | None = None
387
+ size: int | None = None
388
+ hashes: Mapping[str, str] # type: ignore[misc]
389
+
390
+ def __init__(
391
+ self,
392
+ *,
393
+ name: str | None = None,
394
+ upload_time: datetime | None = None,
395
+ url: str | None = None,
396
+ path: str | None = None,
397
+ size: int | None = None,
398
+ hashes: Mapping[str, str],
399
+ ) -> None:
400
+ # In Python 3.10+ make dataclass kw_only=True and remove __init__
401
+ object.__setattr__(self, "name", name)
402
+ object.__setattr__(self, "upload_time", upload_time)
403
+ object.__setattr__(self, "url", url)
404
+ object.__setattr__(self, "path", path)
405
+ object.__setattr__(self, "size", size)
406
+ object.__setattr__(self, "hashes", hashes)
407
+
408
+ @classmethod
409
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
410
+ package_sdist = cls(
411
+ name=_get(d, str, "name"),
412
+ upload_time=_get(d, datetime, "upload-time"),
413
+ url=_get(d, str, "url"),
414
+ path=_get(d, str, "path"),
415
+ size=_get(d, int, "size"),
416
+ hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
417
+ )
418
+ _validate_path_url(package_sdist.path, package_sdist.url)
419
+ return package_sdist
420
+
421
+
422
+ @dataclass(frozen=True, init=False)
423
+ class PackageWheel:
424
+ name: str | None = None
425
+ upload_time: datetime | None = None
426
+ url: str | None = None
427
+ path: str | None = None
428
+ size: int | None = None
429
+ hashes: Mapping[str, str] # type: ignore[misc]
430
+
431
+ def __init__(
432
+ self,
433
+ *,
434
+ name: str | None = None,
435
+ upload_time: datetime | None = None,
436
+ url: str | None = None,
437
+ path: str | None = None,
438
+ size: int | None = None,
439
+ hashes: Mapping[str, str],
440
+ ) -> None:
441
+ # In Python 3.10+ make dataclass kw_only=True and remove __init__
442
+ object.__setattr__(self, "name", name)
443
+ object.__setattr__(self, "upload_time", upload_time)
444
+ object.__setattr__(self, "url", url)
445
+ object.__setattr__(self, "path", path)
446
+ object.__setattr__(self, "size", size)
447
+ object.__setattr__(self, "hashes", hashes)
448
+
449
+ @classmethod
450
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
451
+ package_wheel = cls(
452
+ name=_get(d, str, "name"),
453
+ upload_time=_get(d, datetime, "upload-time"),
454
+ url=_get(d, str, "url"),
455
+ path=_get(d, str, "path"),
456
+ size=_get(d, int, "size"),
457
+ hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
458
+ )
459
+ _validate_path_url(package_wheel.path, package_wheel.url)
460
+ return package_wheel
461
+
462
+
463
+ @dataclass(frozen=True, init=False)
464
+ class Package:
465
+ name: NormalizedName
466
+ version: Version | None = None
467
+ marker: Marker | None = None
468
+ requires_python: SpecifierSet | None = None
469
+ dependencies: Sequence[Mapping[str, Any]] | None = None
470
+ vcs: PackageVcs | None = None
471
+ directory: PackageDirectory | None = None
472
+ archive: PackageArchive | None = None
473
+ index: str | None = None
474
+ sdist: PackageSdist | None = None
475
+ wheels: Sequence[PackageWheel] | None = None
476
+ attestation_identities: Sequence[Mapping[str, Any]] | None = None
477
+ tool: Mapping[str, Any] | None = None
478
+
479
+ def __init__(
480
+ self,
481
+ *,
482
+ name: NormalizedName,
483
+ version: Version | None = None,
484
+ marker: Marker | None = None,
485
+ requires_python: SpecifierSet | None = None,
486
+ dependencies: Sequence[Mapping[str, Any]] | None = None,
487
+ vcs: PackageVcs | None = None,
488
+ directory: PackageDirectory | None = None,
489
+ archive: PackageArchive | None = None,
490
+ index: str | None = None,
491
+ sdist: PackageSdist | None = None,
492
+ wheels: Sequence[PackageWheel] | None = None,
493
+ attestation_identities: Sequence[Mapping[str, Any]] | None = None,
494
+ tool: Mapping[str, Any] | None = None,
495
+ ) -> None:
496
+ # In Python 3.10+ make dataclass kw_only=True and remove __init__
497
+ object.__setattr__(self, "name", name)
498
+ object.__setattr__(self, "version", version)
499
+ object.__setattr__(self, "marker", marker)
500
+ object.__setattr__(self, "requires_python", requires_python)
501
+ object.__setattr__(self, "dependencies", dependencies)
502
+ object.__setattr__(self, "vcs", vcs)
503
+ object.__setattr__(self, "directory", directory)
504
+ object.__setattr__(self, "archive", archive)
505
+ object.__setattr__(self, "index", index)
506
+ object.__setattr__(self, "sdist", sdist)
507
+ object.__setattr__(self, "wheels", wheels)
508
+ object.__setattr__(self, "attestation_identities", attestation_identities)
509
+ object.__setattr__(self, "tool", tool)
510
+
511
+ @classmethod
512
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
513
+ package = cls(
514
+ name=_get_required_as(d, str, _validate_normalized_name, "name"),
515
+ version=_get_as(d, str, Version, "version"),
516
+ requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
517
+ dependencies=_get_sequence(d, Mapping, "dependencies"), # type: ignore[type-abstract]
518
+ marker=_get_as(d, str, Marker, "marker"),
519
+ vcs=_get_object(d, PackageVcs, "vcs"),
520
+ directory=_get_object(d, PackageDirectory, "directory"),
521
+ archive=_get_object(d, PackageArchive, "archive"),
522
+ index=_get(d, str, "index"),
523
+ sdist=_get_object(d, PackageSdist, "sdist"),
524
+ wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
525
+ attestation_identities=_get_sequence(d, Mapping, "attestation-identities"), # type: ignore[type-abstract]
526
+ tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
527
+ )
528
+ distributions = bool(package.sdist) + len(package.wheels or [])
529
+ direct_urls = (
530
+ bool(package.vcs) + bool(package.directory) + bool(package.archive)
531
+ )
532
+ if distributions > 0 and direct_urls > 0:
533
+ raise PylockValidationError(
534
+ "None of vcs, directory, archive must be set if sdist or wheels are set"
535
+ )
536
+ if distributions == 0 and direct_urls != 1:
537
+ raise PylockValidationError(
538
+ "Exactly one of vcs, directory, archive must be set "
539
+ "if sdist and wheels are not set"
540
+ )
541
+ try:
542
+ for i, attestation_identity in enumerate( # noqa: B007
543
+ package.attestation_identities or []
544
+ ):
545
+ _get_required(attestation_identity, str, "kind")
546
+ except Exception as e:
547
+ raise PylockValidationError(
548
+ e, context=f"attestation-identities[{i}]"
549
+ ) from e
550
+ return package
551
+
552
+ @property
553
+ def is_direct(self) -> bool:
554
+ return not (self.sdist or self.wheels)
555
+
556
+
557
+ @dataclass(frozen=True, init=False)
558
+ class Pylock:
559
+ """A class representing a pylock file."""
560
+
561
+ lock_version: Version
562
+ environments: Sequence[Marker] | None = None
563
+ requires_python: SpecifierSet | None = None
564
+ extras: Sequence[NormalizedName] | None = None
565
+ dependency_groups: Sequence[str] | None = None
566
+ default_groups: Sequence[str] | None = None
567
+ created_by: str # type: ignore[misc]
568
+ packages: Sequence[Package] # type: ignore[misc]
569
+ tool: Mapping[str, Any] | None = None
570
+
571
+ def __init__(
572
+ self,
573
+ *,
574
+ lock_version: Version,
575
+ environments: Sequence[Marker] | None = None,
576
+ requires_python: SpecifierSet | None = None,
577
+ extras: Sequence[NormalizedName] | None = None,
578
+ dependency_groups: Sequence[str] | None = None,
579
+ default_groups: Sequence[str] | None = None,
580
+ created_by: str,
581
+ packages: Sequence[Package],
582
+ tool: Mapping[str, Any] | None = None,
583
+ ) -> None:
584
+ # In Python 3.10+ make dataclass kw_only=True and remove __init__
585
+ object.__setattr__(self, "lock_version", lock_version)
586
+ object.__setattr__(self, "environments", environments)
587
+ object.__setattr__(self, "requires_python", requires_python)
588
+ object.__setattr__(self, "extras", extras)
589
+ object.__setattr__(self, "dependency_groups", dependency_groups)
590
+ object.__setattr__(self, "default_groups", default_groups)
591
+ object.__setattr__(self, "created_by", created_by)
592
+ object.__setattr__(self, "packages", packages)
593
+ object.__setattr__(self, "tool", tool)
594
+
595
+ @classmethod
596
+ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
597
+ pylock = cls(
598
+ lock_version=_get_required_as(d, str, Version, "lock-version"),
599
+ environments=_get_sequence_as(d, str, Marker, "environments"),
600
+ extras=_get_sequence_as(d, str, _validate_normalized_name, "extras"),
601
+ dependency_groups=_get_sequence(d, str, "dependency-groups"),
602
+ default_groups=_get_sequence(d, str, "default-groups"),
603
+ created_by=_get_required(d, str, "created-by"),
604
+ requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
605
+ packages=_get_required_sequence_of_objects(d, Package, "packages"),
606
+ tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
607
+ )
608
+ if not Version("1") <= pylock.lock_version < Version("2"):
609
+ raise PylockUnsupportedVersionError(
610
+ f"pylock version {pylock.lock_version} is not supported"
611
+ )
612
+ if pylock.lock_version > Version("1.0"):
613
+ _logger.warning(
614
+ "pylock minor version %s is not supported", pylock.lock_version
615
+ )
616
+ return pylock
617
+
618
+ @classmethod
619
+ def from_dict(cls, d: Mapping[str, Any], /) -> Self:
620
+ """Create and validate a Pylock instance from a TOML dictionary.
621
+
622
+ Raises :class:`PylockValidationError` if the input data is not
623
+ spec-compliant.
624
+ """
625
+ return cls._from_dict(d)
626
+
627
+ def to_dict(self) -> Mapping[str, Any]:
628
+ """Convert the Pylock instance to a TOML dictionary."""
629
+ return dataclasses.asdict(self, dict_factory=_toml_dict_factory)
630
+
631
+ def validate(self) -> None:
632
+ """Validate the Pylock instance against the specification.
633
+
634
+ Raises :class:`PylockValidationError` otherwise."""
635
+ self.from_dict(self.to_dict())
@@ -3,7 +3,7 @@
3
3
  # for complete details.
4
4
  from __future__ import annotations
5
5
 
6
- from typing import Any, Iterator
6
+ from typing import Iterator
7
7
 
8
8
  from ._parser import parse_requirement as _parse_requirement
9
9
  from ._tokenizer import ParserSyntaxError
@@ -57,7 +57,7 @@ class Requirement:
57
57
  yield str(self.specifier)
58
58
 
59
59
  if self.url:
60
- yield f"@ {self.url}"
60
+ yield f" @ {self.url}"
61
61
  if self.marker:
62
62
  yield " "
63
63
 
@@ -68,17 +68,12 @@ class Requirement:
68
68
  return "".join(self._iter_parts(self.name))
69
69
 
70
70
  def __repr__(self) -> str:
71
- return f"<Requirement('{self}')>"
71
+ return f"<{self.__class__.__name__}('{self}')>"
72
72
 
73
73
  def __hash__(self) -> int:
74
- return hash(
75
- (
76
- self.__class__.__name__,
77
- *self._iter_parts(canonicalize_name(self.name)),
78
- )
79
- )
74
+ return hash(tuple(self._iter_parts(canonicalize_name(self.name))))
80
75
 
81
- def __eq__(self, other: Any) -> bool:
76
+ def __eq__(self, other: object) -> bool:
82
77
  if not isinstance(other, Requirement):
83
78
  return NotImplemented
84
79