From 7c38e572b974229c9650cbe7ee8962eed9692d2a Mon Sep 17 00:00:00 2001 From: Faolain Date: Sun, 27 Jul 2025 04:09:45 -0400 Subject: [PATCH] deps: address required missing key in zarr upgrade --- py_hamt/encryption_hamt_store.py | 20 ++++++++++++++++++++ py_hamt/zarr_hamt_store.py | 27 +++++++++++++++++++++++++++ pyproject.toml | 2 +- tests/test_with_read_only.py | 19 +++++++++++++++++++ uv.lock | 8 ++++---- 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 tests/test_with_read_only.py diff --git a/py_hamt/encryption_hamt_store.py b/py_hamt/encryption_hamt_store.py index f13e7c0..a4be5e4 100644 --- a/py_hamt/encryption_hamt_store.py +++ b/py_hamt/encryption_hamt_store.py @@ -180,3 +180,23 @@ async def set(self, key: str, value: zarr.core.buffer.Buffer) -> None: # Encrypt it encrypted_bytes = self._encrypt(raw_bytes) await self.hamt.set(key, encrypted_bytes) + + # we only need to override because we have extra ctor args + def with_read_only(self, read_only: bool = False) -> "SimpleEncryptedZarrHAMTStore": # noqa: D401 + if read_only == self.read_only: + return self + + new_hamt = HAMT( + cas=self.hamt.cas, + root_node_id=self.hamt.root_node_id, + read_only=read_only, + values_are_bytes=self.hamt.values_are_bytes, + max_bucket_size=self.hamt.max_bucket_size, + hash_fn=self.hamt.hash_fn, + ) + return type(self)( + new_hamt, + read_only, + encryption_key=self.encryption_key, + header=self.header, + ) diff --git a/py_hamt/zarr_hamt_store.py b/py_hamt/zarr_hamt_store.py index 8072c07..ef8ba3b 100644 --- a/py_hamt/zarr_hamt_store.py +++ b/py_hamt/zarr_hamt_store.py @@ -83,6 +83,33 @@ def read_only(self) -> bool: """@private""" return self.hamt.read_only + def with_read_only(self, read_only: bool = False) -> "ZarrHAMTStore": # noqa: D401 + """ + Return *this* store or a lightweight clone with the requested + ``read_only`` flag. + + The clone shares the same HAMT *content*: we rebuild a **new** + :class:`~py_hamt.hamt.HAMT` instance that + *points to the same root CID* but is initialised in the desired + mode. This involves **no I/O** – merely instantiating a Python + object – and therefore keeps the helper strictly synchronous, + exactly as expected by Zarr. + """ + # Fast‑path: nothing to do + if read_only == self.read_only: + return self + + # Re‑use all immutable HAMT knobs so that behaviour is identical + new_hamt = HAMT( + cas=self.hamt.cas, + root_node_id=self.hamt.root_node_id, + read_only=read_only, + values_are_bytes=self.hamt.values_are_bytes, + max_bucket_size=self.hamt.max_bucket_size, + hash_fn=self.hamt.hash_fn, + ) + return type(self)(new_hamt, read_only=read_only) + def __eq__(self, other: object) -> bool: """@private""" if not isinstance(other, ZarrHAMTStore): diff --git a/pyproject.toml b/pyproject.toml index 963943f..5e72353 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "dag-cbor>=0.3.3", "msgspec>=0.18.6", "multiformats[full]>=0.3.1.post4", - "zarr>=3.0.8", + "zarr==3.0.9", "pycryptodome>=3.21.0", ] diff --git a/tests/test_with_read_only.py b/tests/test_with_read_only.py new file mode 100644 index 0000000..cfc5409 --- /dev/null +++ b/tests/test_with_read_only.py @@ -0,0 +1,19 @@ +import pytest + +from py_hamt import HAMT, InMemoryCAS, ZarrHAMTStore + + +@pytest.mark.asyncio +async def test_with_read_only_roundtrip(): + cas = InMemoryCAS() + hamt_rw = await HAMT.build(cas=cas, values_are_bytes=True) + store_rw = ZarrHAMTStore(hamt_rw, read_only=False) + + # clone → RO + store_ro = store_rw.with_read_only(True) + assert store_ro.read_only is True + assert store_ro is not store_rw + # clone back → RW + store_rw2 = store_ro.with_read_only(False) + assert store_rw2.read_only is False + assert store_rw2.hamt.root_node_id == store_rw.hamt.root_node_id diff --git a/uv.lock b/uv.lock index 3428102..1ef6392 100644 --- a/uv.lock +++ b/uv.lock @@ -1542,7 +1542,7 @@ requires-dist = [ { name = "msgspec", specifier = ">=0.18.6" }, { name = "multiformats", extras = ["full"], specifier = ">=0.3.1.post4" }, { name = "pycryptodome", specifier = ">=3.21.0" }, - { name = "zarr", specifier = ">=3.0.8" }, + { name = "zarr", specifier = "==3.0.9" }, ] [package.metadata.requires-dev] @@ -2204,7 +2204,7 @@ wheels = [ [[package]] name = "zarr" -version = "3.0.8" +version = "3.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "donfig" }, @@ -2213,9 +2213,9 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/60/9652fd0536fbaca8d08cbc1a5572c52e0ce01773297df75da8bb47e45907/zarr-3.0.8.tar.gz", hash = "sha256:88505d095af899a88ae8ac4db02f4650ef0801d2ff6f65b6d1f0a45dcf760a6d", size = 256825, upload-time = "2025-05-19T14:19:00.123Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/5c/8f7875034629ce58adb7724acf64b06fc4b933e30978faf1b8d72ba28267/zarr-3.0.9.tar.gz", hash = "sha256:7635084efec55511d2940975528c42b8885634fb09e7ab75591a980122950d1e", size = 263587, upload-time = "2025-07-01T08:31:12.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/3b/e20bdf84088c11f2c396d034506cbffadd53e024111c1aa4585c2aba1523/zarr-3.0.8-py3-none-any.whl", hash = "sha256:7f81e7aec086437d98882aa432209107114bd7f3a9f4958b2af9c6b5928a70a7", size = 205364, upload-time = "2025-05-19T14:18:58.789Z" }, + { url = "https://files.pythonhosted.org/packages/54/69/9d703fee22236dc8c610eb6d728f102340fb8a1dfb4ae649564d77c6fb79/zarr-3.0.9-py3-none-any.whl", hash = "sha256:90775a238a56f98b79d0a9853a04b5ba6236f643c7c8560740583126a409b529", size = 209583, upload-time = "2025-07-01T08:31:11.143Z" }, ] [[package]]