Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 71b4638

Browse files
botanthynek
andauthored
New attr.cmp_using helper function (#787)
* New attr.cmp_using helper function * Updated cmp_using to use new_class instead of direct patching * Removed stray annotation that make tests fail in PY2 * Skip test if PY2 because it won't raise TypeError when a method returns NotImplemented * Skip test if PY2 because it won't raise TypeError when a method returns NotImplemented * Fixed PY2 tests related to total_ordering * Added tests to complete converage * Moved class_name argument to definition and made it non-optional * Fixed typo in variable name * Revert accidental black formatting of __init__.pyi in commit 2b7970b Co-authored-by: Hynek Schlawack <[email protected]>
1 parent 253f229 commit 71b4638

File tree

4 files changed

+674
-0
lines changed

4 files changed

+674
-0
lines changed

src/attr/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from functools import partial
66

77
from . import converters, exceptions, filters, setters, validators
8+
from ._cmp import cmp_using
89
from ._config import get_run_validators, set_run_validators
910
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
1011
from ._make import (
@@ -52,6 +53,7 @@
5253
"attrib",
5354
"attributes",
5455
"attrs",
56+
"cmp_using",
5557
"converters",
5658
"evolve",
5759
"exceptions",

src/attr/_cmp.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from __future__ import absolute_import, division, print_function
2+
3+
import functools
4+
5+
from ._compat import new_class
6+
from ._make import _make_ne
7+
8+
9+
_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="}
10+
11+
12+
def cmp_using(
13+
eq=None,
14+
lt=None,
15+
le=None,
16+
gt=None,
17+
ge=None,
18+
require_same_type=True,
19+
class_name="Comparable",
20+
):
21+
"""
22+
Utility function that creates a class with customized equality and
23+
ordering methods.
24+
25+
The resulting class will have a full set of ordering methods if
26+
at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided.
27+
28+
:param Optional[callable] eq: `callable` used to evaluate equality
29+
of two objects.
30+
:param Optional[callable] lt: `callable` used to evaluate whether
31+
one object is less than another object.
32+
:param Optional[callable] le: `callable` used to evaluate whether
33+
one object is less than or equal to another object.
34+
:param Optional[callable] gt: `callable` used to evaluate whether
35+
one object is greater than another object.
36+
:param Optional[callable] ge: `callable` used to evaluate whether
37+
one object is greater than or equal to another object.
38+
39+
:param bool require_same_type: When `True`, equality and ordering methods
40+
will return `NotImplemented` if objects are not of the same type.
41+
42+
:param Optional[str] class_name: Name of class. Defaults to 'Comparable'.
43+
44+
.. versionadded:: 21.1.0
45+
"""
46+
47+
body = {
48+
"__slots__": ["value"],
49+
"__init__": _make_init(),
50+
"_requirements": [],
51+
"_is_comparable_to": _is_comparable_to,
52+
}
53+
54+
# Add operations.
55+
num_order_functions = 0
56+
has_eq_function = False
57+
58+
if eq is not None:
59+
has_eq_function = True
60+
body["__eq__"] = _make_operator("eq", eq)
61+
body["__ne__"] = _make_ne()
62+
63+
if lt is not None:
64+
num_order_functions += 1
65+
body["__lt__"] = _make_operator("lt", lt)
66+
67+
if le is not None:
68+
num_order_functions += 1
69+
body["__le__"] = _make_operator("le", le)
70+
71+
if gt is not None:
72+
num_order_functions += 1
73+
body["__gt__"] = _make_operator("gt", gt)
74+
75+
if ge is not None:
76+
num_order_functions += 1
77+
body["__ge__"] = _make_operator("ge", ge)
78+
79+
type_ = new_class(class_name, (object,), {}, lambda ns: ns.update(body))
80+
81+
# Add same type requirement.
82+
if require_same_type:
83+
type_._requirements.append(_check_same_type)
84+
85+
# Add total ordering if at least one operation was defined.
86+
if 0 < num_order_functions < 4:
87+
if not has_eq_function:
88+
# functools.total_ordering requires __eq__ to be defined,
89+
# so raise early error here to keep a nice stack.
90+
raise ValueError(
91+
"eq must be define is order to complete ordering from "
92+
"lt, le, gt, ge."
93+
)
94+
type_ = functools.total_ordering(type_)
95+
96+
return type_
97+
98+
99+
def _make_init():
100+
"""
101+
Create __init__ method.
102+
"""
103+
104+
def __init__(self, value):
105+
"""
106+
Initialize object with *value*.
107+
"""
108+
self.value = value
109+
110+
return __init__
111+
112+
113+
def _make_operator(name, func):
114+
"""
115+
Create operator method.
116+
"""
117+
118+
def method(self, other):
119+
if not self._is_comparable_to(other):
120+
return NotImplemented
121+
122+
result = func(self.value, other.value)
123+
if result is NotImplemented:
124+
return NotImplemented
125+
126+
return result
127+
128+
method.__name__ = "__%s__" % (name,)
129+
method.__doc__ = "Return a %s b. Computed by attrs." % (
130+
_operation_names[name],
131+
)
132+
133+
return method
134+
135+
136+
def _is_comparable_to(self, other):
137+
"""
138+
Check whether `other` is comparable to `self`.
139+
"""
140+
for func in self._requirements:
141+
if not func(self, other):
142+
return False
143+
return True
144+
145+
146+
def _check_same_type(self, other):
147+
"""
148+
Return True if *self* and *other* are of the same type, False otherwise.
149+
"""
150+
return other.value.__class__ is self.value.__class__

src/attr/_cmp.pyi

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from typing import Type
2+
3+
from . import _CompareWithType
4+
5+
6+
def cmp_using(
7+
eq: Optional[_CompareWithType],
8+
lt: Optional[_CompareWithType],
9+
le: Optional[_CompareWithType],
10+
gt: Optional[_CompareWithType],
11+
ge: Optional[_CompareWithType],
12+
require_same_type: bool,
13+
class_name: str,
14+
) -> Type: ...

0 commit comments

Comments
 (0)