Skip to content

Commit 68ae087

Browse files
authored
Merge branch 'main' into async-dtreec
2 parents 8bc1a74 + 2742e22 commit 68ae087

8 files changed

Lines changed: 76 additions & 12 deletions

File tree

.github/workflows/benchmarks-last-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
3535
- name: "Get Previous tag"
3636
id: previoustag
37-
uses: "WyriHaximus/github-action-get-previous-tag@v1"
37+
uses: "WyriHaximus/github-action-get-previous-tag@v2"
3838
# with:
3939
# fallback: 1.0.0 # Optional fallback tag to use when no tag can be found
4040

doc/whats-new.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ Bug Fixes
5353
- Fix :py:meth:`Dataset.sortby` and :py:meth:`DataArray.sortby` placing NaN values
5454
at the beginning instead of the end when using ``ascending=False`` (:issue:`7358`).
5555
By `Kristian Kollsgård <https://github.com/kkollsga>`_.
56+
- Raise :py:class:`FileNotFoundError` instead of a confusing ``ValueError`` when
57+
:py:func:`open_dataset` is called with a non-existent local file path
58+
(:issue:`10896`).
59+
By `Kristian Kollsgård <https://github.com/kkollsga>`_.
5660

5761
Documentation
5862
~~~~~~~~~~~~~

pixi.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[workspace]
22
preview = ["pixi-build"]
33
channels = ["conda-forge", "nodefaults"]
4-
platforms = ["win-64", "linux-64", "osx-arm64"]
4+
platforms = ["win-64", "linux-64", "osx-arm64", "osx-64"]
55

66
[package]
77
name = "xarray"

properties/test_index_manipulation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,13 @@ def assert_invariants(self):
269269
DatasetTest = DatasetStateMachine.TestCase
270270

271271

272+
@pytest.mark.skip(reason="failure detected by hypothesis")
273+
def test_unstack_string():
274+
ds = xr.Dataset()
275+
ds["0"] = np.array(["", "0", "\x000"], dtype="<U2")
276+
ds.stack({"1": ["0"]}).unstack()
277+
278+
272279
@pytest.mark.skip(reason="failure detected by hypothesis")
273280
def test_unstack_object():
274281
ds = xr.Dataset()

xarray/backends/plugins.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
import functools
44
import inspect
55
import itertools
6+
import os
67
import warnings
78
from collections.abc import Callable
89
from importlib.metadata import entry_points
910
from typing import TYPE_CHECKING, Any
1011

1112
from xarray.backends.common import BACKEND_ENTRYPOINTS, BackendEntrypoint
1213
from xarray.core.options import OPTIONS
13-
from xarray.core.utils import module_available
14+
from xarray.core.utils import is_remote_uri, module_available
1415

1516
if TYPE_CHECKING:
16-
import os
1717
from importlib.metadata import EntryPoint, EntryPoints
1818

1919
from xarray.backends.common import AbstractDataStore
@@ -209,6 +209,11 @@ def guess_engine(
209209
"https://docs.xarray.dev/en/stable/getting-started-guide/installing.html"
210210
)
211211

212+
if isinstance(store_spec, str | os.PathLike):
213+
store_spec_str = str(store_spec)
214+
if not is_remote_uri(store_spec_str) and not os.path.exists(store_spec_str):
215+
raise FileNotFoundError(f"No such file: '{store_spec_str}'")
216+
212217
raise ValueError(error_msg)
213218

214219

xarray/core/dataset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5205,7 +5205,7 @@ def _stack_once(
52055205
vdims = list(var.dims) + add_dims
52065206
shape = [self.sizes[d] for d in vdims]
52075207
exp_var = var.set_dims(vdims, shape)
5208-
stacked_var = exp_var.stack(**{new_dim: dims})
5208+
stacked_var = exp_var.stack({new_dim: dims})
52095209
new_variables[name] = stacked_var
52105210
stacked_var_names.append(name)
52115211
else:

xarray/core/indexes.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,13 @@ def safe_cast_to_index(array: Any) -> pd.Index:
536536
)
537537
kwargs["dtype"] = "float64"
538538

539-
index = pd.Index(to_numpy(array), **kwargs)
539+
values = to_numpy(array)
540+
try:
541+
index = pd.Index(values, **kwargs)
542+
except UnicodeEncodeError:
543+
# coerce to object if pandas fails to coerce to string
544+
kwargs["dtype"] = "object"
545+
index = pd.Index(values, **kwargs)
540546

541547
return _maybe_cast_to_cftimeindex(index)
542548

xarray/tests/test_plugins.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,24 +188,66 @@ def test_build_engines_sorted() -> None:
188188
"xarray.backends.plugins.list_engines",
189189
mock.MagicMock(return_value={"dummy": DummyBackendEntrypointArgs()}),
190190
)
191-
def test_no_matching_engine_found() -> None:
192-
with pytest.raises(ValueError, match=r"did not find a match in any"):
191+
def test_no_matching_engine_found(tmp_path) -> None:
192+
# Non-existent local file raises FileNotFoundError
193+
with pytest.raises(FileNotFoundError, match=r"No such file"):
193194
plugins.guess_engine("not-valid")
194195

196+
# Existing file with unrecognized extension raises ValueError
197+
existing_file = tmp_path / "test.unknown"
198+
existing_file.write_bytes(b"")
199+
with pytest.raises(ValueError, match=r"did not find a match in any"):
200+
plugins.guess_engine(str(existing_file))
201+
202+
# Existing file with recognized magic number raises ValueError
203+
nc_file = tmp_path / "foo.nc"
204+
nc_file.write_bytes(b"CDF\x01\x00\x00\x00\x00")
195205
with pytest.raises(ValueError, match=r"found the following matches with the input"):
196-
plugins.guess_engine("foo.nc")
206+
plugins.guess_engine(str(nc_file))
197207

198208

199209
@mock.patch(
200210
"xarray.backends.plugins.list_engines",
201211
mock.MagicMock(return_value={}),
202212
)
203-
def test_engines_not_installed() -> None:
204-
with pytest.raises(ValueError, match=r"xarray is unable to open"):
213+
def test_engines_not_installed(tmp_path) -> None:
214+
# Non-existent local file raises FileNotFoundError
215+
with pytest.raises(FileNotFoundError, match=r"No such file"):
205216
plugins.guess_engine("not-valid")
206217

218+
# Existing file with no matching engine raises ValueError
219+
existing_file = tmp_path / "test.unknown"
220+
existing_file.write_bytes(b"")
221+
with pytest.raises(ValueError, match=r"xarray is unable to open"):
222+
plugins.guess_engine(str(existing_file))
223+
224+
# Existing file with recognized magic number raises ValueError
225+
nc_file = tmp_path / "foo.nc"
226+
nc_file.write_bytes(b"CDF\x01\x00\x00\x00\x00")
207227
with pytest.raises(ValueError, match=r"found the following matches with the input"):
208-
plugins.guess_engine("foo.nc")
228+
plugins.guess_engine(str(nc_file))
229+
230+
231+
@mock.patch(
232+
"xarray.backends.plugins.list_engines",
233+
mock.MagicMock(return_value={"dummy": DummyBackendEntrypointArgs()}),
234+
)
235+
def test_guess_engine_file_not_found() -> None:
236+
# Non-existent local file path (string)
237+
with pytest.raises(
238+
FileNotFoundError, match=r"No such file: '/nonexistent/path.h5'"
239+
):
240+
plugins.guess_engine("/nonexistent/path.h5")
241+
242+
# Non-existent local file path (PathLike)
243+
from pathlib import Path
244+
245+
with pytest.raises(FileNotFoundError, match=r"No such file"):
246+
plugins.guess_engine(Path("/nonexistent/path.h5"))
247+
248+
# Remote URIs should not raise FileNotFoundError (raises ValueError instead)
249+
with pytest.raises(ValueError):
250+
plugins.guess_engine("https://example.com/missing.h5")
209251

210252

211253
@pytest.mark.parametrize("engine", common.BACKEND_ENTRYPOINTS.keys())

0 commit comments

Comments
 (0)