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

pantsbuild / pants / 24462481292

15 Apr 2026 03:13PM UTC coverage: 89.757% (-3.2%) from 92.924%
24462481292

push

github

web-flow
update the default Pex version to v2.92.2 (#23260)

Release Notes:
 * https://github.com/pex-tool/pex/releases/tag/v2.92.2

3 of 3 new or added lines in 1 file covered. (100.0%)

2567 existing lines in 128 files now uncovered.

82405 of 91809 relevant lines covered (89.76%)

3.6 hits per line

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

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

4
from __future__ import annotations
5✔
5

6
import itertools
5✔
7
import logging
5✔
8
import os
5✔
9
from collections.abc import Callable, Iterable
5✔
10
from typing import cast
5✔
11

12
from packaging.utils import canonicalize_name as canonicalize_project_name
5✔
13

14
from pants.backend.python.goals.lockfile import synthetic_lockfile_target_name
5✔
15
from pants.backend.python.macros.common_fields import (
5✔
16
    ModuleMappingField,
17
    TypeStubsModuleMappingField,
18
)
19
from pants.backend.python.subsystems.setup import PythonSetup
5✔
20
from pants.backend.python.target_types import (
5✔
21
    PythonRequirementModulesField,
22
    PythonRequirementResolveField,
23
    PythonRequirementsField,
24
    PythonRequirementTarget,
25
    PythonRequirementTypeStubModulesField,
26
)
27
from pants.core.target_types import (
5✔
28
    TargetGeneratorSourcesHelperSourcesField,
29
    TargetGeneratorSourcesHelperTarget,
30
)
31
from pants.engine.addresses import Address
5✔
32
from pants.engine.fs import GlobMatchErrorBehavior, PathGlobs
5✔
33
from pants.engine.internals.build_files import find_target_adaptor
5✔
34
from pants.engine.internals.target_adaptor import TargetAdaptorRequest
5✔
35
from pants.engine.intrinsics import get_digest_contents
5✔
36
from pants.engine.rules import implicitly
5✔
37
from pants.engine.target import (
5✔
38
    Dependencies,
39
    GenerateTargetsRequest,
40
    InvalidFieldException,
41
    SingleSourceField,
42
)
43
from pants.engine.unions import UnionMembership
5✔
44
from pants.util.pip_requirement import PipRequirement
5✔
45
from pants.util.strutil import softwrap
5✔
46

47
logger = logging.getLogger(__name__)
5✔
48
ParseRequirementsCallback = Callable[[bytes, str], Iterable[PipRequirement]]
5✔
49

50

51
async def _generate_requirements(
5✔
52
    request: GenerateTargetsRequest,
53
    union_membership: UnionMembership,
54
    python_setup: PythonSetup,
55
    parse_requirements_callback: ParseRequirementsCallback,
56
) -> Iterable[PythonRequirementTarget]:
57
    generator = request.generator
3✔
58
    requirements_rel_path = generator[SingleSourceField].value
3✔
59
    requirements_full_path = generator[SingleSourceField].file_path
3✔
60
    overrides = {
3✔
61
        canonicalize_project_name(k): v
62
        for k, v in request.require_unparametrized_overrides().items()
63
    }
64

65
    # Pretend this is just another generated target, for typing purposes.
66
    file_tgt = cast(
3✔
67
        "PythonRequirementTarget",
68
        TargetGeneratorSourcesHelperTarget(
69
            {TargetGeneratorSourcesHelperSourcesField.alias: requirements_rel_path},
70
            Address(
71
                request.template_address.spec_path,
72
                target_name=request.template_address.target_name,
73
                relative_file_path=requirements_rel_path,
74
            ),
75
            union_membership,
76
        ),
77
    )
78

79
    req_deps = [file_tgt.address.spec]
3✔
80

81
    resolve = request.template.get(
3✔
82
        PythonRequirementResolveField.alias, python_setup.default_resolve
83
    )
84
    lockfile = (
3✔
85
        python_setup.resolves.get(resolve) if python_setup.enable_synthetic_lockfiles else None
86
    )
87
    if lockfile:
3✔
88
        lockfile_address = Address(
2✔
89
            os.path.dirname(lockfile),
90
            target_name=synthetic_lockfile_target_name(resolve),
91
        )
92
        target_adaptor = await find_target_adaptor(
2✔
93
            TargetAdaptorRequest(
94
                description_of_origin=f"{generator.alias} lockfile dep for the {resolve} resolve",
95
                address=lockfile_address,
96
            ),
97
        )
98
        if target_adaptor.type_alias == "_lockfiles":
2✔
99
            req_deps.append(f"{lockfile}:{synthetic_lockfile_target_name(resolve)}")
2✔
100
        else:
UNCOV
101
            logger.warning(
×
102
                softwrap(
103
                    f"""
104
                    The synthetic lockfile target for {lockfile} is being shadowed by the
105
                    {target_adaptor.type_alias} target {lockfile_address}.
106

107
                    There will not be any dependency to the lockfile.
108

109
                    Resolve by either renaming the shadowing target, the resolve {resolve!r} or
110
                    moving the target or the lockfile to another directory.
111
                    """
112
                )
113
            )
114

115
    digest_contents = await get_digest_contents(
3✔
116
        **implicitly(
117
            PathGlobs(
118
                [requirements_full_path],
119
                glob_match_error_behavior=GlobMatchErrorBehavior.error,
120
                description_of_origin=f"{generator}'s field `{SingleSourceField.alias}`",
121
            )
122
        )
123
    )
124

125
    module_mapping = generator[ModuleMappingField].value
3✔
126
    stubs_mapping = generator[TypeStubsModuleMappingField].value
3✔
127

128
    def generate_tgt(
3✔
129
        project_name: str, parsed_reqs: Iterable[PipRequirement]
130
    ) -> PythonRequirementTarget:
131
        normalized_proj_name = canonicalize_project_name(project_name)
3✔
132
        tgt_overrides = overrides.pop(normalized_proj_name, {})
3✔
133
        if Dependencies.alias in tgt_overrides:
3✔
134
            tgt_overrides = tgt_overrides | {  # type: ignore[operator]
1✔
135
                Dependencies.alias: list(tgt_overrides[Dependencies.alias]) + req_deps
136
            }
137

138
        return PythonRequirementTarget(
3✔
139
            {
140
                **request.template,
141
                PythonRequirementsField.alias: list(parsed_reqs),
142
                PythonRequirementModulesField.alias: module_mapping.get(normalized_proj_name),
143
                PythonRequirementTypeStubModulesField.alias: stubs_mapping.get(
144
                    normalized_proj_name
145
                ),
146
                # This may get overridden by `tgt_overrides`, which will have already added in
147
                # the file tgt.
148
                Dependencies.alias: req_deps,
149
                **tgt_overrides,
150
            },
151
            request.template_address.create_generated(project_name),
152
            union_membership,
153
        )
154

155
    requirements = parse_requirements_callback(digest_contents[0].content, requirements_full_path)
3✔
156
    grouped_requirements = itertools.groupby(requirements, lambda parsed_req: parsed_req.name)
3✔
157
    result = tuple(
3✔
158
        generate_tgt(project_name, parsed_reqs_)
159
        for project_name, parsed_reqs_ in grouped_requirements
160
    ) + (file_tgt,)
161

162
    if overrides:
3✔
163
        raise InvalidFieldException(
×
164
            softwrap(
165
                f"""
166
                Unused key in the `overrides` field for {request.template_address}:
167
                {sorted(overrides)}
168
                """
169
            )
170
        )
171

172
    return result
3✔
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

© 2026 Coveralls, Inc