• 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/goals/run_python_requirement.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 logging
×
UNCOV
7
import os
×
UNCOV
8
from collections import defaultdict
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from typing import TypeAlias
×
11

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

UNCOV
14
from pants.backend.python.dependency_inference.module_mapper import (
×
15
    ResolveName,
16
    ThirdPartyPythonModuleMapping,
17
)
UNCOV
18
from pants.backend.python.subsystems.setup import PythonSetup
×
UNCOV
19
from pants.backend.python.target_types import (
×
20
    EntryPoint,
21
    PythonRequirementDependenciesField,
22
    PythonRequirementEntryPointField,
23
    PythonRequirementModulesField,
24
    PythonRequirementResolveField,
25
    PythonRequirementsField,
26
    PythonRequirementTypeStubModulesField,
27
    ResolvePexEntryPointRequest,
28
)
UNCOV
29
from pants.backend.python.target_types_rules import resolve_pex_entry_point
×
UNCOV
30
from pants.backend.python.util_rules.pex import VenvPexRequest, create_venv_pex
×
UNCOV
31
from pants.backend.python.util_rules.pex_environment import PexEnvironment
×
UNCOV
32
from pants.backend.python.util_rules.pex_from_targets import (
×
33
    PexFromTargetsRequest,
34
    create_pex_from_targets,
35
)
UNCOV
36
from pants.build_graph.address import Address
×
UNCOV
37
from pants.core.goals.run import RunFieldSet, RunInSandboxBehavior, RunRequest
×
UNCOV
38
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
39
from pants.util.frozendict import FrozenDict
×
UNCOV
40
from pants.util.logging import LogLevel
×
UNCOV
41
from pants.util.memo import memoized
×
42

UNCOV
43
logger = logging.getLogger(__name__)
×
44

UNCOV
45
InvertedModuleMapping: TypeAlias = FrozenDict[Address, tuple[str, ...]]
×
46

47

UNCOV
48
def _in_chroot(relpath: str) -> str:
×
49
    return os.path.join("{chroot}", relpath)
×
50

51

UNCOV
52
@dataclass(frozen=True)
×
UNCOV
53
class PythonRequirementFieldSet(RunFieldSet):
×
UNCOV
54
    supports_debug_adapter = False
×
UNCOV
55
    run_in_sandbox_behavior = RunInSandboxBehavior.RUN_REQUEST_HERMETIC
×
56

UNCOV
57
    required_fields = (
×
58
        PythonRequirementsField,
59
        PythonRequirementDependenciesField,
60
        PythonRequirementModulesField,
61
        PythonRequirementTypeStubModulesField,
62
        PythonRequirementResolveField,
63
        PythonRequirementEntryPointField,
64
    )
65

UNCOV
66
    requirements: PythonRequirementsField
×
UNCOV
67
    dependencies: PythonRequirementDependenciesField
×
UNCOV
68
    modules: PythonRequirementModulesField
×
UNCOV
69
    resolve: PythonRequirementResolveField
×
UNCOV
70
    entry_point: PythonRequirementEntryPointField
×
71

72

UNCOV
73
@memoized
×
UNCOV
74
def _invert_module_mapping(
×
75
    resolve: ResolveName, module_mapping: ThirdPartyPythonModuleMapping
76
) -> InvertedModuleMapping:
77
    """Provide an inverted module mapping that can specify the set of modules known to be fulfilled
78
    by a given target address."""
79
    d: dict[Address, list[str]] = defaultdict(list)
×
80
    modules_to_providers = module_mapping.resolves_to_modules_to_providers.get(resolve)
×
81
    if not modules_to_providers:
×
82
        return FrozenDict()
×
83

84
    for module_name, providers in modules_to_providers.items():
×
85
        for provider in providers:
×
86
            d[provider.addr].append(module_name)
×
87

88
    return FrozenDict((address, tuple(modules)) for address, modules in d.items())
×
89

90

UNCOV
91
async def _resolve_entry_point(
×
92
    module_mapping: InvertedModuleMapping, field_set: PythonRequirementFieldSet
93
) -> EntryPoint:
94
    modules = field_set.modules.value
×
95
    reqs = field_set.requirements.value
×
96
    entry_point_raw = field_set.entry_point.value
×
97

98
    if entry_point_raw:
×
99
        resolved_entry_point = await resolve_pex_entry_point(
×
100
            ResolvePexEntryPointRequest(field_set.entry_point)
101
        )
102
        entry_point = resolved_entry_point.val
×
103

104
        if not entry_point:
×
105
            raise Exception("The provided `entry_point` was not valid.")
×
106

107
        return entry_point
×
108

109
    entry_point_module: str
110
    if modules and len(modules) == 1:
×
111
        # Unambiguous module specified in the `BUILD` file
112
        entry_point_module = modules[0]
×
113
    elif len(module_mapping.get(field_set.address, ())) == 1:
×
114
        # Check the third-party module mapping
115
        entry_point_module = module_mapping[field_set.address][0]
×
116
    elif len(reqs) == 1:
×
117
        # Use the canonicalized project name for a single-requirement target
118
        entry_point_module = canonicalize_project_name(reqs[0].name)
×
119
    else:
120
        raise Exception(
×
121
            "Requirement must provide a single module, specify a single requirement, or specify "
122
            "an `entry_point`"
123
        )
124

125
    return EntryPoint(entry_point_module)
×
126

127

UNCOV
128
@rule(level=LogLevel.DEBUG)
×
UNCOV
129
async def create_python_requirement_run_request(
×
130
    field_set: PythonRequirementFieldSet,
131
    pex_env: PexEnvironment,
132
    python_setup: PythonSetup,
133
    module_mapping: ThirdPartyPythonModuleMapping,
134
) -> RunRequest:
135
    addresses = [field_set.address]
×
136

137
    resolve = field_set.resolve.value
×
138
    if not resolve:
×
139
        resolve = python_setup.default_resolve
×
140

141
    modules_for_address = _invert_module_mapping(resolve, module_mapping)
×
142
    entry_point = await _resolve_entry_point(modules_for_address, field_set)
×
143
    filename = entry_point.spec.replace(".", "__").replace(":", "___")
×
144

145
    pex_request = await create_pex_from_targets(
×
146
        PexFromTargetsRequest(
147
            addresses,
148
            output_filename=f"{filename}.pex",
149
            internal_only=True,
150
            include_source_files=False,
151
            include_local_dists=True,
152
            main=entry_point,
153
            additional_args=("--no-strip-pex-env",),
154
        ),
155
        **implicitly(),
156
    )
157

158
    complete_pex_environment = pex_env.in_sandbox(working_directory=None)
×
159
    venv_pex = await create_venv_pex(
×
160
        VenvPexRequest(pex_request, complete_pex_environment), **implicitly()
161
    )
162
    input_digest = venv_pex.digest
×
163

164
    extra_env = {
×
165
        **complete_pex_environment.environment_dict(python=None),
166
    }
167

168
    return RunRequest(
×
169
        digest=input_digest,
170
        args=[_in_chroot(venv_pex.pex.argv0)],
171
        extra_env=extra_env,
172
        append_only_caches=complete_pex_environment.append_only_caches,
173
    )
174

175

UNCOV
176
def rules():
×
UNCOV
177
    return [
×
178
        *collect_rules(),
179
        *PythonRequirementFieldSet.rules(),
180
    ]
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