@@ -51,6 +51,8 @@ class ZarrHAMTStore(zarr.abc.store.Store):
5151 ```
5252 """
5353
54+ _forced_read_only : bool | None = None # sentinel for wrapper clones
55+
5456 def __init__ (self , hamt : HAMT , read_only : bool = False ) -> None :
5557 """
5658 ### `hamt` and `read_only`
@@ -79,10 +81,36 @@ def __init__(self, hamt: HAMT, read_only: bool = False) -> None:
7981 """@private"""
8082
8183 @property
82- def read_only (self ) -> bool :
83- """@private"""
84+ def read_only (self ) -> bool : # type: ignore[override]
85+ if self ._forced_read_only is not None : # instance attr overrides
86+ return self ._forced_read_only
8487 return self .hamt .read_only
8588
89+ def with_read_only (self , read_only : bool = False ) -> "ZarrHAMTStore" :
90+ """
91+ Return this store (if the flag already matches) or a *shallow*
92+ clone that presents the requested read‑only status.
93+
94+ The clone **shares** the same :class:`~py_hamt.hamt.HAMT`
95+ instance; no flushing, network traffic or async work is done.
96+ """
97+ # Fast path
98+ if read_only == self .read_only :
99+ return self # Same mode, return same instance
100+
101+ # Create new instance with different read_only flag
102+ # Creates a *bare* instance without running its __init__
103+ clone = type (self ).__new__ (type (self ))
104+
105+ # Copy attributes that matter
106+ clone .hamt = self .hamt # Share the HAMT
107+ clone ._forced_read_only = read_only
108+ clone .metadata_read_cache = self .metadata_read_cache .copy ()
109+
110+ # Re‑initialise the zarr base class so that Zarr sees the flag
111+ zarr .abc .store .Store .__init__ (clone , read_only = read_only )
112+ return clone
113+
86114 def __eq__ (self , other : object ) -> bool :
87115 """@private"""
88116 if not isinstance (other , ZarrHAMTStore ):
@@ -145,6 +173,9 @@ def supports_partial_writes(self) -> bool:
145173
146174 async def set (self , key : str , value : zarr .core .buffer .Buffer ) -> None :
147175 """@private"""
176+ if self .read_only :
177+ raise Exception ("Cannot write to a read-only store." )
178+
148179 if key in self .metadata_read_cache :
149180 self .metadata_read_cache [key ] = value .to_bytes ()
150181 await self .hamt .set (key , value .to_bytes ())
@@ -167,6 +198,8 @@ def supports_deletes(self) -> bool:
167198
168199 async def delete (self , key : str ) -> None :
169200 """@private"""
201+ if self .read_only :
202+ raise Exception ("Cannot write to a read-only store." )
170203 try :
171204 await self .hamt .delete (key )
172205 # In practice these lines never seem to be needed, creating and appending data are the only operations most zarrs actually undergo
0 commit comments