From 3d5bebfd47c1db8fa9231b001ca6813a5b1f651f Mon Sep 17 00:00:00 2001 From: poulami-sau <109125687+poulami-sau@users.noreply.github.com> Date: Tue, 23 Apr 2024 04:45:33 -0400 Subject: [PATCH 1/2] [8.1.x] Fixed Bug Regarding Attribute Error in pytest.approx For Types Implicitly Convertible to Numpy Arrays --- AUTHORS | 1 + changelog/12114.bugfix.rst | 1 + src/_pytest/python_api.py | 14 +++++++++----- testing/python/approx.py | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 changelog/12114.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 4c4d68df147..92d3a14c7c5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -318,6 +318,7 @@ Pierre Sassoulas Pieter Mulder Piotr Banaszkiewicz Piotr Helm +Poulami Sau Prakhar Gurunani Prashant Anand Prashant Sharma diff --git a/changelog/12114.bugfix.rst b/changelog/12114.bugfix.rst new file mode 100644 index 00000000000..79849692505 --- /dev/null +++ b/changelog/12114.bugfix.rst @@ -0,0 +1 @@ +Fixed attribute error in pytest.approx for types implicitly convertible to numpy arrays by converting other_side to a numpy array so that np_array_shape != other_side.shape can be properly checked. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 7e51da3194a..a4ab80e2b17 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -142,7 +142,7 @@ def __repr__(self) -> str: ) return f"approx({list_scalars!r})" - def _repr_compare(self, other_side: "ndarray") -> List[str]: + def _repr_compare(self, other_side: Union["ndarray", List[Any]]) -> List[str]: import itertools import math @@ -163,10 +163,14 @@ def get_value_from_nested_list( self._approx_scalar, self.expected.tolist() ) - if np_array_shape != other_side.shape: + # convert other_side to numpy array to ensure shape attribute is available + other_side_as_array = _as_numpy_array(other_side) + assert other_side_as_array is not None + + if np_array_shape != other_side_as_array.shape: return [ "Impossible to compare arrays with different shapes.", - f"Shapes: {np_array_shape} and {other_side.shape}", + f"Shapes: {np_array_shape} and {other_side_as_array.shape}", ] number_of_elements = self.expected.size @@ -175,7 +179,7 @@ def get_value_from_nested_list( different_ids = [] for index in itertools.product(*(range(i) for i in np_array_shape)): approx_value = get_value_from_nested_list(approx_side_as_seq, index) - other_value = get_value_from_nested_list(other_side, index) + other_value = get_value_from_nested_list(other_side_as_array, index) if approx_value != other_value: abs_diff = abs(approx_value.expected - other_value) max_abs_diff = max(max_abs_diff, abs_diff) @@ -188,7 +192,7 @@ def get_value_from_nested_list( message_data = [ ( str(index), - str(get_value_from_nested_list(other_side, index)), + str(get_value_from_nested_list(other_side_as_array, index)), str(get_value_from_nested_list(approx_side_as_seq, index)), ) for index in different_ids diff --git a/testing/python/approx.py b/testing/python/approx.py index 079667bd093..968e8828512 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -763,6 +763,23 @@ def test_numpy_array_wrong_shape(self): assert a12 != approx(a21) assert a21 != approx(a12) + def test_numpy_array_implicit_conversion(self): + np = pytest.importorskip("numpy") + + class ImplicitArray: + """Type which is implicitly convertible to a numpy array.""" + + def __init__(self, vals): + self.vals = vals + + def __array__(self, dtype=None, copy=None): + return np.array(self.vals) + + vec1 = ImplicitArray([1.0, 2.0, 3.0]) + vec2 = ImplicitArray([1.0, 2.0, 4.0]) + # see issue #12114 for test case + assert vec1 != approx(vec2) + def test_numpy_array_protocol(self): """ array-like objects such as tensorflow's DeviceArray are handled like ndarray. From dfa90bba2de684c9481a6d177eecbf4ccb33d37d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 25 Apr 2024 07:58:05 -0300 Subject: [PATCH 2/2] Apply suggestions from code review --- changelog/12114.bugfix.rst | 2 +- src/_pytest/python_api.py | 2 +- testing/python/approx.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/changelog/12114.bugfix.rst b/changelog/12114.bugfix.rst index 79849692505..220832da577 100644 --- a/changelog/12114.bugfix.rst +++ b/changelog/12114.bugfix.rst @@ -1 +1 @@ -Fixed attribute error in pytest.approx for types implicitly convertible to numpy arrays by converting other_side to a numpy array so that np_array_shape != other_side.shape can be properly checked. +Fixed error in :func:`pytest.approx` when used with `numpy` arrays and comparing with other types. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index a4ab80e2b17..f6986a6a43e 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -163,7 +163,7 @@ def get_value_from_nested_list( self._approx_scalar, self.expected.tolist() ) - # convert other_side to numpy array to ensure shape attribute is available + # Convert other_side to numpy array to ensure shape attribute is available. other_side_as_array = _as_numpy_array(other_side) assert other_side_as_array is not None diff --git a/testing/python/approx.py b/testing/python/approx.py index 968e8828512..cf8cc8d0375 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -763,7 +763,8 @@ def test_numpy_array_wrong_shape(self): assert a12 != approx(a21) assert a21 != approx(a12) - def test_numpy_array_implicit_conversion(self): + def test_numpy_array_implicit_conversion(self) -> None: + """#12114.""" np = pytest.importorskip("numpy") class ImplicitArray: