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

pantsbuild / pants / 24462481292

15 Apr 2026 03:13PM UTC coverage: 89.757% (-3.2%) from 92.924%
24462481292

push

github

web-flow
update the default Pex version to v2.92.2 (#23260)

Release Notes:
 * https://github.com/pex-tool/pex/releases/tag/v2.92.2

3 of 3 new or added lines in 1 file covered. (100.0%)

2567 existing lines in 128 files now uncovered.

82405 of 91809 relevant lines covered (89.76%)

3.6 hits per line

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

68.25
/src/python/pants/backend/python/macros/python_requirements.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
4✔
5

6
import os
4✔
7
from collections.abc import Callable, Iterator
4✔
8
from itertools import chain
4✔
9
from typing import Any
4✔
10

11
import toml
4✔
12

13
from pants.backend.python.macros.common_fields import (
4✔
14
    ModuleMappingField,
15
    RequirementsOverrideField,
16
    TypeStubsModuleMappingField,
17
)
18
from pants.backend.python.macros.common_requirements_rule import _generate_requirements
4✔
19
from pants.backend.python.subsystems.setup import PythonSetup
4✔
20
from pants.backend.python.target_types import PythonRequirementResolveField, PythonRequirementTarget
4✔
21
from pants.engine.rules import collect_rules, rule
4✔
22
from pants.engine.target import (
4✔
23
    COMMON_TARGET_FIELDS,
24
    GeneratedTargets,
25
    GenerateTargetsRequest,
26
    SingleSourceField,
27
    TargetGenerator,
28
)
29
from pants.engine.unions import UnionMembership, UnionRule
4✔
30
from pants.util.logging import LogLevel
4✔
31
from pants.util.pip_requirement import PipRequirement
4✔
32
from pants.util.requirements import parse_requirements_file
4✔
33
from pants.util.strutil import help_text, softwrap
4✔
34

35

36
def parse_pyproject_toml(pyproject_toml: str, *, rel_path: str) -> Iterator[PipRequirement]:
4✔
UNCOV
37
    parsed: dict[str, Any] = toml.loads(pyproject_toml)
×
UNCOV
38
    deps_vals: list[str] = parsed.get("project", {}).get("dependencies", [])
×
UNCOV
39
    optional_dependencies: dict[str, list[str]] = parsed.get("project", {}).get(
×
40
        "optional-dependencies", {}
41
    )
UNCOV
42
    if not deps_vals and not optional_dependencies:
×
UNCOV
43
        raise KeyError(
×
44
            softwrap(
45
                "No section `project.dependencies` or `project.optional-dependencies` "
46
                f"found in {rel_path}"
47
            )
48
        )
UNCOV
49
    for dep in deps_vals:
×
UNCOV
50
        dep, _, _ = dep.partition("--")
×
UNCOV
51
        dep = dep.strip().rstrip("\\")
×
UNCOV
52
        if not dep or dep.startswith(("#", "-")):
×
53
            continue
×
UNCOV
54
        yield PipRequirement.parse(dep, description_of_origin=rel_path)
×
UNCOV
55
    for dep in chain.from_iterable(optional_dependencies.values()):
×
UNCOV
56
        dep, _, _ = dep.partition("--")
×
UNCOV
57
        dep = dep.strip().rstrip("\\")
×
UNCOV
58
        if not dep or dep.startswith(("#", "-")):
×
59
            continue
×
UNCOV
60
        req = PipRequirement.parse(dep, description_of_origin=rel_path)
×
UNCOV
61
        yield req
×
62

63

64
class PythonRequirementsSourceField(SingleSourceField):
4✔
65
    default = "requirements.txt"
4✔
66
    required = False
4✔
67

68

69
class PythonRequirementsTargetGenerator(TargetGenerator):
4✔
70
    alias = "python_requirements"
4✔
71
    help = help_text(
4✔
72
        """
73
        Generate a `python_requirement` for each entry in a requirements.txt-style or PEP 621
74
        compliant `pyproject.toml` file. The choice of parser for the `source` field is determined
75
        by the file name. If the `source` field ends with `pyproject.toml`, then the file is
76
        assumed to be a PEP 621 compliant file. Any other file name uses the requirements.txt-style
77
        parser.
78

79
        Further details about pip-style requirements files are available from the PyPA documentation:
80
        https://pip.pypa.io/en/latest/reference/requirements-file-format/. However, pip options like
81
        `--hash` are (for now) ignored.
82

83
        Pants will not follow `-r reqs.txt` lines. Instead, add a dedicated `python_requirements`
84
        target generator for that additional requirements file.
85

86
        Further details about PEP 621 and `pyproject.toml` files are available from the PEP itself:
87
        https://peps.python.org/pep-0621/. If the `project.optional-dependencies` table is
88
        included, Pants will save the key/name of the optional dependency group as a tag on the
89
        generated `python_requirement`.
90
        """
91
    )
92
    generated_target_cls = PythonRequirementTarget
4✔
93
    # Note that this does not have a `dependencies` field.
94
    core_fields = (
4✔
95
        *COMMON_TARGET_FIELDS,
96
        ModuleMappingField,
97
        TypeStubsModuleMappingField,
98
        PythonRequirementsSourceField,
99
        RequirementsOverrideField,
100
    )
101
    copied_fields = COMMON_TARGET_FIELDS
4✔
102
    moved_fields = (PythonRequirementResolveField,)
4✔
103

104

105
class GenerateFromPythonRequirementsRequest(GenerateTargetsRequest):
4✔
106
    generate_from = PythonRequirementsTargetGenerator
4✔
107

108

109
@rule(
4✔
110
    desc=(
111
        "Generate `python_requirement` targets from requirements.txt or PEP 621 compliant "
112
        "pyproject.toml"
113
    ),
114
    level=LogLevel.DEBUG,
115
)
116
async def generate_from_python_requirement(
4✔
117
    request: GenerateFromPythonRequirementsRequest,
118
    union_membership: UnionMembership,
119
    python_setup: PythonSetup,
120
) -> GeneratedTargets:
121
    generator = request.generator
2✔
122
    requirements_rel_path = generator[PythonRequirementsSourceField].value
2✔
123
    callback: Callable[[bytes, str], Iterator[PipRequirement]]
124
    if os.path.basename(requirements_rel_path) == "pyproject.toml":
2✔
UNCOV
125
        callback = parse_pyproject_callback
×
126
    else:
127
        callback = parse_requirements_callback
2✔
128
    result = await _generate_requirements(
2✔
129
        request,
130
        union_membership,
131
        python_setup,
132
        parse_requirements_callback=callback,
133
    )
134
    return GeneratedTargets(request.generator, result)
2✔
135

136

137
def parse_requirements_callback(file_contents: bytes, file_path: str) -> Iterator[PipRequirement]:
4✔
138
    return parse_requirements_file(file_contents.decode(), rel_path=file_path)
2✔
139

140

141
def parse_pyproject_callback(file_contents: bytes, file_path: str) -> Iterator[PipRequirement]:
4✔
UNCOV
142
    return parse_pyproject_toml(file_contents.decode(), rel_path=file_path)
×
143

144

145
def rules():
4✔
146
    return (
4✔
147
        *collect_rules(),
148
        UnionRule(GenerateTargetsRequest, GenerateFromPythonRequirementsRequest),
149
    )
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

© 2026 Coveralls, Inc