• 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/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

UNCOV
4
from __future__ import annotations
×
5

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

UNCOV
12
from packaging.utils import canonicalize_name as canonicalize_project_name
×
13

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

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

50

UNCOV
51
async def _generate_requirements(
×
52
    request: GenerateTargetsRequest,
53
    union_membership: UnionMembership,
54
    python_setup: PythonSetup,
55
    parse_requirements_callback: ParseRequirementsCallback,
56
) -> Iterable[PythonRequirementTarget]:
57
    generator = request.generator
×
58
    requirements_rel_path = generator[SingleSourceField].value
×
59
    requirements_full_path = generator[SingleSourceField].file_path
×
60
    overrides = {
×
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(
×
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]
×
80

81
    resolve = request.template.get(
×
82
        PythonRequirementResolveField.alias, python_setup.default_resolve
83
    )
84
    lockfile = (
×
85
        python_setup.resolves.get(resolve) if python_setup.enable_synthetic_lockfiles else None
86
    )
87
    if lockfile:
×
88
        lockfile_address = Address(
×
89
            os.path.dirname(lockfile),
90
            target_name=synthetic_lockfile_target_name(resolve),
91
        )
92
        target_adaptor = await find_target_adaptor(
×
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":
×
99
            req_deps.append(f"{lockfile}:{synthetic_lockfile_target_name(resolve)}")
×
100
        else:
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(
×
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
×
126
    stubs_mapping = generator[TypeStubsModuleMappingField].value
×
127

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

138
        return PythonRequirementTarget(
×
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)
×
156
    grouped_requirements = itertools.groupby(requirements, lambda parsed_req: parsed_req.name)
×
157
    result = tuple(
×
158
        generate_tgt(project_name, parsed_reqs_)
159
        for project_name, parsed_reqs_ in grouped_requirements
160
    ) + (file_tgt,)
161

162
    if overrides:
×
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
×
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