• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 20147226056

11 Dec 2025 08:58PM UTC coverage: 78.827% (-1.5%) from 80.293%
20147226056

push

github

web-flow
Forwarded the `style` and `complete-platform` args from pants.toml to PEX (#22910)

## Context

After Apple switched to the `arm64` architecture, some package
publishers stopped releasing `x86_64` variants of their packages for
`darwin`. As a result, generating a universal lockfile now fails because
no single package version is compatible with both `x86_64` and `arm64`
on `darwin`.

The solution is to use the `--style` and `--complete-platform` flags
with PEX. For example:
```
pex3 lock create \
    --style strict \
    --complete-platform 3rdparty/platforms/manylinux_2_28_aarch64.json \
    --complete-platform 3rdparty/platforms/macosx_26_0_arm64.json \
    -r 3rdparty/python/requirements_pyarrow.txt \
    -o python-pyarrow.lock
```

See the Slack discussion here:
https://pantsbuild.slack.com/archives/C046T6T9U/p1760098582461759

## Reproduction

* `BUILD`
```
python_requirement(
    name="awswrangler",
    requirements=["awswrangler==3.12.1"],
    resolve="awswrangler",
)
```
* Run `pants generate-lockfiles --resolve=awswrangler` on macOS with an
`arm64` CPU
```
pip: ERROR: Cannot install awswrangler==3.12.1 because these package versions have conflicting dependencies.
pip: ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts
pip:  
pip:  The conflict is caused by:
pip:      awswrangler 3.12.1 depends on pyarrow<18.0.0 and >=8.0.0; sys_platform == "darwin" and platform_machine == "x86_64"
pip:      awswrangler 3.12.1 depends on pyarrow<21.0.0 and >=18.0.0; sys_platform != "darwin" or platform_machine != "x86_64"
pip:  
pip:  Additionally, some packages in these conflicts have no matching distributions available for your environment:
pip:      pyarrow
pip:  
pip:  To fix this you could try to:
pip:  1. loosen the range of package versions you've specified
pip:  2. remove package versions to allow pip to attempt to solve the dependency conflict
```

## Implementation
... (continued)

77 of 100 new or added lines in 6 files covered. (77.0%)

868 existing lines in 42 files now uncovered.

74471 of 94474 relevant lines covered (78.83%)

3.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.22
/src/python/pants/backend/python/util_rules/lockfile_metadata.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
11✔
5

6
from collections.abc import Iterable
11✔
7
from dataclasses import dataclass
11✔
8
from enum import Enum
11✔
9
from typing import Any, cast
11✔
10

11
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
11✔
12
from pants.core.util_rules.lockfile_metadata import (
11✔
13
    LockfileMetadata,
14
    LockfileMetadataValidation,
15
    LockfileScope,
16
    _get_metadata,
17
    lockfile_metadata_registrar,
18
)
19
from pants.util.pip_requirement import PipRequirement
11✔
20

21
_python_lockfile_metadata = lockfile_metadata_registrar(LockfileScope.PYTHON)
11✔
22

23

24
class InvalidPythonLockfileReason(Enum):
11✔
25
    INVALIDATION_DIGEST_MISMATCH = "invalidation_digest_mismatch"
11✔
26
    INTERPRETER_CONSTRAINTS_MISMATCH = "interpreter_constraints_mismatch"
11✔
27
    REQUIREMENTS_MISMATCH = "requirements_mismatch"
11✔
28
    MANYLINUX_MISMATCH = "manylinux_mismatch"
11✔
29
    CONSTRAINTS_FILE_MISMATCH = "constraints_file_mismatch"
11✔
30
    ONLY_BINARY_MISMATCH = "only_binary_mismatch"
11✔
31
    NO_BINARY_MISMATCH = "no_binary_mismatch"
11✔
32
    EXCLUDES_MISMATCH = "excludes_mismatch"
11✔
33
    OVERRIDES_MISMATCH = "overrides_mismatch"
11✔
34
    SOURCES_MISMATCH = "sources_mismatch"
11✔
35
    LOCK_STYLE_MISMATCH = "lock_style_mismatch"
11✔
36
    COMPLETE_PLATFORMS_MISMATCH = "complete_platforms_mismatch"
11✔
37

38

39
@dataclass(frozen=True)
11✔
40
class PythonLockfileMetadata(LockfileMetadata):
11✔
41
    scope = LockfileScope.PYTHON
11✔
42

43
    valid_for_interpreter_constraints: InterpreterConstraints
11✔
44

45
    @staticmethod
11✔
46
    def new(
11✔
47
        *,
48
        valid_for_interpreter_constraints: InterpreterConstraints,
49
        requirements: set[PipRequirement],
50
        manylinux: str | None,
51
        requirement_constraints: set[PipRequirement],
52
        only_binary: set[str],
53
        no_binary: set[str],
54
        excludes: set[str],
55
        overrides: set[str],
56
        sources: set[str],
57
        lock_style: str,
58
        complete_platforms: tuple[str, ...],
59
    ) -> PythonLockfileMetadata:
60
        """Call the most recent version of the `LockfileMetadata` class to construct a concrete
61
        instance.
62

63
        This static method should be used in place of the `LockfileMetadata` constructor. This gives
64
        calling sites a predictable method to call to construct a new `LockfileMetadata` for
65
        writing, while still allowing us to support _reading_ older, deprecated metadata versions.
66
        """
67

68
        return PythonLockfileMetadataV6(
1✔
69
            valid_for_interpreter_constraints,
70
            requirements,
71
            manylinux=manylinux,
72
            requirement_constraints=requirement_constraints,
73
            only_binary=only_binary,
74
            no_binary=no_binary,
75
            excludes=excludes,
76
            overrides=overrides,
77
            sources=sources,
78
            lock_style=lock_style,
79
            complete_platforms=complete_platforms,
80
        )
81

82
    @staticmethod
11✔
83
    def metadata_location_for_lockfile(lockfile_location: str) -> str:
11✔
84
        return f"{lockfile_location}.metadata"
×
85

86
    @classmethod
11✔
87
    def additional_header_attrs(cls, instance: LockfileMetadata) -> dict[Any, Any]:
11✔
88
        instance = cast(PythonLockfileMetadata, instance)
1✔
89
        return {
1✔
90
            "valid_for_interpreter_constraints": [
91
                str(ic) for ic in instance.valid_for_interpreter_constraints
92
            ]
93
        }
94

95
    def is_valid_for(
11✔
96
        self,
97
        *,
98
        expected_invalidation_digest: str | None,
99
        user_interpreter_constraints: InterpreterConstraints,
100
        interpreter_universe: Iterable[str],
101
        user_requirements: Iterable[PipRequirement],
102
        manylinux: str | None,
103
        requirement_constraints: Iterable[PipRequirement],
104
        only_binary: Iterable[str],
105
        no_binary: Iterable[str],
106
        excludes: Iterable[str],
107
        overrides: Iterable[str],
108
        sources: Iterable[str],
109
        lock_style: str | None,
110
        complete_platforms: Iterable[str],
111
    ) -> LockfileMetadataValidation:
112
        """Returns Truthy if this `PythonLockfileMetadata` can be used in the current execution
113
        context."""
114

115
        raise NotImplementedError("call `is_valid_for` on subclasses only")
×
116

117

118
@_python_lockfile_metadata(1)
11✔
119
@dataclass(frozen=True)
11✔
120
class PythonLockfileMetadataV1(PythonLockfileMetadata):
11✔
121
    requirements_invalidation_digest: str
11✔
122

123
    @classmethod
11✔
124
    def _from_json_dict(
11✔
125
        cls: type[PythonLockfileMetadataV1],
126
        json_dict: dict[Any, Any],
127
        lockfile_description: str,
128
        error_suffix: str,
129
    ) -> PythonLockfileMetadataV1:
130
        metadata = _get_metadata(json_dict, lockfile_description, error_suffix)
1✔
131

132
        interpreter_constraints = metadata(
1✔
133
            "valid_for_interpreter_constraints", InterpreterConstraints, InterpreterConstraints
134
        )
135
        requirements_digest = metadata("requirements_invalidation_digest", str, None)
1✔
136

137
        return PythonLockfileMetadataV1(interpreter_constraints, requirements_digest)
1✔
138

139
    @classmethod
11✔
140
    def additional_header_attrs(cls, instance: LockfileMetadata) -> dict[Any, Any]:
11✔
141
        instance = cast(PythonLockfileMetadataV1, instance)
×
142
        return {"requirements_invalidation_digest": instance.requirements_invalidation_digest}
×
143

144
    def is_valid_for(
11✔
145
        self,
146
        *,
147
        expected_invalidation_digest: str | None,
148
        user_interpreter_constraints: InterpreterConstraints,
149
        interpreter_universe: Iterable[str],
150
        # Everything below is not used by v1.
151
        user_requirements: Iterable[PipRequirement],
152
        manylinux: str | None,
153
        requirement_constraints: Iterable[PipRequirement],
154
        only_binary: Iterable[str],
155
        no_binary: Iterable[str],
156
        excludes: Iterable[str],
157
        overrides: Iterable[str],
158
        sources: Iterable[str],
159
        lock_style: str | None,
160
        complete_platforms: Iterable[str],
161
    ) -> LockfileMetadataValidation:
UNCOV
162
        failure_reasons: set[InvalidPythonLockfileReason] = set()
×
163

UNCOV
164
        if expected_invalidation_digest is None:
×
165
            return LockfileMetadataValidation(failure_reasons)
×
166

UNCOV
167
        if self.requirements_invalidation_digest != expected_invalidation_digest:
×
UNCOV
168
            failure_reasons.add(InvalidPythonLockfileReason.INVALIDATION_DIGEST_MISMATCH)
×
169

UNCOV
170
        if not self.valid_for_interpreter_constraints.contains(
×
171
            user_interpreter_constraints, interpreter_universe
172
        ):
UNCOV
173
            failure_reasons.add(InvalidPythonLockfileReason.INTERPRETER_CONSTRAINTS_MISMATCH)
×
174

UNCOV
175
        return LockfileMetadataValidation(failure_reasons)
×
176

177

178
@_python_lockfile_metadata(2)
11✔
179
@dataclass(frozen=True)
11✔
180
class PythonLockfileMetadataV2(PythonLockfileMetadata):
11✔
181
    """Lockfile version that permits specifying a requirements as a set rather than a digest.
182

183
    Validity is tested by the set of requirements strings being the same in the user requirements as
184
    those in the stored requirements.
185
    """
186

187
    requirements: set[PipRequirement]
11✔
188

189
    @classmethod
11✔
190
    def _from_json_dict(
11✔
191
        cls: type[PythonLockfileMetadataV2],
192
        json_dict: dict[Any, Any],
193
        lockfile_description: str,
194
        error_suffix: str,
195
    ) -> PythonLockfileMetadataV2:
UNCOV
196
        metadata = _get_metadata(json_dict, lockfile_description, error_suffix)
×
197

UNCOV
198
        requirements = metadata(
×
199
            "generated_with_requirements",
200
            set[PipRequirement],
201
            lambda l: {
202
                PipRequirement.parse(i, description_of_origin=lockfile_description) for i in l
203
            },
204
        )
UNCOV
205
        interpreter_constraints = metadata(
×
206
            "valid_for_interpreter_constraints", InterpreterConstraints, InterpreterConstraints
207
        )
208

UNCOV
209
        return PythonLockfileMetadataV2(interpreter_constraints, requirements)
×
210

211
    @classmethod
11✔
212
    def additional_header_attrs(cls, instance: LockfileMetadata) -> dict[Any, Any]:
11✔
213
        instance = cast(PythonLockfileMetadataV2, instance)
1✔
214
        # Requirements need to be stringified then sorted so that tests are deterministic. Sorting
215
        # followed by stringifying does not produce a meaningful result.
216
        return {"generated_with_requirements": (sorted(str(i) for i in instance.requirements))}
1✔
217

218
    def is_valid_for(
11✔
219
        self,
220
        *,
221
        expected_invalidation_digest: str | None,  # Not used by V2.
222
        user_interpreter_constraints: InterpreterConstraints,
223
        interpreter_universe: Iterable[str],
224
        user_requirements: Iterable[PipRequirement],
225
        # Everything below is not used by V2.
226
        manylinux: str | None,
227
        requirement_constraints: Iterable[PipRequirement],
228
        only_binary: Iterable[str],
229
        no_binary: Iterable[str],
230
        excludes: Iterable[str],
231
        overrides: Iterable[str],
232
        sources: Iterable[str],
233
        lock_style: str | None,
234
        complete_platforms: Iterable[str],
235
    ) -> LockfileMetadataValidation:
236
        failure_reasons = set()
1✔
237
        if not set(user_requirements).issubset(self.requirements):
1✔
238
            failure_reasons.add(InvalidPythonLockfileReason.REQUIREMENTS_MISMATCH)
1✔
239

240
        if not self.valid_for_interpreter_constraints.contains(
1✔
241
            user_interpreter_constraints, interpreter_universe
242
        ):
243
            failure_reasons.add(InvalidPythonLockfileReason.INTERPRETER_CONSTRAINTS_MISMATCH)
1✔
244

245
        return LockfileMetadataValidation(failure_reasons)
1✔
246

247

248
@_python_lockfile_metadata(3)
11✔
249
@dataclass(frozen=True)
11✔
250
class PythonLockfileMetadataV3(PythonLockfileMetadataV2):
11✔
251
    """Lockfile version that considers constraints files."""
252

253
    manylinux: str | None
11✔
254
    requirement_constraints: set[PipRequirement]
11✔
255
    only_binary: set[str]
11✔
256
    no_binary: set[str]
11✔
257

258
    @classmethod
11✔
259
    def _from_json_dict(
11✔
260
        cls: type[PythonLockfileMetadataV3],
261
        json_dict: dict[Any, Any],
262
        lockfile_description: str,
263
        error_suffix: str,
264
    ) -> PythonLockfileMetadataV3:
UNCOV
265
        v2_metadata = super()._from_json_dict(json_dict, lockfile_description, error_suffix)
×
UNCOV
266
        metadata = _get_metadata(json_dict, lockfile_description, error_suffix)
×
UNCOV
267
        manylinux = metadata("manylinux", str, lambda l: l)
×
UNCOV
268
        requirement_constraints = metadata(
×
269
            "requirement_constraints",
270
            set[PipRequirement],
271
            lambda l: {
272
                PipRequirement.parse(i, description_of_origin=lockfile_description) for i in l
273
            },
274
        )
UNCOV
275
        only_binary = metadata("only_binary", set[str], lambda l: set(l))
×
UNCOV
276
        no_binary = metadata("no_binary", set[str], lambda l: set(l))
×
277

UNCOV
278
        return PythonLockfileMetadataV3(
×
279
            valid_for_interpreter_constraints=v2_metadata.valid_for_interpreter_constraints,
280
            requirements=v2_metadata.requirements,
281
            manylinux=manylinux,
282
            requirement_constraints=requirement_constraints,
283
            only_binary=only_binary,
284
            no_binary=no_binary,
285
        )
286

287
    @classmethod
11✔
288
    def additional_header_attrs(cls, instance: LockfileMetadata) -> dict[Any, Any]:
11✔
289
        instance = cast(PythonLockfileMetadataV3, instance)
1✔
290
        return {
1✔
291
            "manylinux": instance.manylinux,
292
            "requirement_constraints": sorted(str(i) for i in instance.requirement_constraints),
293
            "only_binary": sorted(instance.only_binary),
294
            "no_binary": sorted(instance.no_binary),
295
        }
296

297
    def is_valid_for(
11✔
298
        self,
299
        *,
300
        expected_invalidation_digest: str | None,  # Validation digests are not used by V2.
301
        user_interpreter_constraints: InterpreterConstraints,
302
        interpreter_universe: Iterable[str],
303
        user_requirements: Iterable[PipRequirement],
304
        manylinux: str | None,
305
        requirement_constraints: Iterable[PipRequirement],
306
        only_binary: Iterable[str],
307
        no_binary: Iterable[str],
308
        # not used for V3
309
        excludes: Iterable[str],
310
        overrides: Iterable[str],
311
        sources: Iterable[str],
312
        lock_style: str | None,
313
        complete_platforms: Iterable[str],
314
    ) -> LockfileMetadataValidation:
315
        failure_reasons = (
1✔
316
            super()
317
            .is_valid_for(
318
                expected_invalidation_digest=expected_invalidation_digest,
319
                user_interpreter_constraints=user_interpreter_constraints,
320
                interpreter_universe=interpreter_universe,
321
                user_requirements=user_requirements,
322
                manylinux=manylinux,
323
                requirement_constraints=requirement_constraints,
324
                only_binary=only_binary,
325
                no_binary=no_binary,
326
                excludes=excludes,
327
                overrides=overrides,
328
                sources=sources,
329
                lock_style=lock_style,
330
                complete_platforms=complete_platforms,
331
            )
332
            .failure_reasons
333
        )
334

335
        if self.manylinux != manylinux:
1✔
336
            failure_reasons.add(InvalidPythonLockfileReason.MANYLINUX_MISMATCH)
1✔
337
        if self.requirement_constraints != set(requirement_constraints):
1✔
338
            failure_reasons.add(InvalidPythonLockfileReason.CONSTRAINTS_FILE_MISMATCH)
1✔
339
        if self.only_binary != set(only_binary):
1✔
340
            failure_reasons.add(InvalidPythonLockfileReason.ONLY_BINARY_MISMATCH)
1✔
341
        if self.no_binary != set(no_binary):
1✔
342
            failure_reasons.add(InvalidPythonLockfileReason.NO_BINARY_MISMATCH)
1✔
343

344
        return LockfileMetadataValidation(failure_reasons)
1✔
345

346

347
@_python_lockfile_metadata(4)
11✔
348
@dataclass(frozen=True)
11✔
349
class PythonLockfileMetadataV4(PythonLockfileMetadataV3):
11✔
350
    """Lockfile version with excludes/overrides."""
351

352
    excludes: set[str]
11✔
353
    overrides: set[str]
11✔
354

355
    @classmethod
11✔
356
    def _from_json_dict(
11✔
357
        cls: type[PythonLockfileMetadataV4],
358
        json_dict: dict[Any, Any],
359
        lockfile_description: str,
360
        error_suffix: str,
361
    ) -> PythonLockfileMetadataV4:
UNCOV
362
        v3_metadata = super()._from_json_dict(json_dict, lockfile_description, error_suffix)
×
UNCOV
363
        metadata = _get_metadata(json_dict, lockfile_description, error_suffix)
×
364

UNCOV
365
        excludes = metadata("excludes", set[str], lambda l: set(l))
×
UNCOV
366
        overrides = metadata("overrides", set[str], lambda l: set(l))
×
367

UNCOV
368
        return PythonLockfileMetadataV4(
×
369
            valid_for_interpreter_constraints=v3_metadata.valid_for_interpreter_constraints,
370
            requirements=v3_metadata.requirements,
371
            manylinux=v3_metadata.manylinux,
372
            requirement_constraints=v3_metadata.requirement_constraints,
373
            only_binary=v3_metadata.only_binary,
374
            no_binary=v3_metadata.no_binary,
375
            excludes=excludes,
376
            overrides=overrides,
377
        )
378

379
    @classmethod
11✔
380
    def additional_header_attrs(cls, instance: LockfileMetadata) -> dict[Any, Any]:
11✔
381
        instance = cast(PythonLockfileMetadataV4, instance)
1✔
382
        return {
1✔
383
            "excludes": sorted(instance.excludes),
384
            "overrides": sorted(instance.overrides),
385
        }
386

387
    def is_valid_for(
11✔
388
        self,
389
        *,
390
        expected_invalidation_digest: str | None,
391
        user_interpreter_constraints: InterpreterConstraints,
392
        interpreter_universe: Iterable[str],
393
        user_requirements: Iterable[PipRequirement],
394
        manylinux: str | None,
395
        requirement_constraints: Iterable[PipRequirement],
396
        only_binary: Iterable[str],
397
        no_binary: Iterable[str],
398
        excludes: Iterable[str],
399
        overrides: Iterable[str],
400
        # not used for V4
401
        sources: Iterable[str],
402
        lock_style: str | None,
403
        complete_platforms: Iterable[str],
404
    ) -> LockfileMetadataValidation:
UNCOV
405
        failure_reasons = (
×
406
            super()
407
            .is_valid_for(
408
                expected_invalidation_digest=expected_invalidation_digest,
409
                user_interpreter_constraints=user_interpreter_constraints,
410
                interpreter_universe=interpreter_universe,
411
                user_requirements=user_requirements,
412
                manylinux=manylinux,
413
                requirement_constraints=requirement_constraints,
414
                only_binary=only_binary,
415
                no_binary=no_binary,
416
                excludes=excludes,
417
                overrides=overrides,
418
                sources=sources,
419
                lock_style=lock_style,
420
                complete_platforms=complete_platforms,
421
            )
422
            .failure_reasons
423
        )
424

UNCOV
425
        if self.excludes != set(excludes):
×
UNCOV
426
            failure_reasons.add(InvalidPythonLockfileReason.EXCLUDES_MISMATCH)
×
UNCOV
427
        if self.overrides != set(overrides):
×
UNCOV
428
            failure_reasons.add(InvalidPythonLockfileReason.OVERRIDES_MISMATCH)
×
429

UNCOV
430
        return LockfileMetadataValidation(failure_reasons)
×
431

432

433
@_python_lockfile_metadata(5)
11✔
434
@dataclass(frozen=True)
11✔
435
class PythonLockfileMetadataV5(PythonLockfileMetadataV4):
11✔
436
    """Lockfile version with sources."""
437

438
    sources: set[str]
11✔
439

440
    @classmethod
11✔
441
    def _from_json_dict(
11✔
442
        cls: type[PythonLockfileMetadataV5],
443
        json_dict: dict[Any, Any],
444
        lockfile_description: str,
445
        error_suffix: str,
446
    ) -> PythonLockfileMetadataV5:
UNCOV
447
        v4_metadata = PythonLockfileMetadataV4._from_json_dict(
×
448
            json_dict, lockfile_description, error_suffix
449
        )
UNCOV
450
        metadata = _get_metadata(json_dict, lockfile_description, error_suffix)
×
451

UNCOV
452
        sources = metadata("sources", set[str], lambda l: set(l))
×
453

UNCOV
454
        return PythonLockfileMetadataV5(
×
455
            valid_for_interpreter_constraints=v4_metadata.valid_for_interpreter_constraints,
456
            requirements=v4_metadata.requirements,
457
            manylinux=v4_metadata.manylinux,
458
            requirement_constraints=v4_metadata.requirement_constraints,
459
            only_binary=v4_metadata.only_binary,
460
            no_binary=v4_metadata.no_binary,
461
            excludes=v4_metadata.excludes,
462
            overrides=v4_metadata.overrides,
463
            sources=sources,
464
        )
465

466
    @classmethod
11✔
467
    def additional_header_attrs(cls, instance: LockfileMetadata) -> dict[Any, Any]:
11✔
468
        instance = cast(PythonLockfileMetadataV5, instance)
1✔
469
        return {
1✔
470
            "sources": sorted(instance.sources),
471
        }
472

473
    def is_valid_for(
11✔
474
        self,
475
        *,
476
        expected_invalidation_digest: str | None,
477
        user_interpreter_constraints: InterpreterConstraints,
478
        interpreter_universe: Iterable[str],
479
        user_requirements: Iterable[PipRequirement],
480
        manylinux: str | None,
481
        requirement_constraints: Iterable[PipRequirement],
482
        only_binary: Iterable[str],
483
        no_binary: Iterable[str],
484
        excludes: Iterable[str],
485
        overrides: Iterable[str],
486
        sources: Iterable[str],
487
        lock_style: str | None,
488
        complete_platforms: Iterable[str],
489
    ) -> LockfileMetadataValidation:
UNCOV
490
        failure_reasons = (
×
491
            super()
492
            .is_valid_for(
493
                expected_invalidation_digest=expected_invalidation_digest,
494
                user_interpreter_constraints=user_interpreter_constraints,
495
                interpreter_universe=interpreter_universe,
496
                user_requirements=user_requirements,
497
                manylinux=manylinux,
498
                requirement_constraints=requirement_constraints,
499
                only_binary=only_binary,
500
                no_binary=no_binary,
501
                excludes=excludes,
502
                overrides=overrides,
503
                sources=sources,
504
                lock_style=lock_style,
505
                complete_platforms=complete_platforms,
506
            )
507
            .failure_reasons
508
        )
509

UNCOV
510
        if self.sources != set(sources):
×
UNCOV
511
            failure_reasons.add(InvalidPythonLockfileReason.SOURCES_MISMATCH)
×
512

UNCOV
513
        return LockfileMetadataValidation(failure_reasons)
×
514

515

516
@_python_lockfile_metadata(6)
11✔
517
@dataclass(frozen=True)
11✔
518
class PythonLockfileMetadataV6(PythonLockfileMetadataV5):
11✔
519
    """Lockfile version with lock_style and complete_platforms."""
520

521
    lock_style: str | None
11✔
522
    complete_platforms: Iterable[str]
11✔
523

524
    @classmethod
11✔
525
    def _from_json_dict(
11✔
526
        cls: type[PythonLockfileMetadataV6],
527
        json_dict: dict[Any, Any],
528
        lockfile_description: str,
529
        error_suffix: str,
530
    ) -> PythonLockfileMetadataV6:
NEW
531
        v5_metadata = PythonLockfileMetadataV5._from_json_dict(
×
532
            json_dict, lockfile_description, error_suffix
533
        )
NEW
534
        metadata = _get_metadata(json_dict, lockfile_description, error_suffix)
×
535

NEW
536
        lock_style = metadata("lock_style", str, lambda l: l)
×
NEW
537
        complete_platforms = metadata("complete_platforms", tuple[str, ...], lambda l: tuple(l))
×
538

NEW
539
        return PythonLockfileMetadataV6(
×
540
            valid_for_interpreter_constraints=v5_metadata.valid_for_interpreter_constraints,
541
            requirements=v5_metadata.requirements,
542
            manylinux=v5_metadata.manylinux,
543
            requirement_constraints=v5_metadata.requirement_constraints,
544
            only_binary=v5_metadata.only_binary,
545
            no_binary=v5_metadata.no_binary,
546
            excludes=v5_metadata.excludes,
547
            overrides=v5_metadata.overrides,
548
            sources=v5_metadata.sources,
549
            lock_style=lock_style,
550
            complete_platforms=complete_platforms,
551
        )
552

553
    @classmethod
11✔
554
    def additional_header_attrs(cls, instance: LockfileMetadata) -> dict[Any, Any]:
11✔
555
        instance = cast(PythonLockfileMetadataV6, instance)
1✔
556
        return {
1✔
557
            "lock_style": instance.lock_style,
558
            "complete_platforms": sorted(instance.complete_platforms),
559
        }
560

561
    def is_valid_for(
11✔
562
        self,
563
        *,
564
        expected_invalidation_digest: str | None,
565
        user_interpreter_constraints: InterpreterConstraints,
566
        interpreter_universe: Iterable[str],
567
        user_requirements: Iterable[PipRequirement],
568
        manylinux: str | None,
569
        requirement_constraints: Iterable[PipRequirement],
570
        only_binary: Iterable[str],
571
        no_binary: Iterable[str],
572
        excludes: Iterable[str],
573
        overrides: Iterable[str],
574
        sources: Iterable[str],
575
        lock_style: str | None,
576
        complete_platforms: Iterable[str],
577
    ) -> LockfileMetadataValidation:
NEW
578
        failure_reasons = (
×
579
            super()
580
            .is_valid_for(
581
                expected_invalidation_digest=expected_invalidation_digest,
582
                user_interpreter_constraints=user_interpreter_constraints,
583
                interpreter_universe=interpreter_universe,
584
                user_requirements=user_requirements,
585
                manylinux=manylinux,
586
                requirement_constraints=requirement_constraints,
587
                only_binary=only_binary,
588
                no_binary=no_binary,
589
                excludes=excludes,
590
                overrides=overrides,
591
                sources=sources,
592
                lock_style=lock_style,
593
                complete_platforms=complete_platforms,
594
            )
595
            .failure_reasons
596
        )
597

NEW
598
        if self.lock_style != lock_style:
×
NEW
599
            failure_reasons.add(InvalidPythonLockfileReason.LOCK_STYLE_MISMATCH)
×
NEW
600
        if self.complete_platforms != complete_platforms:
×
NEW
601
            failure_reasons.add(InvalidPythonLockfileReason.COMPLETE_PLATFORMS_MISMATCH)
×
602

NEW
603
        return LockfileMetadataValidation(failure_reasons)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc