Skip to content

Commit 2e1e70b

Browse files
committed
Prevent mode='r+' from creating new directories
1 parent a26926c commit 2e1e70b

File tree

2 files changed

+39
-5
lines changed

2 files changed

+39
-5
lines changed

src/zarr/storage/_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ async def make_store_path(
358358

359359
elif isinstance(store_like, Path):
360360
# Create a new LocalStore
361-
store = await LocalStore.open(root=store_like, read_only=_read_only)
361+
store = await LocalStore.open(root=store_like, mode=mode)
362362

363363
elif isinstance(store_like, str):
364364
# Either a FSSpec URI or a local filesystem path

src/zarr/storage/_local.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import shutil
77
from pathlib import Path
8-
from typing import TYPE_CHECKING
8+
from typing import TYPE_CHECKING, Self
99

1010
from zarr.abc.store import (
1111
ByteRequest,
@@ -16,7 +16,7 @@
1616
)
1717
from zarr.core.buffer import Buffer
1818
from zarr.core.buffer.core import default_buffer_prototype
19-
from zarr.core.common import concurrent_map
19+
from zarr.core.common import AccessModeLiteral, concurrent_map
2020

2121
if TYPE_CHECKING:
2222
from collections.abc import AsyncIterator, Iterable
@@ -102,16 +102,50 @@ def __init__(self, root: Path | str, *, read_only: bool = False) -> None:
102102
)
103103
self.root = root
104104

105-
def with_read_only(self, read_only: bool = False) -> LocalStore:
105+
def with_read_only(self, read_only: bool = False) -> Self:
106106
# docstring inherited
107107
return type(self)(
108108
root=self.root,
109109
read_only=read_only,
110110
)
111111

112-
async def _open(self) -> None:
112+
@classmethod
113+
async def open(
114+
cls, root: Path | str, *, read_only: bool = False, mode: AccessModeLiteral | None = None
115+
) -> Self:
116+
"""
117+
Create and open the store.
118+
119+
Parameters
120+
----------
121+
root : str or Path
122+
Directory to use as root of store.
123+
read_only : bool
124+
Whether the store is read-only
125+
mode :
126+
Mode in which to create the store. This only affects opening the store,
127+
and the final read-only state of the store is controlled through the
128+
read_only parameter.
129+
130+
Returns
131+
-------
132+
Store
133+
The opened store instance.
134+
"""
135+
if mode is not None:
136+
read_only_creation = mode in ["r", "r+"]
137+
else:
138+
read_only_creation = read_only
139+
store = cls(root, read_only=read_only_creation)
140+
await store._open()
141+
return store.with_read_only(read_only)
142+
143+
async def _open(self, *, mode: AccessModeLiteral | None = None) -> None:
113144
if not self.read_only:
114145
self.root.mkdir(parents=True, exist_ok=True)
146+
147+
if not self.root.exists():
148+
raise FileNotFoundError(f"{self.root} does not exist")
115149
return await super()._open()
116150

117151
async def clear(self) -> None:

0 commit comments

Comments
 (0)