123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- # 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__
|