From c0b303c1accba767d009c1ca937f8e7adf2f8536 Mon Sep 17 00:00:00 2001 From: Cipher Date: Wed, 11 Mar 2026 01:56:42 -0700 Subject: [PATCH] fix: initialize _lock attribute in ZipStore (#3588) ZipStore._lock was initialized in _sync_open() but accessed by methods (get, set, exists, etc.) that can be called before the store is opened. This caused AttributeError: 'ZipStore' object has no attribute '_lock'. Move _lock initialization to __init__ so it is always available after instantiation, regardless of whether the store has been opened yet. Fixes #3588 --- src/zarr/storage/_zip.py | 3 +-- tests/test_store/test_zip.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/zarr/storage/_zip.py b/src/zarr/storage/_zip.py index 72bf9e335a..b051b2cbc8 100644 --- a/src/zarr/storage/_zip.py +++ b/src/zarr/storage/_zip.py @@ -87,13 +87,12 @@ def __init__( self._zmode = mode self.compression = compression self.allowZip64 = allowZip64 + self._lock = threading.RLock() def _sync_open(self) -> None: if self._is_open: raise ValueError("store is already open") - self._lock = threading.RLock() - self._zf = zipfile.ZipFile( self.path, mode=self._zmode, diff --git a/tests/test_store/test_zip.py b/tests/test_store/test_zip.py index 744ee82945..11399fff77 100644 --- a/tests/test_store/test_zip.py +++ b/tests/test_store/test_zip.py @@ -152,3 +152,15 @@ async def test_move(self, tmp_path: Path) -> None: assert destination.exists() assert not origin.exists() assert np.array_equal(array[...], np.arange(10)) + + def test_lock_initialized_before_open(self, store_kwargs: dict[str, Any]) -> None: + """Regression test for https://github.com/zarr-developers/zarr-python/issues/3588. + + ZipStore._lock must be available immediately after __init__, before _open() is called, + so that operations like get/set/exists can use it even on non-opened stores. + """ + import threading + + store = self.store_cls(**store_kwargs) + assert hasattr(store, "_lock"), "ZipStore._lock must be initialized in __init__" + assert isinstance(store._lock, type(threading.RLock())), "_lock must be an RLock"