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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

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

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import itertools
×
UNCOV
7
import json
×
UNCOV
8
import logging
×
UNCOV
9
from collections.abc import Mapping
×
UNCOV
10
from dataclasses import dataclass
×
UNCOV
11
from typing import Any
×
12

UNCOV
13
from packaging.version import Version, parse
×
14

UNCOV
15
from pants.backend.python.util_rules.pex_requirements import (
×
16
    LoadedLockfileRequest,
17
    Lockfile,
18
    load_lockfile,
19
    strip_comments_from_pex_json_lockfile,
20
)
UNCOV
21
from pants.base.exceptions import EngineError
×
UNCOV
22
from pants.core.goals.generate_lockfiles import LockfileDiff, LockfilePackages, PackageName
×
UNCOV
23
from pants.engine.fs import Digest
×
UNCOV
24
from pants.engine.intrinsics import get_digest_contents
×
UNCOV
25
from pants.engine.rules import implicitly
×
UNCOV
26
from pants.util.frozendict import FrozenDict
×
27

UNCOV
28
logger = logging.getLogger(__name__)
×
29

30

UNCOV
31
@dataclass(frozen=True, order=True)
×
UNCOV
32
class PythonRequirementVersion:
×
UNCOV
33
    _parsed: Version
×
34

UNCOV
35
    @classmethod
×
UNCOV
36
    def parse(cls, version: str) -> PythonRequirementVersion:
×
37
        return cls(parse(version))
×
38

UNCOV
39
    def __str__(self) -> str:
×
UNCOV
40
        return str(self._parsed)
×
41

UNCOV
42
    def __getattr__(self, key: str) -> Any:
×
43
        return getattr(self._parsed, key)
×
44

45

UNCOV
46
def _pex_lockfile_requirements(
×
47
    lockfile_data: Mapping[str, Any] | None, path: str | None = None
48
) -> LockfilePackages:
49
    if not lockfile_data:
×
50
        return LockfilePackages({})
×
51

52
    try:
×
53
        # Setup generators
54
        locked_resolves = (
×
55
            (
56
                (PackageName(r["project_name"]), PythonRequirementVersion.parse(r["version"]))
57
                for r in resolve["locked_requirements"]
58
            )
59
            for resolve in lockfile_data["locked_resolves"]
60
        )
61
        requirements = dict(itertools.chain.from_iterable(locked_resolves))
×
62
    except KeyError as e:
×
63
        if path:
×
64
            logger.warning(f"{path}: Failed to parse lockfile: {e}")
×
65

66
        requirements = {}
×
67

68
    return LockfilePackages(requirements)
×
69

70

UNCOV
71
async def _parse_lockfile(lockfile: Lockfile) -> FrozenDict[str, Any] | None:
×
72
    try:
×
73
        loaded = await load_lockfile(LoadedLockfileRequest(lockfile), **implicitly())
×
74
        fc = await get_digest_contents(loaded.lockfile_digest)
×
75
        parsed = await _parse_lockfile_content(next(iter(fc)).content, lockfile.url)
×
76
        return parsed
×
77
    except EngineError:
×
78
        # May fail in case the file doesn't exist, which is expected when parsing the "old" lockfile
79
        # the first time a new lockfile is generated.
80
        return None
×
81

82

UNCOV
83
async def _parse_lockfile_content(content: bytes, url: str) -> FrozenDict[str, Any] | None:
×
84
    try:
×
85
        parsed_lockfile = json.loads(content)
×
86
        return FrozenDict.deep_freeze(parsed_lockfile)
×
87
    except json.JSONDecodeError as e:
×
88
        logger.debug(f"{url}: Failed to parse lockfile contents: {e}")
×
89
        return None
×
90

91

UNCOV
92
async def _generate_python_lockfile_diff(
×
93
    digest: Digest, resolve_name: str, path: str
94
) -> LockfileDiff:
95
    new_digest_contents = await get_digest_contents(digest)
×
96
    new_content = next(c for c in new_digest_contents if c.path == path).content
×
97
    new_content = strip_comments_from_pex_json_lockfile(new_content)
×
98
    new = await _parse_lockfile_content(new_content, path)
×
99
    old = await _parse_lockfile(
×
100
        Lockfile(
101
            url=path,
102
            url_description_of_origin="existing lockfile",
103
            resolve_name=resolve_name,
104
        )
105
    )
106
    return LockfileDiff.create(
×
107
        path=path,
108
        resolve_name=resolve_name,
109
        old=_pex_lockfile_requirements(old),
110
        new=_pex_lockfile_requirements(new, path),
111
    )
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