From a79acf279a80bbbac0a05d8fadf62649753c668a Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Tue, 1 Oct 2019 20:40:13 +0200 Subject: [PATCH 1/2] Fix warnings with attrs 19.2 and fix object assertions attrs 19.2 deprecated cmp in favor of the dataclass-ish eq/order duo. This causes deprecation warnings that in turn break some of the cool new deep object comparisons. Since we at attrs expected this to be a problem, it shipped with helpers to write backward and forward compatible code. This PR uses that and avoids changed to minimal versions. --- src/_pytest/assertion/util.py | 15 ++++++++++++++- src/_pytest/compat.py | 7 +++++++ src/_pytest/mark/structures.py | 3 ++- testing/test_assertion.py | 3 ++- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 11c7bdf6f85..21ec72803a7 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -8,6 +8,7 @@ import _pytest._code from _pytest import outcomes from _pytest._io.saferepr import saferepr +from _pytest.compat import attrs_has_eq # The _reprcompare attribute on the util module is used by the new assertion # interpretation code and assertion rewriter to detect this plugin was @@ -112,6 +113,18 @@ def isattrs(obj): return getattr(obj, "__attrs_attrs__", None) is not None +if attrs_has_eq: + + def attrsfieldhaseq(a): + return a.eq + + +else: + + def attrsfieldhaseq(a): + return a.cmp + + def isiterable(obj): try: iter(obj) @@ -375,7 +388,7 @@ def _compare_eq_cls(left, right, verbose, type_fns): fields_to_check = [field for field, info in all_fields.items() if info.compare] elif isattrs(left): all_fields = left.__attrs_attrs__ - fields_to_check = [field.name for field in all_fields if field.cmp] + fields_to_check = [field.name for field in all_fields if attrsfieldhaseq(field)] same = [] diff = [] diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 3d1531e77c5..575e85b9ab9 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -354,3 +354,10 @@ def funcargnames(self): def overload(f): # noqa: F811 return f + + +attrs_has_eq = getattr(attr, "__version_info__", (0, 0)) >= (19, 2) +if attrs_has_eq: + attrs_no_eq = {"eq": False} +else: + attrs_no_eq = {"cmp": False} diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index f8cf55b4cb6..19333453fb3 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -8,6 +8,7 @@ import attr from ..compat import ascii_escaped +from ..compat import attrs_no_eq from ..compat import getfslineno from ..compat import NOTSET from _pytest.outcomes import fail @@ -367,7 +368,7 @@ def __repr__(self): return "".format(self.node) -@attr.s(cmp=False, hash=False) +@attr.s(hash=False, **attrs_no_eq) # type: ignore class NodeMarkers: """ internal structure for storing marks belonging to a node diff --git a/testing/test_assertion.py b/testing/test_assertion.py index bf23e32023a..83370fb4355 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -9,6 +9,7 @@ from _pytest import outcomes from _pytest.assertion import truncate from _pytest.assertion import util +from _pytest.compat import attrs_no_eq def mock_config(): @@ -687,7 +688,7 @@ def test_attrs_with_attribute_comparison_off(self): @attr.s class SimpleDataObject: field_a = attr.ib() - field_b = attr.ib(cmp=False) + field_b = attr.ib(**attrs_no_eq) left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "b") From c58b0fb4acefefc81a12ab92ad3f37384218ee78 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 5 Oct 2019 18:16:35 -0700 Subject: [PATCH 2/2] Use ATTRS_EQ_FIELD for attrs 19.2 compat --- src/_pytest/assertion/util.py | 18 ++++-------------- src/_pytest/compat.py | 7 +++---- src/_pytest/mark/structures.py | 5 +++-- testing/test_assertion.py | 4 ++-- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 21ec72803a7..c2a4e446ffb 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -8,7 +8,7 @@ import _pytest._code from _pytest import outcomes from _pytest._io.saferepr import saferepr -from _pytest.compat import attrs_has_eq +from _pytest.compat import ATTRS_EQ_FIELD # The _reprcompare attribute on the util module is used by the new assertion # interpretation code and assertion rewriter to detect this plugin was @@ -113,18 +113,6 @@ def isattrs(obj): return getattr(obj, "__attrs_attrs__", None) is not None -if attrs_has_eq: - - def attrsfieldhaseq(a): - return a.eq - - -else: - - def attrsfieldhaseq(a): - return a.cmp - - def isiterable(obj): try: iter(obj) @@ -388,7 +376,9 @@ def _compare_eq_cls(left, right, verbose, type_fns): fields_to_check = [field for field, info in all_fields.items() if info.compare] elif isattrs(left): all_fields = left.__attrs_attrs__ - fields_to_check = [field.name for field in all_fields if attrsfieldhaseq(field)] + fields_to_check = [ + field.name for field in all_fields if getattr(field, ATTRS_EQ_FIELD) + ] same = [] diff = [] diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 575e85b9ab9..3898fb252a1 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -356,8 +356,7 @@ def overload(f): # noqa: F811 return f -attrs_has_eq = getattr(attr, "__version_info__", (0, 0)) >= (19, 2) -if attrs_has_eq: - attrs_no_eq = {"eq": False} +if getattr(attr, "__version_info__", ()) >= (19, 2): + ATTRS_EQ_FIELD = "eq" else: - attrs_no_eq = {"cmp": False} + ATTRS_EQ_FIELD = "cmp" diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 19333453fb3..2cab96d675a 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -8,7 +8,7 @@ import attr from ..compat import ascii_escaped -from ..compat import attrs_no_eq +from ..compat import ATTRS_EQ_FIELD from ..compat import getfslineno from ..compat import NOTSET from _pytest.outcomes import fail @@ -368,7 +368,8 @@ def __repr__(self): return "".format(self.node) -@attr.s(hash=False, **attrs_no_eq) # type: ignore +# mypy cannot find this overload, remove when on attrs>=19.2 +@attr.s(hash=False, **{ATTRS_EQ_FIELD: False}) # type: ignore class NodeMarkers: """ internal structure for storing marks belonging to a node diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 83370fb4355..8fce5e279bb 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -9,7 +9,7 @@ from _pytest import outcomes from _pytest.assertion import truncate from _pytest.assertion import util -from _pytest.compat import attrs_no_eq +from _pytest.compat import ATTRS_EQ_FIELD def mock_config(): @@ -688,7 +688,7 @@ def test_attrs_with_attribute_comparison_off(self): @attr.s class SimpleDataObject: field_a = attr.ib() - field_b = attr.ib(**attrs_no_eq) + field_b = attr.ib(**{ATTRS_EQ_FIELD: False}) left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "b")