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 11c7bdf6f..21ec72803 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -8,6 +8,7 @@ from typing import Optional 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 3d1531e77..575e85b9a 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -354,3 +354,10 @@ if sys.version_info < (3, 5, 2): # pragma: no cover 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 f8cf55b4c..19333453f 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -8,6 +8,7 @@ from typing import Set 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 @@ class NodeKeywords(MutableMapping): 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 bf23e3202..83370fb43 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -9,6 +9,7 @@ import pytest 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 @@ class TestAssert_reprcompare_attrsclass: @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 21ec72803..c2a4e446f 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -8,7 +8,7 @@ from typing import Optional 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 575e85b9a..3898fb252 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -356,8 +356,7 @@ if sys.version_info < (3, 5, 2): # pragma: no cover 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 19333453f..2cab96d67 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -8,7 +8,7 @@ from typing import Set 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 @@ class NodeKeywords(MutableMapping): 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 83370fb43..8fce5e279 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -9,7 +9,7 @@ import pytest 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 @@ class TestAssert_reprcompare_attrsclass: @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")