From 564d690c6beb29bc8d92d5f008f8f8802efbe53c Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Wed, 28 Oct 2026 13:17:49 -0300 Subject: [PATCH 1/7] Fix pytest.approx regression with JAX arrays --- src/_pytest/approx.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_pytest/approx.py b/src/_pytest/approx.py index 70a881e3326..10c88d42334 100644 --- a/src/_pytest/approx.py +++ b/src/_pytest/approx.py @@ -1033,7 +1033,10 @@ def _contains_decimal(expected: object) -> bool: if _is_sequence_like(expected): # ``.flat`` also makes a 0-d numpy array iterable, which it is not # otherwise, despite being sized and subscriptable. - values = getattr(expected, "flat", expected) + try: + values = getattr(expected, "flat", expected) + except NotImplementedError: # ``.flat`` raises like this on a jax.Array + return False # But JAX arrays cannot contain `Decimal` anyway return any(isinstance(value, Decimal) for value in values) return False From df640bc11c4356eee7cb7aa0dea64c5fb751123d Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Wed, 28 Oct 2026 13:18:07 -0300 Subject: [PATCH 2/7] Add regression test for #15284 --- testing/python/approx.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/testing/python/approx.py b/testing/python/approx.py index e0bfc37f85d..5ee2d6ec4c6 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -898,6 +898,40 @@ def __array__(self, dtype=None, copy=None): # see issue #12114 for test case assert vec1 != approx(vec2) + @pytest.mark.parametrize("values", (42, [42, 43], [[42, 43], [44, 45]])) + def test_custom_array_unimplemented_flat(self, values) -> None: + np = pytest.importorskip("numpy") + + class CustomArray: + def __init__(self, values, /): + self.values = np.asarray(values) + self.array_conversions = 0 + + def __array__(self, dtype=None, copy=None): + self.array_conversions += 1 + return np.array(self.values, dtype=dtype, copy=copy) + + def __getitem__(self, _): + raise AssertionError("Should not be called") + + def __len__(self): + return len(self.values) + + @property + def flat(self): + # jax.Array behaves like this (#15284). + raise NotImplementedError("this class does not provide .flat") + + actual = np.array(values) + expected = CustomArray(values) + + assert actual == approx(expected, rel=1e-3) + assert expected.array_conversions == 1 + assert actual + 1e-4 == approx(expected, rel=1e-3) + assert expected.array_conversions == 2 + assert actual + 1 != approx(expected, rel=1e-3) + assert expected.array_conversions == 3 + def test_numpy_array_protocol(self): """ array-like objects such as tensorflow's DeviceArray are handled like ndarray. From b014ec5185aaaa94a4ca676a725f8cde7eed72e6 Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Wed, 28 Oct 2026 13:18:17 -0300 Subject: [PATCH 3/7] Add changelog entry for #15284 --- changelog/15284.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/15284.bugfix.rst diff --git a/changelog/15284.bugfix.rst b/changelog/15284.bugfix.rst new file mode 100644 index 00000000000..51e82840544 --- /dev/null +++ b/changelog/15284.bugfix.rst @@ -0,0 +1 @@ +Fixed a regression in :func:`pytest.approx` with JAX arrays when an inexact float tolerance is supplied. From 59a315280d68ba7bf3b58dbea730275069942f7d Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Wed, 28 Oct 2026 13:23:05 -0300 Subject: [PATCH 4/7] Add myself to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 9e4327c7733..136ffd6c5ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -187,6 +187,7 @@ Floris Bruynooghe Frank Hoffmann Fraser Stark Freya Bruhin +Gabriel Gerlero Gabriel Landau Gabriel Reis Gang Eun Lee From c8f21b6c3b737fcd24713325fd3a0cc2208663fe Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Wed, 28 Oct 2026 13:34:56 -0300 Subject: [PATCH 5/7] Improve regression test --- testing/python/approx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/python/approx.py b/testing/python/approx.py index 5ee2d6ec4c6..73a3467b5d4 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -904,7 +904,7 @@ def test_custom_array_unimplemented_flat(self, values) -> None: class CustomArray: def __init__(self, values, /): - self.values = np.asarray(values) + self.values = values self.array_conversions = 0 def __array__(self, dtype=None, copy=None): @@ -915,7 +915,7 @@ def __getitem__(self, _): raise AssertionError("Should not be called") def __len__(self): - return len(self.values) + return AssertionError("Should not be called") @property def flat(self): From 473f8a7a6bf85407cfcf3744d76c760c8a8c67c3 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 29 Oct 2026 11:31:23 +0200 Subject: [PATCH 6/7] Tweaks --- testing/python/approx.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/testing/python/approx.py b/testing/python/approx.py index 73a3467b5d4..65bd88fcbbd 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -900,6 +900,8 @@ def __array__(self, dtype=None, copy=None): @pytest.mark.parametrize("values", (42, [42, 43], [[42, 43], [44, 45]])) def test_custom_array_unimplemented_flat(self, values) -> None: + """Test handling of an array which raises NotImplementedError from + `flat`, as is done by JAX (#15284).""" np = pytest.importorskip("numpy") class CustomArray: @@ -912,14 +914,13 @@ def __array__(self, dtype=None, copy=None): return np.array(self.values, dtype=dtype, copy=copy) def __getitem__(self, _): - raise AssertionError("Should not be called") + raise AssertionError("Should not be called") # pragma: no-cover def __len__(self): - return AssertionError("Should not be called") + raise AssertionError("Should not be called") # pragma: no-cover @property def flat(self): - # jax.Array behaves like this (#15284). raise NotImplementedError("this class does not provide .flat") actual = np.array(values) From 05b0df743044b53dec881a31ebb6a16a0f244591 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 29 Oct 2026 12:24:14 +0200 Subject: [PATCH 7/7] Under the covers --- testing/python/approx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/python/approx.py b/testing/python/approx.py index 65bd88fcbbd..35adcfea140 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -914,10 +914,10 @@ def __array__(self, dtype=None, copy=None): return np.array(self.values, dtype=dtype, copy=copy) def __getitem__(self, _): - raise AssertionError("Should not be called") # pragma: no-cover + raise AssertionError("Should not be called") # pragma: no cover def __len__(self): - raise AssertionError("Should not be called") # pragma: no-cover + raise AssertionError("Should not be called") # pragma: no cover @property def flat(self):