49643
|
1 # SPDX-License-Identifier: MIT
|
|
2
|
|
3
|
|
4 import functools
|
|
5 import types
|
|
6
|
|
7 from ._make import _make_ne
|
|
8
|
|
9
|
|
10 _operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="}
|
|
11
|
|
12
|
|
13 def cmp_using(
|
|
14 eq=None,
|
|
15 lt=None,
|
|
16 le=None,
|
|
17 gt=None,
|
|
18 ge=None,
|
|
19 require_same_type=True,
|
|
20 class_name="Comparable",
|
|
21 ):
|
|
22 """
|
|
23 Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and
|
|
24 ``cmp`` arguments to customize field comparison.
|
|
25
|
|
26 The resulting class will have a full set of ordering methods if
|
|
27 at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided.
|
|
28
|
|
29 :param Optional[callable] eq: `callable` used to evaluate equality
|
|
30 of two objects.
|
|
31 :param Optional[callable] lt: `callable` used to evaluate whether
|
|
32 one object is less than another object.
|
|
33 :param Optional[callable] le: `callable` used to evaluate whether
|
|
34 one object is less than or equal to another object.
|
|
35 :param Optional[callable] gt: `callable` used to evaluate whether
|
|
36 one object is greater than another object.
|
|
37 :param Optional[callable] ge: `callable` used to evaluate whether
|
|
38 one object is greater than or equal to another object.
|
|
39
|
|
40 :param bool require_same_type: When `True`, equality and ordering methods
|
|
41 will return `NotImplemented` if objects are not of the same type.
|
|
42
|
|
43 :param Optional[str] class_name: Name of class. Defaults to 'Comparable'.
|
|
44
|
|
45 See `comparison` for more details.
|
|
46
|
|
47 .. versionadded:: 21.1.0
|
|
48 """
|
|
49
|
|
50 body = {
|
|
51 "__slots__": ["value"],
|
|
52 "__init__": _make_init(),
|
|
53 "_requirements": [],
|
|
54 "_is_comparable_to": _is_comparable_to,
|
|
55 }
|
|
56
|
|
57 # Add operations.
|
|
58 num_order_functions = 0
|
|
59 has_eq_function = False
|
|
60
|
|
61 if eq is not None:
|
|
62 has_eq_function = True
|
|
63 body["__eq__"] = _make_operator("eq", eq)
|
|
64 body["__ne__"] = _make_ne()
|
|
65
|
|
66 if lt is not None:
|
|
67 num_order_functions += 1
|
|
68 body["__lt__"] = _make_operator("lt", lt)
|
|
69
|
|
70 if le is not None:
|
|
71 num_order_functions += 1
|
|
72 body["__le__"] = _make_operator("le", le)
|
|
73
|
|
74 if gt is not None:
|
|
75 num_order_functions += 1
|
|
76 body["__gt__"] = _make_operator("gt", gt)
|
|
77
|
|
78 if ge is not None:
|
|
79 num_order_functions += 1
|
|
80 body["__ge__"] = _make_operator("ge", ge)
|
|
81
|
|
82 type_ = types.new_class(
|
|
83 class_name, (object,), {}, lambda ns: ns.update(body)
|
|
84 )
|
|
85
|
|
86 # Add same type requirement.
|
|
87 if require_same_type:
|
|
88 type_._requirements.append(_check_same_type)
|
|
89
|
|
90 # Add total ordering if at least one operation was defined.
|
|
91 if 0 < num_order_functions < 4:
|
|
92 if not has_eq_function:
|
|
93 # functools.total_ordering requires __eq__ to be defined,
|
|
94 # so raise early error here to keep a nice stack.
|
|
95 raise ValueError(
|
|
96 "eq must be define is order to complete ordering from "
|
|
97 "lt, le, gt, ge."
|
|
98 )
|
|
99 type_ = functools.total_ordering(type_)
|
|
100
|
|
101 return type_
|
|
102
|
|
103
|
|
104 def _make_init():
|
|
105 """
|
|
106 Create __init__ method.
|
|
107 """
|
|
108
|
|
109 def __init__(self, value):
|
|
110 """
|
|
111 Initialize object with *value*.
|
|
112 """
|
|
113 self.value = value
|
|
114
|
|
115 return __init__
|
|
116
|
|
117
|
|
118 def _make_operator(name, func):
|
|
119 """
|
|
120 Create operator method.
|
|
121 """
|
|
122
|
|
123 def method(self, other):
|
|
124 if not self._is_comparable_to(other):
|
|
125 return NotImplemented
|
|
126
|
|
127 result = func(self.value, other.value)
|
|
128 if result is NotImplemented:
|
|
129 return NotImplemented
|
|
130
|
|
131 return result
|
|
132
|
|
133 method.__name__ = "__%s__" % (name,)
|
|
134 method.__doc__ = "Return a %s b. Computed by attrs." % (
|
|
135 _operation_names[name],
|
|
136 )
|
|
137
|
|
138 return method
|
|
139
|
|
140
|
|
141 def _is_comparable_to(self, other):
|
|
142 """
|
|
143 Check whether `other` is comparable to `self`.
|
|
144 """
|
|
145 for func in self._requirements:
|
|
146 if not func(self, other):
|
|
147 return False
|
|
148 return True
|
|
149
|
|
150
|
|
151 def _check_same_type(self, other):
|
|
152 """
|
|
153 Return True if *self* and *other* are of the same type, False otherwise.
|
|
154 """
|
|
155 return other.value.__class__ is self.value.__class__
|