From 5845809d82d9f4895954a871e27c9b2e39acd79e Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Tue, 20 May 2025 15:50:48 +0100 Subject: [PATCH 01/16] Coerce unknown types to O dtype --- xarray/compat/array_api_compat.py | 13 +++++++++---- xarray/tests/test_dtypes.py | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index e1e5d5c5bdc..1b6c440cc7a 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -38,10 +38,15 @@ def _future_array_api_result_type(*arrays_and_dtypes, xp): def result_type(*arrays_and_dtypes, xp) -> np.dtype: - if xp is np or any( - isinstance(getattr(t, "dtype", t), np.dtype) for t in arrays_and_dtypes - ): - return xp.result_type(*arrays_and_dtypes) + is_np_dtype = [ + hasattr(t, "dtype") and isinstance(t.dtype, np.dtype) for t in arrays_and_dtypes + ] + if xp is np or any(is_np_dtype): + all_valid_arrays_and_dtypes = [ + t if is_np_dtype[i] or t.__class__.__module__ == "builtins" else np.object_ + for i, t in enumerate(arrays_and_dtypes) + ] + return xp.result_type(*all_valid_arrays_and_dtypes) else: return _future_array_api_result_type(*arrays_and_dtypes, xp=xp) diff --git a/xarray/tests/test_dtypes.py b/xarray/tests/test_dtypes.py index 0ccda1d8074..9b76e7d431a 100644 --- a/xarray/tests/test_dtypes.py +++ b/xarray/tests/test_dtypes.py @@ -32,6 +32,10 @@ class DummyArrayAPINamespace: ([np.dtype(" None: From ef3df17dbf4c7e209a820cc3d9a54918a513d2a8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 14:57:50 +0000 Subject: [PATCH 02/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/tests/test_dtypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xarray/tests/test_dtypes.py b/xarray/tests/test_dtypes.py index 9b76e7d431a..3b9c60517f9 100644 --- a/xarray/tests/test_dtypes.py +++ b/xarray/tests/test_dtypes.py @@ -32,10 +32,10 @@ class DummyArrayAPINamespace: ([np.dtype(" None: From 1756bc4ba9bb4a05f85b66de3ca07b2a3d7c129b Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Fri, 23 May 2025 14:34:39 +0100 Subject: [PATCH 03/16] Add comment on setting obj dtype --- xarray/compat/array_api_compat.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 1b6c440cc7a..166a23ba2f8 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -42,6 +42,9 @@ def result_type(*arrays_and_dtypes, xp) -> np.dtype: hasattr(t, "dtype") and isinstance(t.dtype, np.dtype) for t in arrays_and_dtypes ] if xp is np or any(is_np_dtype): + # Numpy can only apply type promotion rules on non-builtin & numpy-compatible Python objects. + # So we convert any incompatible objects (e.g. user-defined class instances) stored in the array to numpy Object dtype. + # Then, numpy's type promotion will fall back to Object dtype rather than raising an exception. all_valid_arrays_and_dtypes = [ t if is_np_dtype[i] or t.__class__.__module__ == "builtins" else np.object_ for i, t in enumerate(arrays_and_dtypes) From 8d15d2fddcc13315532a7f98ffd7a3d1e9eb5c20 Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:54:32 +0000 Subject: [PATCH 04/16] Update xarray/compat/array_api_compat.py Co-authored-by: Justus Magin --- xarray/compat/array_api_compat.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 166a23ba2f8..6ba4e6ca060 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -42,14 +42,10 @@ def result_type(*arrays_and_dtypes, xp) -> np.dtype: hasattr(t, "dtype") and isinstance(t.dtype, np.dtype) for t in arrays_and_dtypes ] if xp is np or any(is_np_dtype): - # Numpy can only apply type promotion rules on non-builtin & numpy-compatible Python objects. - # So we convert any incompatible objects (e.g. user-defined class instances) stored in the array to numpy Object dtype. - # Then, numpy's type promotion will fall back to Object dtype rather than raising an exception. - all_valid_arrays_and_dtypes = [ - t if is_np_dtype[i] or t.__class__.__module__ == "builtins" else np.object_ - for i, t in enumerate(arrays_and_dtypes) - ] - return xp.result_type(*all_valid_arrays_and_dtypes) + builtin_types = (bool, int, float, complex, str, bytes, dt.datetime, dt.timedelta) + if any(not is_numpy and not isinstance(t, builtin_types) for is_numpy, t in zip(is_np_dtype, arrays_and_types)): + return np.object_ + return xp.result_type(*arrays_and_dtypes) else: return _future_array_api_result_type(*arrays_and_dtypes, xp=xp) From 5fc7ccc5586af85707d0234ec2d31afdc43f056a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:55:54 +0000 Subject: [PATCH 05/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/compat/array_api_compat.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 6ba4e6ca060..0428d59536b 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -42,8 +42,20 @@ def result_type(*arrays_and_dtypes, xp) -> np.dtype: hasattr(t, "dtype") and isinstance(t.dtype, np.dtype) for t in arrays_and_dtypes ] if xp is np or any(is_np_dtype): - builtin_types = (bool, int, float, complex, str, bytes, dt.datetime, dt.timedelta) - if any(not is_numpy and not isinstance(t, builtin_types) for is_numpy, t in zip(is_np_dtype, arrays_and_types)): + builtin_types = ( + bool, + int, + float, + complex, + str, + bytes, + dt.datetime, + dt.timedelta, + ) + if any( + not is_numpy and not isinstance(t, builtin_types) + for is_numpy, t in zip(is_np_dtype, arrays_and_types) + ): return np.object_ return xp.result_type(*arrays_and_dtypes) else: From 22ab662ffccec312569fc1a1f330fdfbbd1356ac Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 12 Nov 2025 13:22:26 +0100 Subject: [PATCH 06/16] import datetime --- xarray/compat/array_api_compat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 0428d59536b..3b89e27cbf6 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -1,3 +1,5 @@ +import datetime as dt + import numpy as np from xarray.namedarray.pycompat import array_type From 4cea3fa98dbf910e1a665cf5120b23f34011d346 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 12 Nov 2025 13:22:44 +0100 Subject: [PATCH 07/16] typo --- xarray/compat/array_api_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 3b89e27cbf6..cfe6161afad 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -56,7 +56,7 @@ def result_type(*arrays_and_dtypes, xp) -> np.dtype: ) if any( not is_numpy and not isinstance(t, builtin_types) - for is_numpy, t in zip(is_np_dtype, arrays_and_types) + for is_numpy, t in zip(is_np_dtype, arrays_and_dtypes) ): return np.object_ return xp.result_type(*arrays_and_dtypes) From 8f267686e3737de532343c81c3cbab2b5f4d3094 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 12 Nov 2025 13:22:55 +0100 Subject: [PATCH 08/16] use `zip` with `strict=True` --- xarray/compat/array_api_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index cfe6161afad..069b26cf48d 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -56,7 +56,7 @@ def result_type(*arrays_and_dtypes, xp) -> np.dtype: ) if any( not is_numpy and not isinstance(t, builtin_types) - for is_numpy, t in zip(is_np_dtype, arrays_and_dtypes) + for is_numpy, t in zip(is_np_dtype, arrays_and_dtypes, strict=True) ): return np.object_ return xp.result_type(*arrays_and_dtypes) From 46ae1132a39bfa074d234049ecb62693a6da3857 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 12 Nov 2025 14:25:30 +0100 Subject: [PATCH 09/16] refactor the dtype checks into functions --- xarray/compat/array_api_compat.py | 41 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 069b26cf48d..6a4a0948a69 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -4,6 +4,17 @@ from xarray.namedarray.pycompat import array_type +builtin_types = ( + bool, + int, + float, + complex, + str, + bytes, + dt.datetime, + dt.timedelta, +) + def is_weak_scalar_type(t): return isinstance(t, bool | int | float | complex | str | bytes) @@ -39,23 +50,29 @@ def _future_array_api_result_type(*arrays_and_dtypes, xp): return xp.result_type(dtype, *dtypes) +def is_builtin_type(value): + if isinstance(value, type): + return issubclass(value, builtin_types) + else: + return isinstance(value, builtin_types) + + +def is_dtype(value): + if isinstance(value, type): + return issubclass(value, np.generic) + else: + return isinstance(value, np.dtype) + + def result_type(*arrays_and_dtypes, xp) -> np.dtype: is_np_dtype = [ - hasattr(t, "dtype") and isinstance(t.dtype, np.dtype) for t in arrays_and_dtypes + is_dtype(getattr(t, "dtype", t) if not isinstance(t, type) else t) + for t in arrays_and_dtypes ] + if xp is np or any(is_np_dtype): - builtin_types = ( - bool, - int, - float, - complex, - str, - bytes, - dt.datetime, - dt.timedelta, - ) if any( - not is_numpy and not isinstance(t, builtin_types) + not is_numpy and not is_builtin_type(t) for is_numpy, t in zip(is_np_dtype, arrays_and_dtypes, strict=True) ): return np.object_ From 788eedbabeb47c274272153cd3f8f93c5c0332cd Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 12 Nov 2025 14:38:53 +0100 Subject: [PATCH 10/16] return a dtype object instead of the dtype type --- xarray/compat/array_api_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 6a4a0948a69..67507419bfa 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -75,7 +75,7 @@ def result_type(*arrays_and_dtypes, xp) -> np.dtype: not is_numpy and not is_builtin_type(t) for is_numpy, t in zip(is_np_dtype, arrays_and_dtypes, strict=True) ): - return np.object_ + return np.dtype("object") return xp.result_type(*arrays_and_dtypes) else: return _future_array_api_result_type(*arrays_and_dtypes, xp=xp) From e51a8c415fb72beaa860f16c7a5f6ce29d5b0a14 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 11 Feb 2026 20:08:04 +0100 Subject: [PATCH 11/16] revert the changes to `result_type` --- xarray/compat/array_api_compat.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 67507419bfa..43bfe3153d9 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -65,20 +65,15 @@ def is_dtype(value): def result_type(*arrays_and_dtypes, xp) -> np.dtype: - is_np_dtype = [ - is_dtype(getattr(t, "dtype", t) if not isinstance(t, type) else t) - for t in arrays_and_dtypes - ] - - if xp is np or any(is_np_dtype): - if any( - not is_numpy and not is_builtin_type(t) - for is_numpy, t in zip(is_np_dtype, arrays_and_dtypes, strict=True) + try: + if xp is np or any( + isinstance(getattr(t, "dtype", t), np.dtype) for t in arrays_and_dtypes ): - return np.dtype("object") - return xp.result_type(*arrays_and_dtypes) - else: - return _future_array_api_result_type(*arrays_and_dtypes, xp=xp) + return xp.result_type(*arrays_and_dtypes) + else: + return _future_array_api_result_type(*arrays_and_dtypes, xp=xp) + except TypeError: + return np.dtype(object) def get_array_namespace(*values): From 602adc5dc66d1a82386c605db90c6647f9ddf8f5 Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:04:55 +0000 Subject: [PATCH 12/16] Update given TypeError fallback --- xarray/compat/array_api_compat.py | 14 -------------- xarray/core/dtypes.py | 17 ++++++----------- xarray/core/utils.py | 10 +++------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 43bfe3153d9..63a7cd8ac44 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -50,20 +50,6 @@ def _future_array_api_result_type(*arrays_and_dtypes, xp): return xp.result_type(dtype, *dtypes) -def is_builtin_type(value): - if isinstance(value, type): - return issubclass(value, builtin_types) - else: - return isinstance(value, builtin_types) - - -def is_dtype(value): - if isinstance(value, type): - return issubclass(value, np.generic) - else: - return isinstance(value, np.dtype) - - def result_type(*arrays_and_dtypes, xp) -> np.dtype: try: if xp is np or any( diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index 40334d58e68..4f29df2bb70 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -272,17 +272,11 @@ def should_promote_to_object( """ np_result_types = set() for arr_or_dtype in arrays_and_dtypes: - try: - result_type = array_api_compat.result_type( - maybe_promote_to_variable_width(arr_or_dtype), xp=xp - ) - if isinstance(result_type, np.dtype): - np_result_types.add(result_type) - except TypeError: - # passing individual objects to xp.result_type (i.e., what `array_api_compat.result_type` calls) means NEP-18 implementations won't have - # a chance to intercept special values (such as NA) that numpy core cannot handle. - # Thus they are considered as types that don't need promotion i.e., the `arr_or_dtype` that rose the `TypeError` will not contribute to `np_result_types`. - pass + result_type = array_api_compat.result_type( + maybe_promote_to_variable_width(arr_or_dtype), xp=xp + ) + if isinstance(result_type, np.dtype): + np_result_types.add(result_type) if np_result_types: for left, right in PROMOTE_TO_OBJECT: @@ -322,6 +316,7 @@ def result_type( if should_promote_to_object(arrays_and_dtypes, xp): return np.dtype(object) + maybe_promote = functools.partial( maybe_promote_to_variable_width, # let extension arrays handle their own str/bytes diff --git a/xarray/core/utils.py b/xarray/core/utils.py index c6828f0a363..dc49d41a21b 100644 --- a/xarray/core/utils.py +++ b/xarray/core/utils.py @@ -211,13 +211,9 @@ def maybe_coerce_to_str(index, original_coords): """ from xarray.core import dtypes - try: - result_type = dtypes.result_type(*original_coords) - except TypeError: - pass - else: - if result_type.kind in "SU": - index = np.asarray(index, dtype=result_type.type) + result_type = dtypes.result_type(*original_coords) + if result_type.kind in "SU": + index = np.asarray(index, dtype=result_type.type) return index From 30d94f6ddfbc22dcc307c307a525fa21166e4807 Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:53:19 +0200 Subject: [PATCH 13/16] Fixes post-review - Add `maybe_coerce_to_str` test - Remove redundant builtins list - Add typeerror comment --- xarray/compat/array_api_compat.py | 26 ++++------------ xarray/core/dtypes.py | 9 +++++- xarray/tests/test_utils.py | 49 +++++++++++++++++++------------ 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 63a7cd8ac44..0b8fcb2bdd8 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -4,17 +4,6 @@ from xarray.namedarray.pycompat import array_type -builtin_types = ( - bool, - int, - float, - complex, - str, - bytes, - dt.datetime, - dt.timedelta, -) - def is_weak_scalar_type(t): return isinstance(t, bool | int | float | complex | str | bytes) @@ -51,15 +40,12 @@ def _future_array_api_result_type(*arrays_and_dtypes, xp): def result_type(*arrays_and_dtypes, xp) -> np.dtype: - try: - if xp is np or any( - isinstance(getattr(t, "dtype", t), np.dtype) for t in arrays_and_dtypes - ): - return xp.result_type(*arrays_and_dtypes) - else: - return _future_array_api_result_type(*arrays_and_dtypes, xp=xp) - except TypeError: - return np.dtype(object) + if xp is np or any( + isinstance(getattr(t, "dtype", t), np.dtype) for t in arrays_and_dtypes + ): + return xp.result_type(*arrays_and_dtypes) + else: + return _future_array_api_result_type(*arrays_and_dtypes, xp=xp) def get_array_namespace(*values): diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index 1e9a69ea977..ca155c92bf9 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -320,7 +320,14 @@ def result_type( if xp is None: xp = get_array_namespace(arrays_and_dtypes) - if should_promote_to_object(arrays_and_dtypes, xp): + try: + promote_to_object = should_promote_to_object(arrays_and_dtypes, xp) + except TypeError: + # unknown python objects will raise a TypeError in `xp.result_type`; + # We assume the user wants them to be there and therefore promote to object dtype instead of raising. + promote_to_object = True + + if promote_to_object: return np.dtype(object) maybe_promote = functools.partial( diff --git a/xarray/tests/test_utils.py b/xarray/tests/test_utils.py index 90fdcb7d561..1f2d5a8eaac 100644 --- a/xarray/tests/test_utils.py +++ b/xarray/tests/test_utils.py @@ -29,31 +29,42 @@ def new_method(): old_method() -@pytest.mark.parametrize( - ["a", "b", "expected"], - [ - [np.array(["a"]), np.array(["b"]), np.array(["a", "b"])], - [np.array([1], dtype="int64"), np.array([2], dtype="int64"), pd.Index([1, 2])], - ], -) -def test_maybe_coerce_to_str(a, b, expected): - index = pd.Index(a).append(pd.Index(b)) +class TestMaybeCoerceToStr: + @pytest.mark.parametrize( + ["a", "b", "expected"], + [ + [np.array(["a"]), np.array(["b"]), np.array(["a", "b"])], + [np.array([1], dtype="int64"), np.array([2], dtype="int64"), pd.Index([1, 2])], + ], + ) + def test_maybe_coerce_to_str(self, a, b, expected): + index = pd.Index(a).append(pd.Index(b)) + + actual = utils.maybe_coerce_to_str(index, [a, b]) + + assert_array_equal(expected, actual) + assert expected.dtype == actual.dtype + - actual = utils.maybe_coerce_to_str(index, [a, b]) + def test_maybe_coerce_to_str_minimal_str_dtype(self): + a = np.array(["a", "a_long_string"]) + index = pd.Index(["a"]) - assert_array_equal(expected, actual) - assert expected.dtype == actual.dtype + actual = utils.maybe_coerce_to_str(index, [a]) + expected = np.array("a") + assert_array_equal(expected, actual) + assert expected.dtype == actual.dtype -def test_maybe_coerce_to_str_minimal_str_dtype(): - a = np.array(["a", "a_long_string"]) - index = pd.Index(["a"]) + def test_maybe_coerce_to_str_python_obj_dtype(self): + """No change to dtype if the array contains a custom python object.""" + a = np.array([type("Foo", (object,), {"foo": "bar"}), "a_long_string"]) + index = pd.Index(["a"], dtype=object) - actual = utils.maybe_coerce_to_str(index, [a]) - expected = np.array("a") + actual = utils.maybe_coerce_to_str(index, [a]) - assert_array_equal(expected, actual) - assert expected.dtype == actual.dtype + assert_array_equal(index, actual) + assert index.dtype == actual.dtype class TestArrayEquiv: From d3ba3f386c7b5559455e0fae68c9f78fc9124154 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:53:50 +0000 Subject: [PATCH 14/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/compat/array_api_compat.py | 2 -- xarray/tests/test_utils.py | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/xarray/compat/array_api_compat.py b/xarray/compat/array_api_compat.py index 0b8fcb2bdd8..e1e5d5c5bdc 100644 --- a/xarray/compat/array_api_compat.py +++ b/xarray/compat/array_api_compat.py @@ -1,5 +1,3 @@ -import datetime as dt - import numpy as np from xarray.namedarray.pycompat import array_type diff --git a/xarray/tests/test_utils.py b/xarray/tests/test_utils.py index 1f2d5a8eaac..0d42cd8d48c 100644 --- a/xarray/tests/test_utils.py +++ b/xarray/tests/test_utils.py @@ -34,7 +34,11 @@ class TestMaybeCoerceToStr: ["a", "b", "expected"], [ [np.array(["a"]), np.array(["b"]), np.array(["a", "b"])], - [np.array([1], dtype="int64"), np.array([2], dtype="int64"), pd.Index([1, 2])], + [ + np.array([1], dtype="int64"), + np.array([2], dtype="int64"), + pd.Index([1, 2]), + ], ], ) def test_maybe_coerce_to_str(self, a, b, expected): @@ -45,7 +49,6 @@ def test_maybe_coerce_to_str(self, a, b, expected): assert_array_equal(expected, actual) assert expected.dtype == actual.dtype - def test_maybe_coerce_to_str_minimal_str_dtype(self): a = np.array(["a", "a_long_string"]) index = pd.Index(["a"]) From 8feabaea51715f0928d75050dacee02a7dc860ff Mon Sep 17 00:00:00 2001 From: Bryn Pickering <17178478+brynpickering@users.noreply.github.com> Date: Wed, 24 Jun 2026 09:34:58 +0200 Subject: [PATCH 15/16] Fix when typerror is caught --- xarray/core/dtypes.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index ca155c92bf9..516fb3af063 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -321,14 +321,12 @@ def result_type( xp = get_array_namespace(arrays_and_dtypes) try: - promote_to_object = should_promote_to_object(arrays_and_dtypes, xp) + if should_promote_to_object(arrays_and_dtypes, xp): + return np.dtype(object) except TypeError: - # unknown python objects will raise a TypeError in `xp.result_type`; - # We assume the user wants them to be there and therefore promote to object dtype instead of raising. - promote_to_object = True - - if promote_to_object: - return np.dtype(object) + # Unknown python objects will raise a TypeError in `xp.result_type`; + # We pass the decision on its impact on array type to after we've attempted to promote. + pass maybe_promote = functools.partial( maybe_promote_to_variable_width, @@ -337,4 +335,10 @@ def result_type( map(utils.is_allowed_extension_array_dtype, arrays_and_dtypes) ), ) - return array_api_compat.result_type(*map(maybe_promote, arrays_and_dtypes), xp=xp) + try: + result = array_api_compat.result_type(*map(maybe_promote, arrays_and_dtypes), xp=xp) + except TypeError: + # Unknown python objects will raise a TypeError in `xp.result_type`; + # We assume the user wants them to be there and therefore promote to object dtype instead of raising. + return np.dtype(object) + return result From fc583c268264390b66f7cff6a65d2f8ed26625fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 07:35:25 +0000 Subject: [PATCH 16/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/core/dtypes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index 516fb3af063..d8ef4c4c6a8 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -336,7 +336,9 @@ def result_type( ), ) try: - result = array_api_compat.result_type(*map(maybe_promote, arrays_and_dtypes), xp=xp) + result = array_api_compat.result_type( + *map(maybe_promote, arrays_and_dtypes), xp=xp + ) except TypeError: # Unknown python objects will raise a TypeError in `xp.result_type`; # We assume the user wants them to be there and therefore promote to object dtype instead of raising.