• 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

62.94
/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
1✔
5

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

12
from packaging.utils import canonicalize_name
1✔
13

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

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

31

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

38

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

44

45
RESOLVE_OPTION_KEY__DEFAULT = "__default__"
1✔
46

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

49

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

54
    default_interpreter_universe = [
1✔
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
        "3.14",
66
    ]
67

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
143
        return self._interpreter_constraints
×
144

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

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

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

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

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

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

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

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

202
            If you need multiple resolves:
203

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

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

223
            For example:
224

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

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

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

243
            The name must be defined as a resolve in `[python].resolves`.
244
            """
245
        ),
246
        advanced=True,
247
    )
248

249
    _default_to_resolve_interpreter_constraints = BoolOption(
1✔
250
        default=False,
251
        help=softwrap(
252
            """
253
            For Python targets with both `resolve` and `interpreter_constraints` fields, default to using the `interpreter_constraints` field of the resolve if `interpreter_constraints` is not set on the target itself.
254

255
            `[python].enable_resolves` must be `True` for this option to also be enabled. This will become True by default in a future version of Pants and eventually be deprecated and then removed.
256
            """
257
        ),
258
        advanced=True,
259
    )
260

261
    @memoized_property
1✔
262
    def default_to_resolve_interpreter_constraints(self) -> bool:
1✔
UNCOV
263
        if self._default_to_resolve_interpreter_constraints and not self.enable_resolves:
×
264
            raise OptionsError(
×
265
                softwrap(
266
                    """
267
                You cannot set `[python].default_to_resolve_interpreter_constraints = true` without setting `[python].enable_resolves = true`.
268

269
                Please either enable resolves or set `[python].default_to_resolve_interpreter_constraints = false` (the default setting).
270
                """
271
                )
272
            )
UNCOV
273
        return self._default_to_resolve_interpreter_constraints
×
274

275
    separate_lockfile_metadata_file = BoolOption(
1✔
276
        advanced=True,
277
        default=False,
278
        help=softwrap(
279
            """
280
            If set, lockfile metadata will be written to a separate sibling file, rather than
281
            prepended as a header to the lockfile (which has various disadvantages).
282
            This will soon become True by default and eventually the header option will be
283
            deprecated and then removed.
284
            """
285
        ),
286
    )
287
    default_run_goal_use_sandbox = BoolOption(
1✔
288
        default=True,
289
        help=softwrap(
290
            """
291
            The default value used for the `run_goal_use_sandbox` field of Python targets. See the
292
            relevant field for more details.
293
            """
294
        ),
295
    )
296
    pip_version = StrOption(
1✔
297
        default="24.2",
298
        help=softwrap(
299
            f"""
300
            Use this version of Pip for resolving requirements and generating lockfiles.
301

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

305
            N.B.: The `latest` value selects the latest of the choices listed by PEX which is not
306
            necessarily the latest Pip version released on PyPI.
307
            """
308
        ),
309
        advanced=True,
310
    )
311
    _resolves_to_interpreter_constraints = DictOption[list[str]](
1✔
312
        help=softwrap(
313
            """
314
            Override the interpreter constraints to use when generating a resolve's lockfile
315
            with the `generate-lockfiles` goal.
316

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

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

326
            Pants will validate that the interpreter constraints of your code using a
327
            resolve are compatible with that resolve's own constraints. For example, if your
328
            code is set to use `['==3.9.*']` via the `interpreter_constraints` field, but it's
329
            using a resolve whose interpreter constraints are set to `['==3.7.*']`, then
330
            Pants will error explaining the incompatibility.
331

332
            The keys must be defined as resolves in `[python].resolves`.
333
            """
334
        ),
335
        advanced=True,
336
    )
337
    _resolves_to_constraints_file = DictOption[str](
1✔
338
        help=softwrap(
339
            f"""
340
            When generating a resolve's lockfile, use a constraints file to pin the version of
341
            certain requirements. This is particularly useful to pin the versions of transitive
342
            dependencies of your direct requirements.
343

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

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

353
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
354
            resolves.
355
            """
356
        ),
357
        advanced=True,
358
    )
359
    _resolves_to_no_binary = DictOption[list[str]](
1✔
360
        help=softwrap(
361
            f"""
362
            When generating a resolve's lockfile, do not use binary packages (i.e. wheels) for
363
            these 3rdparty project names.
364

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

370
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
371
            resolves.
372

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

376
            Note that some packages are tricky to compile and may fail to install when this option
377
            is used on them. See https://pip.pypa.io/en/stable/cli/pip_install/#install-no-binary
378
            for details.
379
            """
380
        ),
381
        advanced=True,
382
    )
383
    _resolves_to_only_binary = DictOption[list[str]](
1✔
384
        help=softwrap(
385
            f"""
386
            When generating a resolve's lockfile, do not use source packages (i.e. sdists) for
387
            these 3rdparty project names, e.g `['django', 'requests']`.
388

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

394
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
395
            resolves.
396

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

400
            Packages without binary distributions will fail to install when this option is used on
401
            them. See https://pip.pypa.io/en/stable/cli/pip_install/#install-only-binary for
402
            details.
403
            """
404
        ),
405
        advanced=True,
406
    )
407
    _resolves_to_excludes = DictOption[list[str]](
1✔
408
        help=softwrap(
409
            """ Specifies requirements to exclude from a resolve and its
410
            lockfile.  Any distribution included in the PEX's resolve that
411
            matches the requirement is excluded from the built PEX along with
412
            all of its transitive dependencies that are not also required by
413
            other non-excluded distributions.  At runtime, the PEX will boot
414
            without checking the excluded dependencies are available.
415
            """
416
        ),
417
        advanced=True,
418
    )
419
    _resolves_to_overrides = DictOption[list[str]](
1✔
420
        help=softwrap(
421
            """ Specifies a transitive requirement to override in a resolve
422
            and its lockfile.  Overrides can either modify an existing
423
            dependency on a project name by changing extras, version
424
            constraints or markers or else they can completely swap out the
425
            dependency for a dependency on another project altogether. For the
426
            former, simply supply the requirement you wish. For example,
427
            specifying `--override cowsay==5.0` will override any transitive
428
            dependency on cowsay that has any combination of extras, version
429
            constraints or markers with the requirement `cowsay==5.0`. To
430
            completely replace cowsay with another library altogether, you can
431
            specify an override like `--override cowsay=my-cowsay>2`. This
432
            will replace any transitive dependency on cowsay that has any
433
            combination of extras, version constraints or markers with the
434
            requirement `my-cowsay>2`."""
435
        ),
436
        advanced=True,
437
    )
438

439
    _resolves_to_sources = DictOption[list[str]](
1✔
440
        help=softwrap(""" Defines a limited scope to use a named find links repo or
441
            index for specific dependencies in a resolve and its lockfile.
442
            Sources take the form `<name>=<scope>` where the name must match
443
            a find links repo or index defined via `[python-repos].indexes` or
444
            `[python-repos].find_links`. The scope can be a project name
445
            (e.g., `internal=torch` to resolve the `torch` project from the
446
            `internal` repo), a project name with a marker (e.g.,
447
            `internal=torch; sys_platform != 'darwin'` to resolve `torch` from
448
            the `internal` repo except on macOS), or just a marker (e.g.,
449
            `piwheels=platform_machine == 'armv7l'` to resolve from the
450
            `piwheels` repo when targeting 32bit ARM machines)."""),
451
        advanced=True,
452
    )
453

454
    invalid_lockfile_behavior = EnumOption(
1✔
455
        default=InvalidLockfileBehavior.error,
456
        help=softwrap(
457
            """
458
            The behavior when a lockfile has requirements or interpreter constraints that are
459
            not compatible with what the current build is using.
460

461
            We recommend keeping the default of `error` for CI builds.
462

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

466
            Use `ignore` to avoid needing a lockfile header at all, e.g. if you are manually
467
            managing lockfiles rather than using the `generate-lockfiles` goal.
468
            """
469
        ),
470
        advanced=True,
471
    )
472
    resolves_generate_lockfiles = BoolOption(
1✔
473
        default=True,
474
        help=softwrap(
475
            """
476
            If False, Pants will not attempt to generate lockfiles for `[python].resolves` when
477
            running the `generate-lockfiles` goal.
478

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

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

489
            Warning: it will likely be slower to install manually generated user lockfiles than Pex
490
            ones because Pants cannot as efficiently extract the subset of requirements used for a
491
            particular task. See the option `[python].run_against_entire_lockfile`.
492
            """
493
        ),
494
        advanced=True,
495
    )
496
    run_against_entire_lockfile = BoolOption(
1✔
497
        default=False,
498
        help=softwrap(
499
            """
500
            If enabled, when running binaries, tests, and repls, Pants will use the entire
501
            lockfile file instead of just the relevant subset.
502

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

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

513
            This option does not affect packaging deployable artifacts, such as
514
            PEX files, wheels and cloud functions, which will still use just the exact
515
            subset of requirements needed.
516
            """
517
        ),
518
        advanced=True,
519
    )
520

521
    __constraints_deprecation_msg = softwrap(
1✔
522
        f"""
523
        We encourage instead migrating to `[python].enable_resolves` and `[python].resolves`,
524
        which is an improvement over this option. The `[python].resolves` feature ensures that
525
        your lockfiles are fully comprehensive, i.e. include all transitive dependencies;
526
        uses hashes for better supply chain security; and supports advanced features like VCS
527
        and local requirements, along with options `[python].resolves_to_only_binary`.
528

529
        To migrate, stop setting `[python].requirement_constraints` and
530
        `[python].resolve_all_constraints`, and instead set `[python].enable_resolves` to
531
        `true`. Then, run `{bin_name()} generate-lockfiles`.
532
        """
533
    )
534
    requirement_constraints = FileOption(
1✔
535
        default=None,
536
        help=softwrap(
537
            """
538
            When resolving third-party requirements for your own code (vs. tools you run),
539
            use this constraints file to determine which versions to use.
540

541
            Mutually exclusive with `[python].enable_resolves`, which we generally recommend as an
542
            improvement over constraints file.
543

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

548
            This only applies when resolving user requirements, rather than tools you run
549
            like Black and Pytest. To constrain tools, set `[tool].lockfile`, e.g.
550
            `[black].lockfile`.
551
            """
552
        ),
553
        advanced=True,
554
        mutually_exclusive_group="lockfile",
555
        removal_version="3.0.0.dev0",
556
        removal_hint=__constraints_deprecation_msg,
557
    )
558
    _resolve_all_constraints = BoolOption(
1✔
559
        default=True,
560
        help=softwrap(
561
            """
562
            (Only relevant when using `[python].requirement_constraints.`) If enabled, when
563
            resolving requirements, Pants will first resolve your entire
564
            constraints file as a single global resolve. Then, if the code uses a subset of
565
            your constraints file, Pants will extract the relevant requirements from that
566
            global resolve so that only what's actually needed gets used. If disabled, Pants
567
            will not use a global resolve and will resolve each subset of your requirements
568
            independently.
569

570
            Usually this option should be enabled because it can result in far fewer resolves.
571
            """
572
        ),
573
        advanced=True,
574
        removal_version="3.0.0.dev0",
575
        removal_hint=__constraints_deprecation_msg,
576
    )
577
    resolver_manylinux = StrOption(
1✔
578
        default="manylinux2014",
579
        help=softwrap(
580
            """
581
            Whether to allow resolution of manylinux wheels when resolving requirements for
582
            foreign linux platforms. The value should be a manylinux platform upper bound,
583
            e.g. `'manylinux2010'`, or else the string `'no'` to disallow.
584
            """
585
        ),
586
        advanced=True,
587
    )
588

589
    tailor_source_targets = BoolOption(
1✔
590
        default=True,
591
        help=softwrap(
592
            """
593
            If true, add `python_sources`, `python_tests`, and `python_test_utils` targets with
594
            the `tailor` goal."""
595
        ),
596
        advanced=True,
597
    )
598
    tailor_ignore_empty_init_files = BoolOption(
1✔
599
        "--tailor-ignore-empty-init-files",
600
        default=True,
601
        help=softwrap(
602
            """
603
            If true, don't add `python_sources` targets for `__init__.py` files that are both empty
604
            and where there are no other Python files in the directory.
605

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

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

612
            If you set to false, you may also want to set `[python-infer].init_files = "always"`.
613
            """
614
        ),
615
        advanced=True,
616
    )
617
    tailor_requirements_targets = BoolOption(
1✔
618
        default=True,
619
        help=softwrap(
620
            """
621
            If true, add `python_requirements`, `poetry_requirements`, and `pipenv_requirements`
622
            target generators with the `tailor` goal.
623

624
            `python_requirements` targets are added for any file that matches the pattern
625
            `*requirements*.txt`. You will need to manually add `python_requirements` for different
626
            file names like `reqs.txt`.
627

628
            `poetry_requirements` targets are added for `pyproject.toml` files with `[tool.poetry`
629
            in them.
630
            """
631
        ),
632
        advanced=True,
633
    )
634
    tailor_pex_binary_targets = BoolOption(
1✔
635
        default=False,
636
        help=softwrap(
637
            """
638
            If true, add `pex_binary` targets for Python files named `__main__.py` or with a
639
            `__main__` clause with the `tailor` goal.
640
            """
641
        ),
642
        advanced=True,
643
    )
644
    tailor_py_typed_targets = BoolOption(
1✔
645
        default=True,
646
        help=softwrap(
647
            """
648
            If true, add `resource` targets for marker files named `py.typed` with the `tailor` goal.
649
            """
650
        ),
651
        advanced=True,
652
    )
653
    macos_big_sur_compatibility = BoolOption(
1✔
654
        default=False,
655
        help=softwrap(
656
            """
657
            If set, and if running on macOS Big Sur, use `macosx_10_16` as the platform
658
            when building wheels. Otherwise, the default of `macosx_11_0` will be used.
659
            This may be required for `pip` to be able to install the resulting distribution
660
            on Big Sur.
661
            """
662
        ),
663
        advanced=True,
664
    )
665
    enable_lockfile_targets = BoolOption(
1✔
666
        default=True,
667
        help=softwrap(
668
            """
669
            Create targets for all Python lockfiles defined in `[python].resolves`.
670

671
            The lockfile targets will then be used as dependencies to the `python_requirement`
672
            targets that use them, invalidating source targets per resolve when the lockfile
673
            changes.
674

675
            If another targets address is in conflict with the created lockfile target, it will
676
            shadow the lockfile target and it will not be available as a dependency for any
677
            `python_requirement` targets.
678
            """
679
        ),
680
        advanced=True,
681
    )
682
    repl_history = BoolOption(
1✔
683
        default=True,
684
        help="Whether to use the standard Python command history file when running a repl.",
685
    )
686

687
    @property
1✔
688
    def enable_synthetic_lockfiles(self) -> bool:
1✔
689
        return self.enable_resolves and self.enable_lockfile_targets
×
690

691
    @memoized_property
1✔
692
    def resolves_to_interpreter_constraints(self) -> dict[str, tuple[str, ...]]:
1✔
UNCOV
693
        result = {}
×
UNCOV
694
        unrecognized_resolves = []
×
UNCOV
695
        for resolve, ics in self._resolves_to_interpreter_constraints.items():
×
UNCOV
696
            if resolve not in self.resolves:
×
UNCOV
697
                unrecognized_resolves.append(resolve)
×
UNCOV
698
            if ics and self.warn_on_python2_usage:
×
699
                # Side-step import cycle.
700
                from pants.backend.python.util_rules.interpreter_constraints import (
×
701
                    warn_on_python2_usage_in_interpreter_constraints,
702
                )
703

704
                warn_on_python2_usage_in_interpreter_constraints(
×
705
                    ics,
706
                    description_of_origin=f"the `[python].resolves_to_interpreter_constraints` option for resolve {resolve}",
707
                )
708

UNCOV
709
            result[resolve] = tuple(ics)
×
UNCOV
710
        if unrecognized_resolves:
×
UNCOV
711
            raise UnrecognizedResolveNamesError(
×
712
                unrecognized_resolves,
713
                self.resolves.keys(),
714
                description_of_origin="the option `[python].resolves_to_interpreter_constraints`",
715
            )
UNCOV
716
        return result
×
717

718
    def _resolves_to_option_helper(
1✔
719
        self,
720
        option_value: dict[str, _T],
721
        option_name: str,
722
    ) -> dict[str, _T]:
UNCOV
723
        all_valid_resolves = set(self.resolves)
×
UNCOV
724
        unrecognized_resolves = set(option_value.keys()) - {
×
725
            RESOLVE_OPTION_KEY__DEFAULT,
726
            *all_valid_resolves,
727
        }
UNCOV
728
        if unrecognized_resolves:
×
UNCOV
729
            raise UnrecognizedResolveNamesError(
×
730
                sorted(unrecognized_resolves),
731
                {*all_valid_resolves, RESOLVE_OPTION_KEY__DEFAULT},
732
                description_of_origin=f"the option `[python].{option_name}`",
733
            )
UNCOV
734
        default_val = option_value.get(RESOLVE_OPTION_KEY__DEFAULT)
×
UNCOV
735
        if not default_val:
×
UNCOV
736
            return option_value
×
UNCOV
737
        return {resolve: option_value.get(resolve, default_val) for resolve in all_valid_resolves}
×
738

739
    @memoized_method
1✔
740
    def resolves_to_constraints_file(self) -> dict[str, str]:
1✔
UNCOV
741
        return self._resolves_to_option_helper(
×
742
            self._resolves_to_constraints_file,
743
            "resolves_to_constraints_file",
744
        )
745

746
    @memoized_method
1✔
747
    def resolves_to_no_binary(self) -> dict[str, list[str]]:
1✔
UNCOV
748
        return {
×
749
            resolve: [canonicalize_name(v) for v in vals]
750
            for resolve, vals in self._resolves_to_option_helper(
751
                self._resolves_to_no_binary,
752
                "resolves_to_no_binary",
753
            ).items()
754
        }
755

756
    @memoized_method
1✔
757
    def resolves_to_only_binary(self) -> dict[str, list[str]]:
1✔
UNCOV
758
        return {
×
759
            resolve: sorted([canonicalize_name(v) for v in vals])
760
            for resolve, vals in self._resolves_to_option_helper(
761
                self._resolves_to_only_binary,
762
                "resolves_to_only_binary",
763
            ).items()
764
        }
765

766
    @memoized_method
1✔
767
    def resolves_to_excludes(self) -> dict[str, list[str]]:
1✔
768
        return {
×
769
            resolve: sorted(vals)
770
            for resolve, vals in self._resolves_to_option_helper(
771
                self._resolves_to_excludes,
772
                "resolves_to_excludes",
773
            ).items()
774
        }
775

776
    @memoized_method
1✔
777
    def resolves_to_overrides(self) -> dict[str, list[str]]:
1✔
778
        return {
×
779
            resolve: sorted(vals)
780
            for resolve, vals in self._resolves_to_option_helper(
781
                self._resolves_to_overrides,
782
                "resolves_to_overrides",
783
            ).items()
784
        }
785

786
    @memoized_method
1✔
787
    def resolves_to_sources(self) -> dict[str, list[str]]:
1✔
788
        return {
×
789
            resolve: sorted(vals)
790
            for resolve, vals in self._resolves_to_option_helper(
791
                self._resolves_to_sources,
792
                "resolves_to_sources",
793
            ).items()
794
        }
795

796
    @property
1✔
797
    def manylinux(self) -> str | None:
1✔
798
        manylinux = cast(Optional[str], self.resolver_manylinux)
×
799
        if manylinux is None or manylinux.lower() in ("false", "no", "none"):
×
800
            return None
×
801
        return manylinux
×
802

803
    @property
1✔
804
    def resolve_all_constraints(self) -> bool:
1✔
UNCOV
805
        if (
×
806
            self._resolve_all_constraints
807
            and not self.options.is_default("resolve_all_constraints")
808
            and not self.requirement_constraints
809
        ):
810
            raise ValueError(
×
811
                softwrap(
812
                    """
813
                    `[python].resolve_all_constraints` is enabled, so
814
                    `[python].requirement_constraints` must also be set.
815
                    """
816
                )
817
            )
UNCOV
818
        return self._resolve_all_constraints
×
819

820
    @property
1✔
821
    def scratch_dir(self):
1✔
822
        return os.path.join(self.options.pants_workdir, *self.options_scope.split("."))
×
823

824
    def compatibility_or_constraints(
1✔
825
        self, compatibility: Iterable[str] | None, resolve: str | None
826
    ) -> tuple[str, ...]:
827
        """Return either the given `compatibility` field or the global interpreter constraints.
828

829
        If interpreter constraints are supplied by the CLI flag, return those only.
830
        """
UNCOV
831
        if self.options.is_flagged("interpreter_constraints"):
×
832
            return self.interpreter_constraints
×
UNCOV
833
        if compatibility:
×
UNCOV
834
            return tuple(compatibility)
×
UNCOV
835
        if resolve and self.default_to_resolve_interpreter_constraints:
×
UNCOV
836
            return self.resolves_to_interpreter_constraints.get(
×
837
                resolve, self.interpreter_constraints
838
            )
UNCOV
839
        return self.interpreter_constraints
×
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