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

beartype / plum / 18142089570

30 Sep 2025 08:06PM UTC coverage: 99.532% (-0.08%) from 99.61%
18142089570

Pull #195

github

web-flow
Merge 8f387be49 into 60d178297
Pull Request #195: refactor: use dependency groups

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

1 existing line in 1 file now uncovered.

1277 of 1283 relevant lines covered (99.53%)

6.96 hits per line

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

99.09
/plum/type.py
1
import abc
7✔
2
import sys
7✔
3
import typing
7✔
4
import warnings
7✔
5
from collections.abc import Hashable
7✔
6
from typing import Union, get_args, get_origin
7✔
7

8
from typing_extensions import Self, TypeGuard
7✔
9

10
from beartype.vale._core._valecore import BeartypeValidator
7✔
11

12
try:  # pragma: specific no cover 3.9
13
    from types import UnionType
14
except ImportError:  # pragma: specific no cover 3.10 3.11
15

16
    class UnionType:
17
        """Replacement for :class:`types.UnionType`."""
18

19

20
__all__ = [
7✔
21
    "PromisedType",
22
    "ModuleType",
23
    "type_mapping",
24
    "resolve_type_hint",
25
    "is_faithful",
26
]
27

28

29
class ResolvableType(type):
7✔
30
    """A resolvable type that will resolve to `type` after `type` has been delivered via
31
    :meth:`.ResolvableType.deliver`. Before then, it will resolve to itself.
32

33
    Args:
34
        name (str): Name of the type to be delivered.
35
    """
36

37
    def __init__(self, name: str) -> None:
7✔
38
        type.__init__(self, name, (), {})
7✔
39
        self._type = None
7✔
40

41
    def __new__(self, name: str) -> Self:
7✔
42
        return type.__new__(self, name, (), {})
7✔
43

44
    def deliver(self, type: type) -> Self:
7✔
45
        """Deliver the type.
46

47
        Args:
48
            type (type): Type to deliver.
49

50
        Returns:
51
            :class:`ResolvableType`: `self`.
52
        """
53
        self._type = type
7✔
54
        return self
7✔
55

56
    def resolve(self) -> Union[type, Self]:
7✔
57
        """Resolve the type.
58

59
        Returns:
60
            type: If no type has been delivered, this will return itself. If a type
61
                `type` has been delivered via :meth:`.ResolvableType.deliver`, this will
62
                return that type.
63
        """
64
        if self._type is None:
7✔
65
            return self
7✔
66
        else:
67
            return self._type
7✔
68

69

70
class PromisedType(ResolvableType):
7✔
71
    """A type that is promised to be available when you will you need it.
72

73
    Args:
74
        name (str, optional): Name of the type that is promised. Defaults to
75
            `"SomeType"`.
76
    """
77

78
    def __init__(self, name: str = "SomeType") -> None:
7✔
79
        ResolvableType.__init__(self, f"PromisedType[{name}]")
7✔
80
        self._name = name
7✔
81

82
    def __new__(cls, name: str = "SomeType") -> Self:
7✔
83
        return ResolvableType.__new__(cls, f"PromisedType[{name}]")
7✔
84

85

86
class ModuleType(ResolvableType):
7✔
87
    """A type from another module.
88

89
    Args:
90
        module (str): Module that the type lives in.
91
        name (str): Name of the type that is promised.
92
        allow_fail (bool, optional): If the type is does not exist in `module`,
93
            do not raise an `AttributeError`.
94
    """
95

96
    def __init__(self, module: str, name: str, allow_fail: bool = False) -> None:
7✔
97
        if module in {"__builtin__", "__builtins__"}:
7✔
98
            module = "builtins"
7✔
99
        ResolvableType.__init__(self, f"ModuleType[{module}.{name}]")
7✔
100
        self._name = name
7✔
101
        self._module = module
7✔
102
        self._allow_fail = allow_fail
7✔
103

104
    def __new__(cls, module: str, name: str, allow_fail: bool = False) -> Self:
7✔
105
        return ResolvableType.__new__(cls, f"ModuleType[{module}.{name}]")
7✔
106

107
    def retrieve(self) -> bool:
7✔
108
        """Attempt to retrieve the type from the reference module.
109

110
        Returns:
111
            bool: Whether the retrieval succeeded.
112
        """
113
        if self._type is None and self._module in sys.modules:
7✔
114
            type = sys.modules[self._module]
7✔
115
            for name in self._name.split("."):
7✔
116
                # If `type` does not contain `name` and `self._allow_fail` is
117
                # set, then silently fail.
118
                if not hasattr(type, name) and self._allow_fail:
7✔
119
                    return False
7✔
120
                type = getattr(type, name)
7✔
121
            self.deliver(type)
7✔
122
        return self._type is not None
7✔
123

124

125
def _is_hint(x: object) -> bool:
7✔
126
    """Check if an object is a type hint.
127

128
    Args:
129
        x (object): Object.
130

131
    Returns:
132
        bool: `True` if `x` is a type hint and `False` otherwise.
133
    """
134
    try:
7✔
135
        if x.__module__ == "builtins":
7✔
136
            # Check if `x` is a subscripted built-in. We do this by checking the module
137
            # of the type of `x`.
138
            x = type(x)
7✔
139
        return x.__module__ in {
7✔
140
            "types",  # E.g., `tuple[int]`
141
            "typing",
142
            "collections.abc",  # E.g., `Callable`
143
            "typing_extensions",
144
        }
145
    except AttributeError:
7✔
146
        return False
7✔
147

148

149
def _hashable(x: object) -> TypeGuard[Hashable]:
7✔
150
    """Check if an object is hashable.
151

152
    Args:
153
        x (object): Object to check.
154

155
    Returns:
156
        bool: `True` if `x` is hashable and `False` otherwise.
157
    """
158
    try:
7✔
159
        hash(x)
7✔
160
        return True
7✔
161
    except TypeError:
7✔
162
        return False
7✔
163

164

165
type_mapping = {}
7✔
166
"""dict: When running :func:`resolve_type_hint`, map keys in this dictionary to the
6✔
167
values."""
168

169

170
def resolve_type_hint(x):
7✔
171
    """Resolve all :class:`ResolvableType` in a type or type hint.
172

173
    Args:
174
        x (type or type hint): Type hint.
175

176
    Returns:
177
        type or type hint: `x`, but with all :class:`ResolvableType`\\s resolved.
178
    """
179
    if _hashable(x) and x in type_mapping:
7✔
180
        return resolve_type_hint(type_mapping[x])
7✔
181
    elif _is_hint(x):
7✔
182
        origin = get_origin(x)
7✔
183
        args = get_args(x)
7✔
184
        if args == ():
7✔
185
            # `origin` might not make sense here. For example, `get_origin(Any)` is
186
            # `None`. Since the hint wasn't subscripted, the right thing is to right the
187
            # hint itself.
188
            return x
7✔
189
        else:
190
            if origin is UnionType:  # pragma: specific no cover 3.9
191
                # The new union syntax was used.
192
                y = args[0]
193
                for arg in args[1:]:
194
                    y = y | arg
195
                return y
196
            else:
197
                # Do not resolve the arguments for `Literal`s.
198
                if origin != typing.Literal:
7✔
199
                    args = resolve_type_hint(args)
7✔
200

201
                return origin[args]
7✔
202

203
    elif x is None or x is Ellipsis:
7✔
204
        return x
7✔
205

206
    elif isinstance(x, tuple):
7✔
207
        return tuple(resolve_type_hint(arg) for arg in x)
7✔
208
    elif isinstance(x, list):
7✔
209
        return list(resolve_type_hint(arg) for arg in x)
7✔
210
    elif isinstance(x, type):
7✔
211
        if isinstance(x, ResolvableType):
7✔
212
            if isinstance(x, ModuleType) and not x.retrieve():
7✔
213
                # If the type could not be retrieved, then just return the
214
                # wrapper. Namely, `x.resolve()` will then return `x`, which means
215
                # that the below call will result in an infinite recursion.
216
                return x
7✔
217
            return resolve_type_hint(x.resolve())
7✔
218
        else:
219
            return x
7✔
220

221
    # For example, `Is[lambda x: x > 0]` is an example of a `BeartypeValidator`. We
222
    # shouldn't resolve those.
223
    elif isinstance(x, BeartypeValidator):
7✔
UNCOV
224
        return x
×
225

226
    else:
227
        warnings.warn(
7✔
228
            f"Could not resolve the type hint of `{x}`. "
229
            f"I have ended the resolution here to not make your code break, but some "
230
            f"types might not be working correctly. "
231
            f"Please open an issue at https://github.com/beartype/plum.",
232
            stacklevel=2,
233
        )
234
        return x
7✔
235

236

237
def is_faithful(x) -> bool:
7✔
238
    """Check whether a type hint is faithful.
239

240
    A type or type hint `t` is defined _faithful_ if, for all `x`, the following holds
241
    true::
242

243
        isinstance(x, x) == issubclass(type(x), t)
244

245
    You can control whether types are faithful or not by setting the attribute
246
    `__faithful__`::
247

248
        class UnfaithfulType:
249
            __faithful__ = False
250

251
    Args:
252
        x (type or type hint): Type hint.
253

254
    Returns:
255
        bool: Whether `x` is faithful or not.
256
    """
257
    return _is_faithful(resolve_type_hint(x))
7✔
258

259

260
def _is_faithful(x) -> bool:
7✔
261
    if _is_hint(x):
7✔
262
        origin = get_origin(x)
7✔
263
        args = get_args(x)
7✔
264
        if args == ():
7✔
265
            # Unsubscripted type hints tend to be faithful. For example, `Any`, `List`,
266
            # `Tuple`, `Dict`, `Callable`, and `Generator` are. When we come across a
267
            # counter-example, we will refine this logic.
268
            return True
7✔
269
        else:
270
            if origin in {typing.Union, typing.Optional}:
7✔
271
                return all(is_faithful(arg) for arg in args)
7✔
272
            else:
273
                return False
7✔
274

275
    elif x is None or x == Ellipsis:
7✔
276
        return True
7✔
277

278
    elif isinstance(x, (tuple, list)):
7✔
279
        return all(is_faithful(arg) for arg in x)
7✔
280
    elif isinstance(x, type):
7✔
281
        if hasattr(x, "__faithful__"):
7✔
282
            return x.__faithful__
7✔
283
        else:
284
            # This is the fallback method. Check whether `__instancecheck__` is default
285
            # or not. If it is, assume that it is faithful.
286
            return type(x).__instancecheck__ in {
7✔
287
                type.__instancecheck__,
288
                abc.ABCMeta.__instancecheck__,
289
            }
290
    else:
291
        warnings.warn(
7✔
292
            f"Could not determine whether `{x}` is faithful or not. "
293
            f"I have concluded that the type is not faithful, so your code might run "
294
            f"with subpar performance. "
295
            f"Please open an issue at https://github.com/beartype/plum.",
296
            stacklevel=2,
297
        )
298
        return False
7✔
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