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

beartype / plum / 12640304563

06 Jan 2025 09:04PM UTC coverage: 99.609% (-0.2%) from 99.765%
12640304563

Pull #202

github

web-flow
Merge c637c7839 into bad8ae6be
Pull Request #202: types(promotion): add typing to convert

6 of 8 new or added lines in 1 file covered. (75.0%)

1275 of 1280 relevant lines covered (99.61%)

4.97 hits per line

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

97.53
/plum/promotion.py
1
"""Promotion and conversion functions."""
2

3
__all__ = [
5✔
4
    "convert",
5
    "add_conversion_method",
6
    "conversion_method",
7
    "add_promotion_rule",
8
    "promote",
9
]
10

11
from typing import TYPE_CHECKING, Any, Callable, Protocol, Type, TypeVar
5✔
12

13
from typing_extensions import TypeAlias
5✔
14

15
from beartype.door import TypeHint
5✔
16

17
import plum.function
5✔
18
from . import _is_bearable
5✔
19
from .dispatcher import Dispatcher
5✔
20
from .repr import repr_short
5✔
21
from .type import resolve_type_hint
5✔
22

23
T = TypeVar("T")
5✔
24
R = TypeVar("R")
5✔
25

26
if TYPE_CHECKING:
5✔
NEW
27
    TypeTo = TypeVar("TypeTo")
×
NEW
28
    typeTypeTo: TypeAlias = type[TypeTo]
×
29
else:
30
    TypeTo = Any
5✔
31
    typeTypeTo = Any
5✔
32

33
_dispatch = Dispatcher()
5✔
34

35

36
@_dispatch
5✔
37
def convert(obj: object, type_to: typeTypeTo) -> TypeTo:
5✔
38
    """Convert an object to a particular type.
39

40
    Args:
41
        obj (object): Object to convert.
42
        type_to (type): Type to convert to.
43

44
    Returns:
45
        object: `obj` converted to type `type_to`.
46
    """
47
    type_to = resolve_type_hint(type_to)
5✔
48
    # TODO: Can we implement this without using `type`?!
49
    return _convert.invoke(type(obj), type_to)(obj, type_to)
5✔
50

51

52
# Deliver `convert`.
53
plum.function._promised_convert = convert
5✔
54

55

56
@_dispatch
5✔
57
def _convert(obj, type_to):
5✔
58
    if not _is_bearable(obj, resolve_type_hint(type_to)):
5✔
59
        raise TypeError(f"Cannot convert `{obj}` to `{repr_short(type_to)}`.")
5✔
60
    return obj
5✔
61

62

63
class _ConversionCallable(Protocol[T, R]):
5✔
64
    def __call__(self, obj: T) -> R: ...
5✔
65

66

67
def add_conversion_method(
5✔
68
    type_from: Type[T],
69
    type_to: Type[R],
70
    f: _ConversionCallable[T, R],
71
) -> None:
72
    """Add a conversion method to convert an object from one type to another.
73

74
    Args:
75
        type_from (type): Type to convert from.
76
        type_to (type): Type to convert to.
77
        f (function): Function that converts an object of type `type_from` to
78
            type `type_to`.
79
    """
80

81
    @_convert.dispatch
5✔
82
    def perform_conversion(obj: type_from, _: type_to):
5✔
83
        return f(obj)
5✔
84

85

86
def conversion_method(
5✔
87
    type_from: Type[T], type_to: Type[R]
88
) -> Callable[[_ConversionCallable[T, R]], _ConversionCallable[T, R]]:
89
    """Decorator to add a conversion method to convert an object from one
90
    type to another.
91

92
    Args:
93
        type_from (type): Type to convert from.
94
        type_to (type): Type to convert to.
95
    """
96

97
    def add_method(f: _ConversionCallable[T, R]) -> _ConversionCallable[T, R]:
5✔
98
        add_conversion_method(type_from, type_to, f)
5✔
99
        return f
5✔
100

101
    return add_method
5✔
102

103

104
# Add some common conversion methods.
105
add_conversion_method(object, tuple, lambda x: (x,))
5✔
106
add_conversion_method(tuple, tuple, lambda x: x)
5✔
107
add_conversion_method(list, tuple, tuple)
5✔
108
add_conversion_method(object, list, lambda x: [x])
5✔
109
add_conversion_method(list, list, lambda x: x)
5✔
110
add_conversion_method(tuple, list, list)
5✔
111
add_conversion_method(bytes, str, lambda x: x.decode("utf-8", "replace"))
5✔
112

113

114
@_dispatch
5✔
115
def _promotion_rule(type1, type2):
5✔
116
    """Promotion rule.
117

118
    Args:
119
        type1 (type): First type to promote.
120
        type2 (type): Second type to promote.
121

122
    Returns:
123
        type: Type to convert to.
124
    """
125
    type1 = resolve_type_hint(type1)
5✔
126
    type2 = resolve_type_hint(type2)
5✔
127
    if TypeHint(type1) <= TypeHint(type2):
5✔
128
        return type2
5✔
129
    elif TypeHint(type2) <= TypeHint(type1):
5✔
130
        return type1
5✔
131
    else:
132
        raise TypeError(
5✔
133
            f"No promotion rule for `{repr_short(type1)}` and `{repr_short(type2)}`."
134
        )
135

136

137
@_dispatch
5✔
138
def add_promotion_rule(type1, type2, type_to):
5✔
139
    """Add a promotion rule.
140

141
    Args:
142
        type1 (type): First type to promote.
143
        type2 (type): Second type to promote.
144
        type_to (type): Type to convert to.
145
    """
146

147
    @_promotion_rule.dispatch
5✔
148
    def rule(t1: type1, t2: type2):
5✔
149
        return type_to
5✔
150

151
    # If the types are the same, we don't need to add the reverse rule. Resolve the
152
    # types to handle the case where types are equal, but not identical.
153
    if TypeHint(resolve_type_hint(type1)) == TypeHint(resolve_type_hint(type2)):
5✔
154
        return  # Escape early.
5✔
155

156
    @_promotion_rule.dispatch
5✔
157
    def rule(t1: type2, t2: type1):  # noqa: F811
5✔
158
        return type_to
5✔
159

160

161
@_dispatch
5✔
162
def promote(obj1, obj2, *objs):
5✔
163
    """Promote objects to a common type.
164

165
    Args:
166
        \\*objs (object): Objects to convert.
167

168
    Returns:
169
        tuple: `objs`, but all converted to a common type.
170
    """
171
    # Convert to a single tuple.
172
    objs = (obj1, obj2) + objs
5✔
173

174
    # Get the types of the objects.
175
    # TODO: Can we implement this without calling `type`?!
176
    types = [type(obj) for obj in objs]
5✔
177

178
    def _promote_types(t0, t1):
5✔
179
        return resolve_type_hint(_promotion_rule.invoke(t0, t1)(t0, t1))
5✔
180

181
    # Find the common type.
182
    _promotion_rule._resolve_pending_registrations()
5✔
183
    common_type = _promote_types(types[0], types[1])
5✔
184
    for t in types[2:]:
5✔
185
        common_type = _promote_types(common_type, t)
5✔
186

187
    # Convert objects and return.
188
    return tuple(convert(obj, common_type) for obj in objs)
5✔
189

190

191
@_dispatch
5✔
192
def promote(obj: object):  # noqa: F811
5✔
193
    # Promote should always return a tuple to avoid edge cases.
194
    return (obj,)
5✔
195

196

197
@_dispatch
5✔
198
def promote():  # noqa: F811
5✔
199
    return ()
5✔
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