Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions py_hamt/encryption_hamt_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
27 changes: 27 additions & 0 deletions py_hamt/zarr_hamt_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]

Expand Down
19 changes: 19 additions & 0 deletions tests/test_with_read_only.py
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading