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

pantsbuild / pants / 23173035367

17 Mar 2026 12:47AM UTC coverage: 91.371% (-1.6%) from 92.933%
23173035367

push

github

web-flow
update helm (and friends) to a recent 3.x seies (#23143)

v4 is a major breaking change, so holding off on that. (There was also
just a 3.20, but 3.19 has this reassuring series of bug fixes and was
also quite recent.)

2 of 2 new or added lines in 2 files covered. (100.0%)

1263 existing lines in 73 files now uncovered.

86196 of 94336 relevant lines covered (91.37%)

3.87 hits per line

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

85.71
/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
5✔
5

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

11
import toml
5✔
12

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

35

36
def parse_pyproject_toml(pyproject_toml: str, *, rel_path: str) -> Iterator[PipRequirement]:
5✔
37
    parsed: dict[str, Any] = toml.loads(pyproject_toml)
1✔
38
    deps_vals: list[str] = parsed.get("project", {}).get("dependencies", [])
1✔
39
    optional_dependencies: dict[str, list[str]] = parsed.get("project", {}).get(
1✔
40
        "optional-dependencies", {}
41
    )
42
    if not deps_vals and not optional_dependencies:
1✔
43
        raise KeyError(
1✔
44
            softwrap(
45
                "No section `project.dependencies` or `project.optional-dependencies` "
46
                f"found in {rel_path}"
47
            )
48
        )
49
    for dep in deps_vals:
1✔
50
        dep, _, _ = dep.partition("--")
1✔
51
        dep = dep.strip().rstrip("\\")
1✔
52
        if not dep or dep.startswith(("#", "-")):
1✔
53
            continue
×
54
        yield PipRequirement.parse(dep, description_of_origin=rel_path)
1✔
55
    for dep in chain.from_iterable(optional_dependencies.values()):
1✔
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):
5✔
65
    default = "requirements.txt"
5✔
66
    required = False
5✔
67

68

69
class PythonRequirementsTargetGenerator(TargetGenerator):
5✔
70
    alias = "python_requirements"
5✔
71
    help = help_text(
5✔
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
5✔
93
    # Note that this does not have a `dependencies` field.
94
    core_fields = (
5✔
95
        *COMMON_TARGET_FIELDS,
96
        ModuleMappingField,
97
        TypeStubsModuleMappingField,
98
        PythonRequirementsSourceField,
99
        RequirementsOverrideField,
100
    )
101
    copied_fields = COMMON_TARGET_FIELDS
5✔
102
    moved_fields = (PythonRequirementResolveField,)
5✔
103

104

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

108

109
@rule(
5✔
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(
5✔
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]:
5✔
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]:
5✔
UNCOV
142
    return parse_pyproject_toml(file_contents.decode(), rel_path=file_path)
×
143

144

145
def rules():
5✔
146
    return (
5✔
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