• 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/repl.py
1
# Copyright 2020 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 os
×
UNCOV
7
from collections.abc import Iterable
×
8

UNCOV
9
from pants.backend.python.subsystems import ipython
×
UNCOV
10
from pants.backend.python.subsystems.ipython import IPython
×
UNCOV
11
from pants.backend.python.subsystems.setup import PythonSetup
×
UNCOV
12
from pants.backend.python.target_types import PythonResolveField
×
UNCOV
13
from pants.backend.python.util_rules.local_dists import LocalDistsPexRequest, build_local_dists
×
UNCOV
14
from pants.backend.python.util_rules.pex import create_pex
×
UNCOV
15
from pants.backend.python.util_rules.pex_environment import PexEnvironment
×
UNCOV
16
from pants.backend.python.util_rules.pex_from_targets import (
×
17
    InterpreterConstraintsRequest,
18
    RequirementsPexRequest,
19
    interpreter_constraints_for_targets,
20
)
UNCOV
21
from pants.backend.python.util_rules.python_sources import (
×
22
    PythonSourceFilesRequest,
23
    prepare_python_sources,
24
)
UNCOV
25
from pants.core.goals.generate_lockfiles import NoCompatibleResolveException
×
UNCOV
26
from pants.core.goals.repl import ReplImplementation, ReplRequest
×
UNCOV
27
from pants.engine.fs import MergeDigests
×
UNCOV
28
from pants.engine.internals.graph import transitive_targets as transitive_targets_get
×
UNCOV
29
from pants.engine.intrinsics import merge_digests
×
UNCOV
30
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
×
UNCOV
31
from pants.engine.target import Target, TransitiveTargetsRequest
×
UNCOV
32
from pants.engine.unions import UnionRule
×
UNCOV
33
from pants.util.docutil import bin_name
×
UNCOV
34
from pants.util.logging import LogLevel
×
UNCOV
35
from pants.util.strutil import softwrap
×
36

37

UNCOV
38
def validate_compatible_resolve(root_targets: Iterable[Target], python_setup: PythonSetup) -> None:
×
39
    """Eagerly validate that all roots are compatible.
40

41
    We already end up checking this in pex_from_targets.py, but this is a more eager check so that
42
    we have a better error message.
43
    """
44
    root_resolves = {
×
45
        root[PythonResolveField].normalized_value(python_setup)
46
        for root in root_targets
47
        if root.has_field(PythonResolveField)
48
    }
49

50
    def maybe_get_resolve(t: Target) -> str | None:
×
51
        if not t.has_field(PythonResolveField):
×
52
            return None
×
53
        return t[PythonResolveField].normalized_value(python_setup)
×
54

55
    if len(root_resolves) > 1:
×
56
        raise NoCompatibleResolveException.bad_input_roots(
×
57
            root_targets,
58
            maybe_get_resolve=maybe_get_resolve,
59
            doc_url_slug="docs/python/overview/lockfiles#multiple-lockfiles",
60
            workaround=softwrap(
61
                f"""
62
                To work around this, choose which resolve you want to use from above. Then, run
63
                `{bin_name()} peek :: | jq -r \'.[] | select(.resolve == "example") |
64
                .["address"]\' | xargs {bin_name()} repl`, where you replace "example" with the
65
                resolve name, and possibly replace the specs `::` with what you were using
66
                before. If the resolve is the `[python].default_resolve`, use
67
                `select(.resolve == "example" or .resolve == null)`. These queries will result in
68
                opening a REPL with only targets using the desired resolve.
69
                """
70
            ),
71
        )
72

73

UNCOV
74
class PythonRepl(ReplImplementation):
×
UNCOV
75
    name = "python"
×
UNCOV
76
    supports_args = False
×
77

78

UNCOV
79
@rule(level=LogLevel.DEBUG)
×
UNCOV
80
async def create_python_repl_request(
×
81
    request: PythonRepl, pex_env: PexEnvironment, python_setup: PythonSetup
82
) -> ReplRequest:
83
    validate_compatible_resolve(request.targets, python_setup)
×
84

85
    interpreter_constraints, transitive_targets = await concurrently(
×
86
        interpreter_constraints_for_targets(
87
            InterpreterConstraintsRequest(request.addresses), **implicitly()
88
        ),
89
        transitive_targets_get(TransitiveTargetsRequest(request.addresses), **implicitly()),
90
    )
91

92
    requirements_request = create_pex(**implicitly(RequirementsPexRequest(request.addresses)))
×
93
    local_dists_request = build_local_dists(
×
94
        LocalDistsPexRequest(
95
            request.addresses,
96
            interpreter_constraints=interpreter_constraints,
97
        )
98
    )
99

100
    sources_request = prepare_python_sources(
×
101
        PythonSourceFilesRequest(transitive_targets.closure, include_files=True), **implicitly()
102
    )
103

104
    requirements_pex, local_dists, sources = await concurrently(
×
105
        requirements_request, local_dists_request, sources_request
106
    )
107
    merged_digest = await merge_digests(
×
108
        MergeDigests(
109
            (requirements_pex.digest, local_dists.pex.digest, sources.source_files.snapshot.digest)
110
        )
111
    )
112

113
    complete_pex_env = pex_env.in_workspace()
×
114
    args = complete_pex_env.create_argv(request.in_chroot(requirements_pex.name))
×
115

116
    chrooted_source_roots = [request.in_chroot(sr) for sr in sources.source_roots]
×
117
    extra_env = {
×
118
        **complete_pex_env.environment_dict(python=requirements_pex.python),
119
        "PEX_EXTRA_SYS_PATH": ":".join(chrooted_source_roots),
120
        "PEX_PATH": request.in_chroot(local_dists.pex.name),
121
        "PEX_INTERPRETER_HISTORY": "1" if python_setup.repl_history else "0",
122
    }
123

124
    return ReplRequest(digest=merged_digest, args=args, extra_env=extra_env)
×
125

126

UNCOV
127
class IPythonRepl(ReplImplementation):
×
UNCOV
128
    name = "ipython"
×
UNCOV
129
    supports_args = True
×
130

131

UNCOV
132
@rule(level=LogLevel.DEBUG)
×
UNCOV
133
async def create_ipython_repl_request(
×
134
    request: IPythonRepl, ipython: IPython, pex_env: PexEnvironment, python_setup: PythonSetup
135
) -> ReplRequest:
136
    validate_compatible_resolve(request.targets, python_setup)
×
137

138
    interpreter_constraints, transitive_targets = await concurrently(
×
139
        interpreter_constraints_for_targets(
140
            InterpreterConstraintsRequest(request.addresses), **implicitly()
141
        ),
142
        transitive_targets_get(TransitiveTargetsRequest(request.addresses), **implicitly()),
143
    )
144

145
    requirements_request = create_pex(**implicitly(RequirementsPexRequest(request.addresses)))
×
146
    sources_request = prepare_python_sources(
×
147
        PythonSourceFilesRequest(transitive_targets.closure, include_files=True), **implicitly()
148
    )
149

150
    ipython_request = create_pex(
×
151
        ipython.to_pex_request(interpreter_constraints=interpreter_constraints)
152
    )
153

154
    requirements_pex, sources, ipython_pex = await concurrently(
×
155
        requirements_request, sources_request, ipython_request
156
    )
157

158
    local_dists = await build_local_dists(
×
159
        LocalDistsPexRequest(
160
            request.addresses,
161
            interpreter_constraints=interpreter_constraints,
162
            sources=sources,
163
        )
164
    )
165

166
    merged_digest = await merge_digests(
×
167
        MergeDigests(
168
            (
169
                requirements_pex.digest,
170
                local_dists.pex.digest,
171
                local_dists.remaining_sources.source_files.snapshot.digest,
172
                ipython_pex.digest,
173
            )
174
        )
175
    )
176

177
    complete_pex_env = pex_env.in_workspace()
×
178
    args = list(complete_pex_env.create_argv(request.in_chroot(ipython_pex.name)))
×
179
    if ipython.ignore_cwd:
×
180
        args.append("--ignore-cwd")
×
181

182
    chrooted_source_roots = [request.in_chroot(sr) for sr in sources.source_roots]
×
183
    extra_env = {
×
184
        **complete_pex_env.environment_dict(python=ipython_pex.python),
185
        "PEX_PATH": os.pathsep.join(
186
            [
187
                request.in_chroot(requirements_pex.name),
188
                request.in_chroot(local_dists.pex.name),
189
            ]
190
        ),
191
        "PEX_EXTRA_SYS_PATH": os.pathsep.join(chrooted_source_roots),
192
    }
193

194
    return ReplRequest(digest=merged_digest, args=args, extra_env=extra_env)
×
195

196

UNCOV
197
def rules():
×
UNCOV
198
    return [
×
199
        *collect_rules(),
200
        *ipython.rules(),
201
        UnionRule(ReplImplementation, PythonRepl),
202
        UnionRule(ReplImplementation, IPythonRepl),
203
    ]
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