codekingpro/portable-devtools
114k
1# SPDX-License-Identifier: MIT2 3 4import functools5import types6 7from ._make import __ne__8 9 10_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="}11 12 13def 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 `attrs.field`'s ``eq``, ``order``,24 and ``cmp`` arguments to customize field comparison.25 26 The resulting class will have a full set of ordering methods if at least27 one of ``{lt, le, gt, ge}`` and ``eq`` are provided.28 29 Args:30 eq (typing.Callable | None):31 Callable used to evaluate equality of two objects.32 33 lt (typing.Callable | None):34 Callable used to evaluate whether one object is less than another35 object.36 37 le (typing.Callable | None):38 Callable used to evaluate whether one object is less than or equal39 to another object.40 41 gt (typing.Callable | None):42 Callable used to evaluate whether one object is greater than43 another object.44 45 ge (typing.Callable | None):46 Callable used to evaluate whether one object is greater than or47 equal to another object.48 49 require_same_type (bool):50 When `True`, equality and ordering methods will return51 `NotImplemented` if objects are not of the same type.52 53 class_name (str | None): Name of class. Defaults to "Comparable".54 55 See `comparison` for more details.56 57 .. versionadded:: 21.1.058 """59 60 body = {61 "__slots__": ["value"],62 "__init__": _make_init(),63 "_requirements": [],64 "_is_comparable_to": _is_comparable_to,65 }66 67 # Add operations.68 num_order_functions = 069 has_eq_function = False70 71 if eq is not None:72 has_eq_function = True73 body["__eq__"] = _make_operator("eq", eq)74 body["__ne__"] = __ne__75 76 if lt is not None:77 num_order_functions += 178 body["__lt__"] = _make_operator("lt", lt)79 80 if le is not None:81 num_order_functions += 182 body["__le__"] = _make_operator("le", le)83 84 if gt is not None:85 num_order_functions += 186 body["__gt__"] = _make_operator("gt", gt)87 88 if ge is not None:89 num_order_functions += 190 body["__ge__"] = _make_operator("ge", ge)91 92 type_ = types.new_class(93 class_name, (object,), {}, lambda ns: ns.update(body)94 )95 96 # Add same type requirement.97 if require_same_type:98 type_._requirements.append(_check_same_type)99 100 # Add total ordering if at least one operation was defined.101 if 0 < num_order_functions < 4:102 if not has_eq_function:103 # functools.total_ordering requires __eq__ to be defined,104 # so raise early error here to keep a nice stack.105 msg = "eq must be define is order to complete ordering from lt, le, gt, ge."106 raise ValueError(msg)107 type_ = functools.total_ordering(type_)108 109 return type_110 111 112def _make_init():113 """114 Create __init__ method.115 """116 117 def __init__(self, value):118 """119 Initialize object with *value*.120 """121 self.value = value122 123 return __init__124 125 126def _make_operator(name, func):127 """128 Create operator method.129 """130 131 def method(self, other):132 if not self._is_comparable_to(other):133 return NotImplemented134 135 result = func(self.value, other.value)136 if result is NotImplemented:137 return NotImplemented138 139 return result140 141 method.__name__ = f"__{name}__"142 method.__doc__ = (143 f"Return a {_operation_names[name]} b. Computed by attrs."144 )145 146 return method147 148 149def _is_comparable_to(self, other):150 """151 Check whether `other` is comparable to `self`.152 """153 return all(func(self, other) for func in self._requirements)154 155 156def _check_same_type(self, other):157 """158 Return True if *self* and *other* are of the same type, False otherwise.159 """160 return other.value.__class__ is self.value.__class__161 