|
- # SPDX-License-Identifier: MIT
-
-
- import functools
- import types
-
- from ._make import __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.
-
- Args:
- eq (typing.Callable | None):
- Callable used to evaluate equality of two objects.
-
- lt (typing.Callable | None):
- Callable used to evaluate whether one object is less than another
- object.
-
- le (typing.Callable | None):
- Callable used to evaluate whether one object is less than or equal
- to another object.
-
- gt (typing.Callable | None):
- Callable used to evaluate whether one object is greater than
- another object.
-
- ge (typing.Callable | None):
- Callable used to evaluate whether one object is greater than or
- equal to another object.
-
- require_same_type (bool):
- When `True`, equality and ordering methods will return
- `NotImplemented` if objects are not of the same type.
-
- class_name (str | None): 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__"] = __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__
|