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

pantsbuild / pants / 18517631058

15 Oct 2025 04:18AM UTC coverage: 69.207% (-11.1%) from 80.267%
18517631058

Pull #22745

github

web-flow
Merge 642a76ca1 into 99919310e
Pull Request #22745: [windows] Add windows support in the stdio crate.

53815 of 77759 relevant lines covered (69.21%)

2.42 hits per line

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

66.41
/src/python/pants/backend/python/subsystems/setup.py
1
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
7✔
5

6
import enum
7✔
7
import logging
7✔
8
import os
7✔
9
from collections.abc import Iterable
7✔
10
from typing import Optional, TypeVar, cast
7✔
11

12
from packaging.utils import canonicalize_name
7✔
13

14
from pants.core.goals.generate_lockfiles import UnrecognizedResolveNamesError
7✔
15
from pants.option.errors import OptionsError
7✔
16
from pants.option.option_types import (
7✔
17
    BoolOption,
18
    DictOption,
19
    EnumOption,
20
    FileOption,
21
    StrListOption,
22
    StrOption,
23
)
24
from pants.option.subsystem import Subsystem
7✔
25
from pants.util.docutil import bin_name, doc_url
7✔
26
from pants.util.memo import memoized_method, memoized_property
7✔
27
from pants.util.strutil import softwrap
7✔
28

29
logger = logging.getLogger(__name__)
7✔
30

31

32
@enum.unique
7✔
33
class InvalidLockfileBehavior(enum.Enum):
7✔
34
    error = "error"
7✔
35
    ignore = "ignore"
7✔
36
    warn = "warn"
7✔
37

38

39
@enum.unique
7✔
40
class LockfileGenerator(enum.Enum):
7✔
41
    PEX = "pex"
7✔
42
    POETRY = "poetry"
7✔
43

44

45
RESOLVE_OPTION_KEY__DEFAULT = "__default__"
7✔
46

47
_T = TypeVar("_T")
7✔
48

49

50
class PythonSetup(Subsystem):
7✔
51
    options_scope = "python"
7✔
52
    help = "Options for Pants's Python backend."
7✔
53

54
    default_interpreter_universe = [
7✔
55
        "2.7",
56
        "3.5",
57
        "3.6",
58
        "3.7",
59
        "3.8",
60
        "3.9",
61
        "3.10",
62
        "3.11",
63
        "3.12",
64
        "3.13",
65
    ]
66

67
    _interpreter_constraints = StrListOption(
7✔
68
        default=None,
69
        help=softwrap(
70
            """
71
            The Python interpreters your codebase is compatible with.
72

73
            These constraints are used as the default value for the `interpreter_constraints`
74
            field of Python targets.
75

76
            Specify with requirement syntax, e.g. `'CPython>=2.7,<3'` (A CPython interpreter with
77
            version >=2.7 AND version <3) or `'PyPy'` (A pypy interpreter of any version). Multiple
78
            constraint strings will be ORed together.
79
            """
80
        ),
81
        metavar="<requirement>",
82
    )
83

84
    warn_on_python2_usage = BoolOption(
7✔
85
        default=True,
86
        advanced=True,
87
        help=softwrap(
88
            """\
89
            True if Pants should generate a deprecation warning when Python 2.x is used in interpreter constraints.
90

91
            As of Pants v2.24.x and later, Pants will no longer be tested regularly with Python 2.7.x. As such, going
92
            forward, Pants may or may not work with Python 2.7. This option allows disabling the deprecation
93
            warning announcing this policy change.
94
            """
95
        ),
96
    )
97

98
    @memoized_property
7✔
99
    def interpreter_constraints(self) -> tuple[str, ...]:
7✔
100
        if not self._interpreter_constraints:
×
101
            # TODO: This is a hacky affordance for Pants's own tests, dozens of which were
102
            #  written when Pants provided default ICs, and implicitly rely on that assumption.
103
            #  We'll probably want to find and modify all those tests to set an explicit IC, but
104
            #  that will take time.
105
            if "PYTEST_CURRENT_TEST" in os.environ:
×
106
                return (">=3.9,<3.14",)
×
107
            raise OptionsError(
×
108
                softwrap(
109
                    f"""\
110
                    You must explicitly specify the default Python interpreter versions your code
111
                    is intended to run against.
112

113
                    You specify these interpreter constraints using the `interpreter_constraints`
114
                    option in the `[python]` section of pants.toml.
115

116
                    We recommend constraining to a single interpreter minor version if you can,
117
                    e.g., `interpreter_constraints = ['==3.11.*']`, or at least a small number of
118
                    interpreter minor versions, e.g., `interpreter_constraints = ['>=3.10,<3.12']`.
119

120
                    Individual targets can override these default interpreter constraints,
121
                    if different parts of your codebase run against different python interpreter
122
                    versions in a single repo.
123

124
                    See {doc_url("docs/python/overview/interpreter-compatibility")} for details.
125
                    """
126
                ),
127
            )
128

129
        # Warn if Python 2.x is still in use. This warning should only be displayed once since this
130
        # function is memoized.
131
        if self.warn_on_python2_usage:
×
132
            # Side-step import cycle.
133
            from pants.backend.python.util_rules.interpreter_constraints import (
×
134
                warn_on_python2_usage_in_interpreter_constraints,
135
            )
136

137
            warn_on_python2_usage_in_interpreter_constraints(
×
138
                self._interpreter_constraints,
139
                description_of_origin="the `[python].interpreter_constraints` option",
140
            )
141

142
        return self._interpreter_constraints
×
143

144
    interpreter_versions_universe = StrListOption(
7✔
145
        default=default_interpreter_universe,
146
        help=softwrap(
147
            f"""
148
            All known Python major/minor interpreter versions that may be used by either
149
            your code or tools used by your code.
150

151
            This is used by Pants to robustly handle interpreter constraints, such as knowing
152
            when generating lockfiles which Python versions to check if your code is using.
153

154
            This does not control which interpreter your code will use. Instead, to set your
155
            interpreter constraints, update `[python].interpreter_constraints`, the
156
            `interpreter_constraints` field, and relevant tool options like
157
            `[isort].interpreter_constraints` to tell Pants which interpreters your code
158
            actually uses. See {doc_url("docs/python/overview/interpreter-compatibility")}.
159

160
            All elements must be the minor and major Python version, e.g. `'2.7'` or `'3.10'`. Do
161
            not include the patch version.
162
            """
163
        ),
164
        advanced=True,
165
    )
166
    enable_resolves = BoolOption(
7✔
167
        default=False,
168
        help=softwrap(
169
            """
170
            Set to true to enable lockfiles for user code. See `[python].resolves` for an
171
            explanation of this feature.
172

173
            This option is mutually exclusive with `[python].requirement_constraints`. We strongly
174
            recommend using this option because it:
175

176
              1. Uses `--hash` to validate that all downloaded files are expected, which reduces\
177
                the risk of supply chain attacks.
178
              2. Enforces that all transitive dependencies are in the lockfile, whereas\
179
                constraints allow you to leave off dependencies. This ensures your build is more\
180
                stable and reduces the risk of supply chain attacks.
181
              3. Allows you to have multiple lockfiles in your repository.
182
            """
183
        ),
184
        advanced=True,
185
        mutually_exclusive_group="lockfile",
186
    )
187
    resolves = DictOption[str](
7✔
188
        default={"python-default": "3rdparty/python/default.lock"},
189
        help=softwrap(
190
            f"""
191
            A mapping of logical names to lockfile paths used in your project.
192

193
            Many organizations only need a single resolve for their whole project, which is
194
            a good default and often the simplest thing to do. However, you may need multiple
195
            resolves, such as if you use two conflicting versions of a requirement in
196
            your repository.
197

198
            If you only need a single resolve, run `{bin_name()} generate-lockfiles` to
199
            generate the lockfile.
200

201
            If you need multiple resolves:
202

203
              1. Via this option, define multiple resolve names and their lockfile paths.\
204
                The names should be meaningful to your repository, such as `data-science` or\
205
                `pants-plugins`.
206
              2. Set the default with `[python].default_resolve`.
207
              3. Update your `python_requirement` targets with the `resolve` field to declare which\
208
                resolve they should be available in. They default to `[python].default_resolve`,\
209
                so you only need to update targets that you want in non-default resolves.\
210
                (Often you'll set this via the `python_requirements` or `poetry_requirements`\
211
                target generators)
212
              4. Run `{bin_name()} generate-lockfiles` to generate the lockfiles. If the results\
213
                aren't what you'd expect, adjust the prior step.
214
              5. Update any targets like `python_source` / `python_sources`,\
215
                `python_test` / `python_tests`, and `pex_binary` which need to set a non-default\
216
                resolve with the `resolve` field.
217

218
            If a target can work with multiple resolves, you can either use the `parametrize`
219
            mechanism or manually create a distinct target per resolve. See {doc_url("docs/using-pants/key-concepts/targets-and-build-files")}
220
            for information about `parametrize`.
221

222
            For example:
223

224
                python_sources(
225
                    resolve=parametrize("data-science", "web-app"),
226
                )
227

228
            You can name the lockfile paths what you would like; Pants does not expect a
229
            certain file extension or location.
230

231
            Only applies if `[python].enable_resolves` is true.
232
            """
233
        ),
234
        advanced=True,
235
    )
236
    default_resolve = StrOption(
7✔
237
        default="python-default",
238
        help=softwrap(
239
            """
240
            The default value used for the `resolve` field.
241

242
            The name must be defined as a resolve in `[python].resolves`.
243
            """
244
        ),
245
        advanced=True,
246
    )
247
    separate_lockfile_metadata_file = BoolOption(
7✔
248
        advanced=True,
249
        default=False,
250
        help=softwrap(
251
            """
252
            If set, lockfile metadata will be written to a separate sibling file, rather than
253
            prepended as a header to the lockfile (which has various disadvantages).
254
            This will soon become True by default and eventually the header option will be
255
            deprecated and then removed.
256
            """
257
        ),
258
    )
259
    default_run_goal_use_sandbox = BoolOption(
7✔
260
        default=True,
261
        help=softwrap(
262
            """
263
            The default value used for the `run_goal_use_sandbox` field of Python targets. See the
264
            relevant field for more details.
265
            """
266
        ),
267
    )
268
    pip_version = StrOption(
7✔
269
        default="24.2",
270
        help=softwrap(
271
            f"""
272
            Use this version of Pip for resolving requirements and generating lockfiles.
273

274
            The value used here must be one of the Pip versions supported by the underlying PEX
275
            version. See {doc_url("docs/python/overview/pex")} for details.
276

277
            N.B.: The `latest` value selects the latest of the choices listed by PEX which is not
278
            necessarily the latest Pip version released on PyPI.
279
            """
280
        ),
281
        advanced=True,
282
    )
283
    _resolves_to_interpreter_constraints = DictOption[list[str]](
7✔
284
        help=softwrap(
285
            """
286
            Override the interpreter constraints to use when generating a resolve's lockfile
287
            with the `generate-lockfiles` goal.
288

289
            By default, each resolve from `[python].resolves` will use your
290
            global interpreter constraints set in `[python].interpreter_constraints`. With
291
            this option, you can override each resolve to use certain interpreter
292
            constraints, such as `{'data-science': ['==3.8.*']}`.
293

294
            Warning: this does NOT impact the interpreter constraints used by targets within the
295
            resolve, which is instead set by the option `[python].interpreter_constraints` and the
296
            `interpreter_constraints` field. It only impacts how the lockfile is generated.
297

298
            Pants will validate that the interpreter constraints of your code using a
299
            resolve are compatible with that resolve's own constraints. For example, if your
300
            code is set to use `['==3.9.*']` via the `interpreter_constraints` field, but it's
301
            using a resolve whose interpreter constraints are set to `['==3.7.*']`, then
302
            Pants will error explaining the incompatibility.
303

304
            The keys must be defined as resolves in `[python].resolves`.
305
            """
306
        ),
307
        advanced=True,
308
    )
309
    _resolves_to_constraints_file = DictOption[str](
7✔
310
        help=softwrap(
311
            f"""
312
            When generating a resolve's lockfile, use a constraints file to pin the version of
313
            certain requirements. This is particularly useful to pin the versions of transitive
314
            dependencies of your direct requirements.
315

316
            See https://pip.pypa.io/en/stable/user_guide/#constraints-files for more information on
317
            the format of constraint files and how constraints are applied in Pex and pip.
318

319
            Expects a dictionary of resolve names from `[python].resolves` and Python tools (e.g.
320
            `black` and `pytest`) to file paths for
321
            constraints files. For example,
322
            `{{'data-science': '3rdparty/data-science-constraints.txt'}}`.
323
            If a resolve is not set in the dictionary, it will not use a constraints file.
324

325
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
326
            resolves.
327
            """
328
        ),
329
        advanced=True,
330
    )
331
    _resolves_to_no_binary = DictOption[list[str]](
7✔
332
        help=softwrap(
333
            f"""
334
            When generating a resolve's lockfile, do not use binary packages (i.e. wheels) for
335
            these 3rdparty project names.
336

337
            Expects a dictionary of resolve names from `[python].resolves` and Python tools (e.g.
338
            `black` and `pytest`) to lists of project names. For example,
339
            `{{'data-science': ['requests', 'numpy']}}`. If a resolve is not set in the dictionary,
340
            it will have no restrictions on binary packages.
341

342
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
343
            resolves.
344

345
            For each resolve, you can also use the value `:all:` to disable all binary packages:
346
            `{{'data-science': [':all:']}}`.
347

348
            Note that some packages are tricky to compile and may fail to install when this option
349
            is used on them. See https://pip.pypa.io/en/stable/cli/pip_install/#install-no-binary
350
            for details.
351
            """
352
        ),
353
        advanced=True,
354
    )
355
    _resolves_to_only_binary = DictOption[list[str]](
7✔
356
        help=softwrap(
357
            f"""
358
            When generating a resolve's lockfile, do not use source packages (i.e. sdists) for
359
            these 3rdparty project names, e.g `['django', 'requests']`.
360

361
            Expects a dictionary of resolve names from `[python].resolves` and Python tools (e.g.
362
            `black` and `pytest`) to lists of project names. For example,
363
            `{{'data-science': ['requests', 'numpy']}}`. If a resolve is not set in the dictionary,
364
            it will have no restrictions on source packages.
365

366
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
367
            resolves.
368

369
            For each resolve you can use the value `:all:` to disable all source packages:
370
            `{{'data-science': [':all:']}}`.
371

372
            Packages without binary distributions will fail to install when this option is used on
373
            them. See https://pip.pypa.io/en/stable/cli/pip_install/#install-only-binary for
374
            details.
375
            """
376
        ),
377
        advanced=True,
378
    )
379
    _resolves_to_excludes = DictOption[list[str]](
7✔
380
        help=softwrap(
381
            """ Specifies requirements to exclude from a resolve and its
382
            lockfile.  Any distribution included in the PEX's resolve that
383
            matches the requirement is excluded from the built PEX along with
384
            all of its transitive dependencies that are not also required by
385
            other non-excluded distributions.  At runtime, the PEX will boot
386
            without checking the excluded dependencies are available.
387
            """
388
        ),
389
        advanced=True,
390
    )
391
    _resolves_to_overrides = DictOption[list[str]](
7✔
392
        help=softwrap(
393
            """ Specifies a transitive requirement to override in a resolve
394
            and its lockfile.  Overrides can either modify an existing
395
            dependency on a project name by changing extras, version
396
            constraints or markers or else they can completely swap out the
397
            dependency for a dependency on another project altogether. For the
398
            former, simply supply the requirement you wish. For example,
399
            specifying `--override cowsay==5.0` will override any transitive
400
            dependency on cowsay that has any combination of extras, version
401
            constraints or markers with the requirement `cowsay==5.0`. To
402
            completely replace cowsay with another library altogether, you can
403
            specify an override like `--override cowsay=my-cowsay>2`. This
404
            will replace any transitive dependency on cowsay that has any
405
            combination of extras, version constraints or markers with the
406
            requirement `my-cowsay>2`."""
407
        ),
408
        advanced=True,
409
    )
410

411
    invalid_lockfile_behavior = EnumOption(
7✔
412
        default=InvalidLockfileBehavior.error,
413
        help=softwrap(
414
            """
415
            The behavior when a lockfile has requirements or interpreter constraints that are
416
            not compatible with what the current build is using.
417

418
            We recommend keeping the default of `error` for CI builds.
419

420
            Note that `warn` will still expect a Pants lockfile header, it only won't error if
421
            the lockfile is stale and should be regenerated.
422

423
            Use `ignore` to avoid needing a lockfile header at all, e.g. if you are manually
424
            managing lockfiles rather than using the `generate-lockfiles` goal.
425
            """
426
        ),
427
        advanced=True,
428
    )
429
    resolves_generate_lockfiles = BoolOption(
7✔
430
        default=True,
431
        help=softwrap(
432
            """
433
            If False, Pants will not attempt to generate lockfiles for `[python].resolves` when
434
            running the `generate-lockfiles` goal.
435

436
            This is intended to allow you to manually generate lockfiles for your own code,
437
            rather than using Pex lockfiles. For example, when adopting Pants in a project already
438
            using Poetry, you can use `poetry export --dev` to create a requirements.txt-style
439
            lockfile understood by Pants, then point `[python].resolves` to the file.
440

441
            If you set this to False, Pants will not attempt to validate the metadata headers
442
            for your user lockfiles. This is useful so that you can keep
443
            `[python].invalid_lockfile_behavior` to `error` or `warn` if you'd like so that tool
444
            lockfiles continue to be validated, while user lockfiles are skipped.
445

446
            Warning: it will likely be slower to install manually generated user lockfiles than Pex
447
            ones because Pants cannot as efficiently extract the subset of requirements used for a
448
            particular task. See the option `[python].run_against_entire_lockfile`.
449
            """
450
        ),
451
        advanced=True,
452
    )
453
    run_against_entire_lockfile = BoolOption(
7✔
454
        default=False,
455
        help=softwrap(
456
            """
457
            If enabled, when running binaries, tests, and repls, Pants will use the entire
458
            lockfile file instead of just the relevant subset.
459

460
            If you are using Pex lockfiles, we generally do not recommend this. You will already
461
            get similar performance benefits to this option, without the downsides.
462

463
            Otherwise, this option can improve performance and reduce cache size.
464
            But it has two consequences:
465
            1) All cached test results will be invalidated if any requirement in the lockfile
466
               changes, rather than just those that depend on the changed requirement.
467
            2) Requirements unneeded by a test/run/repl will be present on the sys.path, which
468
               might in rare cases cause their behavior to change.
469

470
            This option does not affect packaging deployable artifacts, such as
471
            PEX files, wheels and cloud functions, which will still use just the exact
472
            subset of requirements needed.
473
            """
474
        ),
475
        advanced=True,
476
    )
477

478
    __constraints_deprecation_msg = softwrap(
7✔
479
        f"""
480
        We encourage instead migrating to `[python].enable_resolves` and `[python].resolves`,
481
        which is an improvement over this option. The `[python].resolves` feature ensures that
482
        your lockfiles are fully comprehensive, i.e. include all transitive dependencies;
483
        uses hashes for better supply chain security; and supports advanced features like VCS
484
        and local requirements, along with options `[python].resolves_to_only_binary`.
485

486
        To migrate, stop setting `[python].requirement_constraints` and
487
        `[python].resolve_all_constraints`, and instead set `[python].enable_resolves` to
488
        `true`. Then, run `{bin_name()} generate-lockfiles`.
489
        """
490
    )
491
    requirement_constraints = FileOption(
7✔
492
        default=None,
493
        help=softwrap(
494
            """
495
            When resolving third-party requirements for your own code (vs. tools you run),
496
            use this constraints file to determine which versions to use.
497

498
            Mutually exclusive with `[python].enable_resolves`, which we generally recommend as an
499
            improvement over constraints file.
500

501
            See https://pip.pypa.io/en/stable/user_guide/#constraints-files for more
502
            information on the format of constraint files and how constraints are applied in
503
            Pex and pip.
504

505
            This only applies when resolving user requirements, rather than tools you run
506
            like Black and Pytest. To constrain tools, set `[tool].lockfile`, e.g.
507
            `[black].lockfile`.
508
            """
509
        ),
510
        advanced=True,
511
        mutually_exclusive_group="lockfile",
512
        removal_version="3.0.0.dev0",
513
        removal_hint=__constraints_deprecation_msg,
514
    )
515
    _resolve_all_constraints = BoolOption(
7✔
516
        default=True,
517
        help=softwrap(
518
            """
519
            (Only relevant when using `[python].requirement_constraints.`) If enabled, when
520
            resolving requirements, Pants will first resolve your entire
521
            constraints file as a single global resolve. Then, if the code uses a subset of
522
            your constraints file, Pants will extract the relevant requirements from that
523
            global resolve so that only what's actually needed gets used. If disabled, Pants
524
            will not use a global resolve and will resolve each subset of your requirements
525
            independently.
526

527
            Usually this option should be enabled because it can result in far fewer resolves.
528
            """
529
        ),
530
        advanced=True,
531
        removal_version="3.0.0.dev0",
532
        removal_hint=__constraints_deprecation_msg,
533
    )
534
    resolver_manylinux = StrOption(
7✔
535
        default="manylinux2014",
536
        help=softwrap(
537
            """
538
            Whether to allow resolution of manylinux wheels when resolving requirements for
539
            foreign linux platforms. The value should be a manylinux platform upper bound,
540
            e.g. `'manylinux2010'`, or else the string `'no'` to disallow.
541
            """
542
        ),
543
        advanced=True,
544
    )
545

546
    tailor_source_targets = BoolOption(
7✔
547
        default=True,
548
        help=softwrap(
549
            """
550
            If true, add `python_sources`, `python_tests`, and `python_test_utils` targets with
551
            the `tailor` goal."""
552
        ),
553
        advanced=True,
554
    )
555
    tailor_ignore_empty_init_files = BoolOption(
7✔
556
        "--tailor-ignore-empty-init-files",
557
        default=True,
558
        help=softwrap(
559
            """
560
            If true, don't add `python_sources` targets for `__init__.py` files that are both empty
561
            and where there are no other Python files in the directory.
562

563
            Empty and solitary `__init__.py` files usually exist as import scaffolding rather than
564
            true library code, so it can be noisy to add BUILD files.
565

566
            Even if this option is set to true, Pants will still ensure the empty `__init__.py`
567
            files are included in the sandbox when running processes.
568

569
            If you set to false, you may also want to set `[python-infer].init_files = "always"`.
570
            """
571
        ),
572
        advanced=True,
573
    )
574
    tailor_requirements_targets = BoolOption(
7✔
575
        default=True,
576
        help=softwrap(
577
            """
578
            If true, add `python_requirements`, `poetry_requirements`, and `pipenv_requirements`
579
            target generators with the `tailor` goal.
580

581
            `python_requirements` targets are added for any file that matches the pattern
582
            `*requirements*.txt`. You will need to manually add `python_requirements` for different
583
            file names like `reqs.txt`.
584

585
            `poetry_requirements` targets are added for `pyproject.toml` files with `[tool.poetry`
586
            in them.
587
            """
588
        ),
589
        advanced=True,
590
    )
591
    tailor_pex_binary_targets = BoolOption(
7✔
592
        default=False,
593
        help=softwrap(
594
            """
595
            If true, add `pex_binary` targets for Python files named `__main__.py` or with a
596
            `__main__` clause with the `tailor` goal.
597
            """
598
        ),
599
        advanced=True,
600
    )
601
    tailor_py_typed_targets = BoolOption(
7✔
602
        default=True,
603
        help=softwrap(
604
            """
605
            If true, add `resource` targets for marker files named `py.typed` with the `tailor` goal.
606
            """
607
        ),
608
        advanced=True,
609
    )
610
    macos_big_sur_compatibility = BoolOption(
7✔
611
        default=False,
612
        help=softwrap(
613
            """
614
            If set, and if running on macOS Big Sur, use `macosx_10_16` as the platform
615
            when building wheels. Otherwise, the default of `macosx_11_0` will be used.
616
            This may be required for `pip` to be able to install the resulting distribution
617
            on Big Sur.
618
            """
619
        ),
620
        advanced=True,
621
    )
622
    enable_lockfile_targets = BoolOption(
7✔
623
        default=True,
624
        help=softwrap(
625
            """
626
            Create targets for all Python lockfiles defined in `[python].resolves`.
627

628
            The lockfile targets will then be used as dependencies to the `python_requirement`
629
            targets that use them, invalidating source targets per resolve when the lockfile
630
            changes.
631

632
            If another targets address is in conflict with the created lockfile target, it will
633
            shadow the lockfile target and it will not be available as a dependency for any
634
            `python_requirement` targets.
635
            """
636
        ),
637
        advanced=True,
638
    )
639
    repl_history = BoolOption(
7✔
640
        default=True,
641
        help="Whether to use the standard Python command history file when running a repl.",
642
    )
643

644
    @property
7✔
645
    def enable_synthetic_lockfiles(self) -> bool:
7✔
646
        return self.enable_resolves and self.enable_lockfile_targets
×
647

648
    @memoized_property
7✔
649
    def resolves_to_interpreter_constraints(self) -> dict[str, tuple[str, ...]]:
7✔
650
        result = {}
×
651
        unrecognized_resolves = []
×
652
        for resolve, ics in self._resolves_to_interpreter_constraints.items():
×
653
            if resolve not in self.resolves:
×
654
                unrecognized_resolves.append(resolve)
×
655
            if ics and self.warn_on_python2_usage:
×
656
                # Side-step import cycle.
657
                from pants.backend.python.util_rules.interpreter_constraints import (
×
658
                    warn_on_python2_usage_in_interpreter_constraints,
659
                )
660

661
                warn_on_python2_usage_in_interpreter_constraints(
×
662
                    ics,
663
                    description_of_origin=f"the `[python].resolves_to_interpreter_constraints` option for resolve {resolve}",
664
                )
665

666
            result[resolve] = tuple(ics)
×
667
        if unrecognized_resolves:
×
668
            raise UnrecognizedResolveNamesError(
×
669
                unrecognized_resolves,
670
                self.resolves.keys(),
671
                description_of_origin="the option `[python].resolves_to_interpreter_constraints`",
672
            )
673
        return result
×
674

675
    def _resolves_to_option_helper(
7✔
676
        self,
677
        option_value: dict[str, _T],
678
        option_name: str,
679
    ) -> dict[str, _T]:
680
        all_valid_resolves = set(self.resolves)
×
681
        unrecognized_resolves = set(option_value.keys()) - {
×
682
            RESOLVE_OPTION_KEY__DEFAULT,
683
            *all_valid_resolves,
684
        }
685
        if unrecognized_resolves:
×
686
            raise UnrecognizedResolveNamesError(
×
687
                sorted(unrecognized_resolves),
688
                {*all_valid_resolves, RESOLVE_OPTION_KEY__DEFAULT},
689
                description_of_origin=f"the option `[python].{option_name}`",
690
            )
691
        default_val = option_value.get(RESOLVE_OPTION_KEY__DEFAULT)
×
692
        if not default_val:
×
693
            return option_value
×
694
        return {resolve: option_value.get(resolve, default_val) for resolve in all_valid_resolves}
×
695

696
    @memoized_method
7✔
697
    def resolves_to_constraints_file(self) -> dict[str, str]:
7✔
698
        return self._resolves_to_option_helper(
×
699
            self._resolves_to_constraints_file,
700
            "resolves_to_constraints_file",
701
        )
702

703
    @memoized_method
7✔
704
    def resolves_to_no_binary(self) -> dict[str, list[str]]:
7✔
705
        return {
×
706
            resolve: [canonicalize_name(v) for v in vals]
707
            for resolve, vals in self._resolves_to_option_helper(
708
                self._resolves_to_no_binary,
709
                "resolves_to_no_binary",
710
            ).items()
711
        }
712

713
    @memoized_method
7✔
714
    def resolves_to_only_binary(self) -> dict[str, list[str]]:
7✔
715
        return {
×
716
            resolve: sorted([canonicalize_name(v) for v in vals])
717
            for resolve, vals in self._resolves_to_option_helper(
718
                self._resolves_to_only_binary,
719
                "resolves_to_only_binary",
720
            ).items()
721
        }
722

723
    @memoized_method
7✔
724
    def resolves_to_excludes(self) -> dict[str, list[str]]:
7✔
725
        return {
×
726
            resolve: sorted(vals)
727
            for resolve, vals in self._resolves_to_option_helper(
728
                self._resolves_to_excludes,
729
                "resolves_to_excludes",
730
            ).items()
731
        }
732

733
    @memoized_method
7✔
734
    def resolves_to_overrides(self) -> dict[str, list[str]]:
7✔
735
        return {
×
736
            resolve: sorted(vals)
737
            for resolve, vals in self._resolves_to_option_helper(
738
                self._resolves_to_overrides,
739
                "resolves_to_overrides",
740
            ).items()
741
        }
742

743
    @property
7✔
744
    def manylinux(self) -> str | None:
7✔
745
        manylinux = cast(Optional[str], self.resolver_manylinux)
×
746
        if manylinux is None or manylinux.lower() in ("false", "no", "none"):
×
747
            return None
×
748
        return manylinux
×
749

750
    @property
7✔
751
    def resolve_all_constraints(self) -> bool:
7✔
752
        if (
1✔
753
            self._resolve_all_constraints
754
            and not self.options.is_default("resolve_all_constraints")
755
            and not self.requirement_constraints
756
        ):
757
            raise ValueError(
×
758
                softwrap(
759
                    """
760
                    `[python].resolve_all_constraints` is enabled, so
761
                    `[python].requirement_constraints` must also be set.
762
                    """
763
                )
764
            )
765
        return self._resolve_all_constraints
1✔
766

767
    @property
7✔
768
    def scratch_dir(self):
7✔
769
        return os.path.join(self.options.pants_workdir, *self.options_scope.split("."))
×
770

771
    def compatibility_or_constraints(self, compatibility: Iterable[str] | None) -> tuple[str, ...]:
7✔
772
        """Return either the given `compatibility` field or the global interpreter constraints.
773

774
        If interpreter constraints are supplied by the CLI flag, return those only.
775
        """
776
        if self.options.is_flagged("interpreter_constraints"):
×
777
            return self.interpreter_constraints
×
778
        return tuple(compatibility or self.interpreter_constraints)
×
779

780
    def compatibilities_or_constraints(
7✔
781
        self, compatibilities: Iterable[Iterable[str] | None]
782
    ) -> tuple[str, ...]:
783
        return tuple(
×
784
            constraint
785
            for compatibility in compatibilities
786
            for constraint in self.compatibility_or_constraints(compatibility)
787
        )
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