Spaces:
Running
Running
# SPDX-License-Identifier: MIT | |
import functools | |
import types | |
from ._make import _make_ne | |
_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} | |
def cmp_using( | |
eq=None, | |
lt=None, | |
le=None, | |
gt=None, | |
ge=None, | |
require_same_type=True, | |
class_name="Comparable", | |
): | |
""" | |
Create a class that can be passed into `attrs.field`'s ``eq``, ``order``, | |
and ``cmp`` arguments to customize field comparison. | |
The resulting class will have a full set of ordering methods if at least | |
one of ``{lt, le, gt, ge}`` and ``eq`` are provided. | |
:param Optional[callable] eq: `callable` used to evaluate equality of two | |
objects. | |
:param Optional[callable] lt: `callable` used to evaluate whether one | |
object is less than another object. | |
:param Optional[callable] le: `callable` used to evaluate whether one | |
object is less than or equal to another object. | |
:param Optional[callable] gt: `callable` used to evaluate whether one | |
object is greater than another object. | |
:param Optional[callable] ge: `callable` used to evaluate whether one | |
object is greater than or equal to another object. | |
:param bool require_same_type: When `True`, equality and ordering methods | |
will return `NotImplemented` if objects are not of the same type. | |
:param Optional[str] class_name: Name of class. Defaults to 'Comparable'. | |
See `comparison` for more details. | |
.. versionadded:: 21.1.0 | |
""" | |
body = { | |
"__slots__": ["value"], | |
"__init__": _make_init(), | |
"_requirements": [], | |
"_is_comparable_to": _is_comparable_to, | |
} | |
# Add operations. | |
num_order_functions = 0 | |
has_eq_function = False | |
if eq is not None: | |
has_eq_function = True | |
body["__eq__"] = _make_operator("eq", eq) | |
body["__ne__"] = _make_ne() | |
if lt is not None: | |
num_order_functions += 1 | |
body["__lt__"] = _make_operator("lt", lt) | |
if le is not None: | |
num_order_functions += 1 | |
body["__le__"] = _make_operator("le", le) | |
if gt is not None: | |
num_order_functions += 1 | |
body["__gt__"] = _make_operator("gt", gt) | |
if ge is not None: | |
num_order_functions += 1 | |
body["__ge__"] = _make_operator("ge", ge) | |
type_ = types.new_class( | |
class_name, (object,), {}, lambda ns: ns.update(body) | |
) | |
# Add same type requirement. | |
if require_same_type: | |
type_._requirements.append(_check_same_type) | |
# Add total ordering if at least one operation was defined. | |
if 0 < num_order_functions < 4: | |
if not has_eq_function: | |
# functools.total_ordering requires __eq__ to be defined, | |
# so raise early error here to keep a nice stack. | |
msg = "eq must be define is order to complete ordering from lt, le, gt, ge." | |
raise ValueError(msg) | |
type_ = functools.total_ordering(type_) | |
return type_ | |
def _make_init(): | |
""" | |
Create __init__ method. | |
""" | |
def __init__(self, value): | |
""" | |
Initialize object with *value*. | |
""" | |
self.value = value | |
return __init__ | |
def _make_operator(name, func): | |
""" | |
Create operator method. | |
""" | |
def method(self, other): | |
if not self._is_comparable_to(other): | |
return NotImplemented | |
result = func(self.value, other.value) | |
if result is NotImplemented: | |
return NotImplemented | |
return result | |
method.__name__ = f"__{name}__" | |
method.__doc__ = ( | |
f"Return a {_operation_names[name]} b. Computed by attrs." | |
) | |
return method | |
def _is_comparable_to(self, other): | |
""" | |
Check whether `other` is comparable to `self`. | |
""" | |
return all(func(self, other) for func in self._requirements) | |
def _check_same_type(self, other): | |
""" | |
Return True if *self* and *other* are of the same type, False otherwise. | |
""" | |
return other.value.__class__ is self.value.__class__ | |