Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5845809
Coerce unknown types to O dtype
brynpickering May 20, 2025
ef3df17
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 20, 2025
b82fc90
Merge branch 'main' into fix-obj-dtype-infer
kmuehlbauer May 23, 2025
1756bc4
Add comment on setting obj dtype
brynpickering May 23, 2025
8f0e197
Merge branch 'main' into fix-obj-dtype-infer
brynpickering Jun 11, 2025
5c3acf1
Merge branch 'main' into fix-obj-dtype-infer
brynpickering Sep 17, 2025
8d15d2f
Update xarray/compat/array_api_compat.py
brynpickering Nov 5, 2025
5fc7ccc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 5, 2025
0afd465
Merge branch 'main' into fix-obj-dtype-infer
brynpickering Nov 12, 2025
22ab662
import datetime
keewis Nov 12, 2025
4cea3fa
typo
keewis Nov 12, 2025
8f26768
use `zip` with `strict=True`
keewis Nov 12, 2025
46ae113
refactor the dtype checks into functions
keewis Nov 12, 2025
788eedb
return a dtype object instead of the dtype type
keewis Nov 12, 2025
6512e09
Merge branch 'main' into fix-obj-dtype-infer
jsignell Feb 10, 2026
e51a8c4
revert the changes to `result_type`
keewis Feb 11, 2026
602adc5
Update given TypeError fallback
brynpickering Feb 22, 2026
78751f8
Merge branch 'main' into fix-obj-dtype-infer
brynpickering Mar 30, 2026
0eb085d
Merge branch 'main' into fix-obj-dtype-infer
brynpickering May 1, 2026
791bf98
Merge branch 'main' into fix-obj-dtype-infer
keewis Jun 4, 2026
46b5670
Merge branch 'main' into fix-obj-dtype-infer
brynpickering Jun 19, 2026
30d94f6
Fixes post-review
brynpickering Jun 23, 2026
d3ba3f3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 23, 2026
b4c5ff8
Merge branch 'main' into fix-obj-dtype-infer
brynpickering Jun 23, 2026
8feabae
Fix when typerror is caught
brynpickering Jun 24, 2026
fc583c2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions xarray/core/dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,17 +278,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:
Expand Down Expand Up @@ -326,13 +320,27 @@ def result_type(
if xp is None:
xp = get_array_namespace(arrays_and_dtypes)

if should_promote_to_object(arrays_and_dtypes, xp):
return np.dtype(object)
try:
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 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,
# let extension arrays handle their own str/bytes
should_return_str_or_bytes=any(
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
2 changes: 1 addition & 1 deletion xarray/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def maybe_coerce_to_str(index, original_coords):

try:
result_type = dtypes.result_type(*original_coords)
except (TypeError, ValueError):
except ValueError:
pass
else:
if result_type.kind in "SU":
Expand Down
4 changes: 4 additions & 0 deletions xarray/tests/test_dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class DummyArrayAPINamespace:
([np.dtype("<U2"), str], np.dtype("U")),
([np.dtype("S3"), np.bytes_], np.dtype("S")),
([np.dtype("S10"), bytes], np.dtype("S")),
([type("Foo", (object,), {"foo": "bar"})()], np.object_),
([np.float32, type("Foo", (object,), {"foo": "bar"})()], np.object_),
([np.str_, type("Foo", (object,), {"foo": "bar"})()], np.object_),
([np.bytes_, type("Foo", (object,), {"foo": "bar"})()], np.object_),
],
)
def test_result_type(args, expected) -> None:
Expand Down
64 changes: 39 additions & 25 deletions xarray/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,45 @@ 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))

actual = utils.maybe_coerce_to_str(index, [a, b])

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"])

actual = utils.maybe_coerce_to_str(index, [a])
expected = np.array("a")

assert_array_equal(expected, actual)
assert expected.dtype == actual.dtype
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

def test_maybe_coerce_to_str_minimal_str_dtype(self):
a = np.array(["a", "a_long_string"])
index = pd.Index(["a"])

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_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])

assert_array_equal(index, actual)
assert index.dtype == actual.dtype


class TestArrayEquiv:
Expand Down
Loading