You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

161 lines
4.0 KiB

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