From 66034cbdacbac473aced9fbcb0f9d89b3c9118ca Mon Sep 17 00:00:00 2001 From: Gabriel Demetrios Lafis <33261275+galafis@users.noreply.github.com> Date: Sat, 24 Oct 2026 12:49:11 -0300 Subject: [PATCH 1/3] Handle Windows directory permission errors during engine detection Co-authored-by: Codex --- doc/whats-new.rst | 5 +++++ xarray/core/utils.py | 6 ++++++ xarray/tests/backends/test_plugins.py | 18 ++++++++++++++++++ xarray/tests/test_utils.py | 26 ++++++++++++++++++++++++++ 4 files changed, 55 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 44545597c12..2a10411074a 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -174,6 +174,11 @@ Deprecations Bug Fixes ~~~~~~~~~ +- :py:func:`open_dataset` can detect local Zarr directories on Windows without + raising ``PermissionError`` while checking file-based backends. Permission + errors for ordinary files still propagate (:issue:`11799`). + By `Gabriel Demetrios Lafis `_. + - :py:meth:`Dataset.query` now resolves coordinates in datasets with no data variables (:issue:`6449`). By `Liang, Jingyi `_. diff --git a/xarray/core/utils.py b/xarray/core/utils.py index dd11dd9b0e4..625f06907b8 100644 --- a/xarray/core/utils.py +++ b/xarray/core/utils.py @@ -788,6 +788,12 @@ def try_read_magic_number_from_path(pathlike, count=8) -> bytes | None: return read_magic_number_from_file(f, count) except (FileNotFoundError, IsADirectoryError, TypeError): pass + except PermissionError: + # Windows reports PermissionError rather than IsADirectoryError + # when opening a directory. Let other backends inspect it, but + # preserve permission errors for ordinary files. + if not os.path.isdir(path): + raise return None diff --git a/xarray/tests/backends/test_plugins.py b/xarray/tests/backends/test_plugins.py index 8cdbc44300c..35bdb927f3c 100644 --- a/xarray/tests/backends/test_plugins.py +++ b/xarray/tests/backends/test_plugins.py @@ -5,6 +5,7 @@ import sys from importlib.metadata import EntryPoint, EntryPoints from itertools import starmap +from pathlib import Path from unittest import mock import pytest @@ -26,6 +27,23 @@ importlib_metadata_mock = "importlib.metadata" +@requires_netCDF4 +@requires_zarr +@pytest.mark.parametrize("zarr_format", [2, 3]) +@pytest.mark.parametrize("path_as_string", [False, True]) +def test_guess_engine_local_zarr_directory( + tmp_path: Path, zarr_format: int, path_as_string: bool +) -> None: + expected = xr.Dataset({"temperature": ("time", [1.0, 2.0, 3.0])}) + path = tmp_path / "example.zarr" + expected.to_zarr(path, zarr_format=zarr_format, consolidated=False) + store = str(path) if path_as_string else path + + assert plugins.guess_engine(store) == "zarr" + with xr.open_dataset(store, consolidated=False) as actual: + xr.testing.assert_identical(actual, expected) + + class DummyBackendEntrypointArgs(common.BackendEntrypoint): def open_dataset(filename_or_obj, *args): # type: ignore[override] pass diff --git a/xarray/tests/test_utils.py b/xarray/tests/test_utils.py index 226348d8d4d..5b08f277f8f 100644 --- a/xarray/tests/test_utils.py +++ b/xarray/tests/test_utils.py @@ -448,6 +448,32 @@ def test_try_read_magic_number_from_path_missing(tmp_path: Path) -> None: assert try_read_magic_number_from_path(tmp_path / "missing.nc") is None +def test_try_read_magic_number_from_directory(tmp_path: Path) -> None: + assert try_read_magic_number_from_path(tmp_path) is None + assert try_read_magic_number_from_path(str(tmp_path)) is None + + +@pytest.mark.parametrize("directory", [False, True]) +def test_try_read_magic_number_permission_error( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, directory: bool +) -> None: + path = tmp_path / "store" + if directory: + path.mkdir() + else: + path.write_bytes(b"CDF") + + def denied_open(*args: Any, **kwargs: Any) -> IO[Any]: + raise PermissionError("Access denied") + + monkeypatch.setattr(utils, "open", denied_open, raising=False) + if directory: + assert try_read_magic_number_from_path(path) is None + else: + with pytest.raises(PermissionError, match="Access denied"): + try_read_magic_number_from_path(path) + + def test_try_read_magic_number_from_path_unbuffered( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: From 3fd48bec3c04b3cd2a409b59f67a858771922f36 Mon Sep 17 00:00:00 2001 From: Gabriel Demetrios Lafis <33261275+galafis@users.noreply.github.com> Date: Sat, 24 Oct 2026 12:58:17 -0300 Subject: [PATCH 2/3] Reference PR 11802 in the release note Co-authored-by: Codex --- doc/whats-new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 2a10411074a..caa1a111bb2 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -176,7 +176,7 @@ Bug Fixes ~~~~~~~~~ - :py:func:`open_dataset` can detect local Zarr directories on Windows without raising ``PermissionError`` while checking file-based backends. Permission - errors for ordinary files still propagate (:issue:`11799`). + errors for ordinary files still propagate (:issue:`11799`, :pull:`11802`). By `Gabriel Demetrios Lafis `_. - :py:meth:`Dataset.query` now resolves coordinates in datasets with no data From c8495566c7a4067dd6a692af8aa281970f393311 Mon Sep 17 00:00:00 2001 From: Gabriel Demetrios Lafis <33261275+galafis@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:01:56 -0300 Subject: [PATCH 3/3] Annotate the edited magic-number helper parameters Co-authored-by: Codex --- xarray/core/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/utils.py b/xarray/core/utils.py index 625f06907b8..6a901efae17 100644 --- a/xarray/core/utils.py +++ b/xarray/core/utils.py @@ -777,7 +777,7 @@ def read_magic_number_from_file(filename_or_obj, count=8) -> bytes: return magic_number -def try_read_magic_number_from_path(pathlike, count=8) -> bytes | None: +def try_read_magic_number_from_path(pathlike: Any, count: int = 8) -> bytes | None: if isinstance(pathlike, str) or hasattr(pathlike, "__fspath__"): path = os.fspath(pathlike) try: