• 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

84.62
/src/python/pants/engine/unions.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
1✔
5

6
from collections.abc import Callable
1✔
7
from dataclasses import dataclass
1✔
8
from typing import Any, Generic, TypeVar, cast, overload
1✔
9

10
from pants.engine.internals.native_engine import (  # noqa: F401 # re-export
1✔
11
    UnionMembership as UnionMembership,
12
)
13
from pants.engine.internals.native_engine import UnionRule as UnionRule  # noqa: F401 # re-export
1✔
14
from pants.util.memo import memoized_method
1✔
15

16
_T = TypeVar("_T", bound=type)
1✔
17

18

19
@overload
20
def union(cls: _T, *, in_scope_types: None = None) -> _T: ...
21

22

23
@overload
24
def union(cls: None = None, *, in_scope_types: list[type]) -> Callable[[_T], _T]: ...
25

26

27
def union(
1✔
28
    cls: _T | None = None, *, in_scope_types: list[type] | None = None
29
) -> Callable[[_T], _T] | _T:
30
    """A class decorator to allow a class to be a union base in the engine's mechanism for
31
    polymorphism.
32

33
    Annotating a class with @union allows other classes to register a `UnionRule(BaseClass,
34
    MemberClass)`. Then, given
35

36
    @rule(polymorphic=True)
37
    async def base_rule(arg: BaseClass) -> Output:
38
       raise NotImplementedError()
39

40
    and
41

42
    @rule
43
    async def member_rule(arg: MemberClass) -> Output:
44
        ....
45

46
    Then `await base_rule(**implicitly({member_class_instance: MemberClass}))` will dispatch
47
    to member_rule() at runtime based on the type of the argument.
48

49
    This allows you to write generic code without knowing what concrete classes might later
50
    implement that union.
51

52
    Often, union bases are abstract classes, and the members subclass the base, but this need not
53
    be the case.
54

55
    By default, in order to provide a stable extension API, when a `@union` is used in a @rule
56
    _only_ the provided parameter is available to callees, But in order to expand its API, a
57
    `@union` declaration may optionally include additional "in_scope_types", which are types
58
    which must already be in scope at callsites where the `@union` is used, and
59
    which are propagated to the callee, which must have an argument of that type.
60

61
    See https://www.pantsbuild.org/stable/docs/writing-plugins/the-rules-api/union-rules-advanced.
62
    """
63

64
    def decorator(cls: _T) -> _T:
1✔
65
        assert isinstance(cls, type)
1✔
66
        setattr(cls, "_is_union_for", cls)
1✔
67
        # TODO: this should involve an explicit interface soon, rather than one being implicitly
68
        # created with only the provided Param.
69
        setattr(cls, "_union_in_scope_types", tuple(in_scope_types) if in_scope_types else tuple())
1✔
70
        return cls
1✔
71

72
    return decorator if cls is None else decorator(cls)
1✔
73

74

75
def is_union(input_type: type) -> bool:
1✔
76
    """Return whether or not a type has been annotated with `@union`.
77

78
    This function is also implemented in Rust as `engine::externs::is_union`.
79
    """
UNCOV
80
    is_union: bool = input_type == getattr(input_type, "_is_union_for", None)
×
UNCOV
81
    return is_union
×
82

83

84
def union_in_scope_types(input_type: type) -> tuple[type, ...] | None:
1✔
85
    """If the given type is a `@union`, return its declared in-scope types.
86

87
    This function is also implemented in Rust as `engine::externs::union_in_scope_types`.
88
    """
UNCOV
89
    if not is_union(input_type):
×
90
        return None
×
UNCOV
91
    return cast("tuple[type, ...]", getattr(input_type, "_union_in_scope_types"))
×
92

93

94
@dataclass(frozen=True)
1✔
95
class _DistinctUnionTypePerSubclassGetter(Generic[_T]):
1✔
96
    _class: _T
1✔
97
    _in_scope_types: list[type] | None
1✔
98

99
    @memoized_method
1✔
100
    def _make_type_copy(self, objtype: type) -> _T:
1✔
101
        cls = self._class
1✔
102

103
        nu_type = cast(
1✔
104
            _T,
105
            type(
106
                cls.__name__,
107
                cls.__bases__,
108
                # NB: Override `__qualname__` so the attribute path is easily identifiable
109
                dict(cls.__dict__, __qualname__=f"{objtype.__qualname__}.{cls.__name__}"),
110
            ),
111
        )
112
        return union(in_scope_types=self._in_scope_types)(nu_type)  # type: ignore[arg-type]
1✔
113

114
    def __get__(self, obj: object | None, objtype: Any) -> _T:
1✔
115
        if objtype is None:
1✔
116
            objtype = type(obj)
×
117
        return self._make_type_copy(objtype)
1✔
118

119

120
@overload
121
def distinct_union_type_per_subclass(cls: _T, *, in_scope_types: None = None) -> _T: ...
122

123

124
@overload
125
def distinct_union_type_per_subclass(
126
    cls: None = None, *, in_scope_types: list[type]
127
) -> Callable[[_T], _T]: ...
128

129

130
def distinct_union_type_per_subclass(
1✔
131
    cls: _T | None = None, *, in_scope_types: list[type] | None = None
132
) -> _T | Callable[[_T], _T]:
133
    """Makes the decorated inner-class have a distinct, yet identical, union type per subclass.
134

135
    >>> class Foo:
136
    ...   @distinct_union_type_per_subclass
137
    ...   class Bar(cls):
138
    ...      pass
139
    ...
140
    >>> class Oof(Foo):
141
    ...   pass
142
    ...
143
    >>> Foo.Bar is not Oof.Bar
144
    True
145

146
    NOTE: In order to make identical class types, this should be used first of all decorators.
147
    NOTE: This works by making a "copy" of the class for each subclass. YMMV on how well this
148
        interacts with other decorators.
149
    """
150

151
    def decorator(cls: type):
1✔
152
        return _DistinctUnionTypePerSubclassGetter(cls, in_scope_types)
1✔
153

154
    return decorator if cls is None else decorator(cls)
1✔
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