• 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/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

UNCOV
4
from __future__ import annotations
×
5

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

UNCOV
11
import toml
×
12

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

35

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

63

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

68

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

104

UNCOV
105
class GenerateFromPythonRequirementsRequest(GenerateTargetsRequest):
×
UNCOV
106
    generate_from = PythonRequirementsTargetGenerator
×
107

108

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

136

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

140

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

144

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

© 2025 Coveralls, Inc