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

pantsbuild / pants / 23838324561

01 Apr 2026 07:59AM UTC coverage: 60.966% (-31.9%) from 92.907%
23838324561

Pull #23204

github

web-flow
Merge fd6066932 into 0c78ceb96
Pull Request #23204: Port ScalarField, AsyncFieldMixin and friends to rust

8 of 12 new or added lines in 2 files covered. (66.67%)

19216 existing lines in 560 files now uncovered.

39119 of 64165 relevant lines covered (60.97%)

0.96 hits per line

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

97.56
/src/python/pants/backend/python/util_rules/ancestor_files.py
1
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
2✔
5

6
import os
2✔
7
from dataclasses import dataclass
2✔
8

9
from pants.engine.fs import EMPTY_SNAPSHOT, PathGlobs, Snapshot
2✔
10
from pants.engine.intrinsics import digest_to_snapshot, get_digest_contents
2✔
11
from pants.engine.rules import collect_rules, implicitly, rule
2✔
12

13

14
@dataclass(frozen=True)
2✔
15
class AncestorFilesRequest:
2✔
16
    """A request for ancestor files of the given names.
17

18
    "Ancestor files" means all files with one of the given names that are siblings of, or in parent
19
    directories of, a `.py` or `.pyi` file in the input_files.
20
    """
21

22
    input_files: tuple[str, ...]
2✔
23
    requested: tuple[str, ...]
2✔
24
    ignore_empty_files: bool = False
2✔
25

26

27
@dataclass(frozen=True)
2✔
28
class AncestorFiles:
2✔
29
    """Any ancestor files found."""
30

31
    snapshot: Snapshot
2✔
32

33

34
def putative_ancestor_files(input_files: tuple[str, ...], requested: tuple[str, ...]) -> set[str]:
2✔
35
    """Return the paths of potentially missing ancestor files.
36

37
    NB: The sources are expected to not have had their source roots stripped.
38
    Therefore this function will consider superfluous files at and above the source roots,
39
    (e.g., src/python/<name>, src/<name>). It is the caller's responsibility to filter these
40
    out if necessary.
41
    """
42
    packages: set[str] = set()
2✔
43
    for input_file in input_files:
2✔
44
        if not input_file.endswith((".py", ".pyi")):
2✔
UNCOV
45
            continue
×
46
        pkg_dir = os.path.dirname(input_file)
2✔
47
        if pkg_dir in packages:
2✔
48
            continue
2✔
49
        package = ""
2✔
50
        packages.add(package)
2✔
51
        for component in pkg_dir.split(os.sep):
2✔
52
            package = os.path.join(package, component)
2✔
53
            packages.add(package)
2✔
54

55
    return {
2✔
56
        os.path.join(package, requested_f) for package in packages for requested_f in requested
57
    } - set(input_files)
58

59

60
@rule
2✔
61
async def find_ancestor_files(request: AncestorFilesRequest) -> AncestorFiles:
2✔
62
    putative = putative_ancestor_files(request.input_files, request.requested)
2✔
63
    if not putative:
2✔
64
        return AncestorFiles(EMPTY_SNAPSHOT)
2✔
65

66
    # NB: This will intentionally _not_ error on any unmatched globs.
67
    globs = PathGlobs(putative)
2✔
68
    if request.ignore_empty_files:
2✔
69
        digest_contents = await get_digest_contents(**implicitly({globs: PathGlobs}))
1✔
70
        snapshot = await digest_to_snapshot(
1✔
71
            **implicitly(PathGlobs([fc.path for fc in digest_contents if fc.content.strip()]))
72
        )
73
    else:
74
        snapshot = await digest_to_snapshot(**implicitly({globs: PathGlobs}))
2✔
75

76
    return AncestorFiles(snapshot)
2✔
77

78

79
def rules():
2✔
80
    return collect_rules()
2✔
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