Skip to content

Commit dd161df

Browse files
authored
Fix overwrite modes (#3062)
* Fix overwrite modes * Add many more tests that data doesn't disappear * Add bugfix entry. * Fix function name
1 parent a941224 commit dd161df

File tree

4 files changed

+76
-10
lines changed

4 files changed

+76
-10
lines changed

changes/3062.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Using various functions to open data with ``mode='a'`` no longer deletes existing data in the store.

src/zarr/api/asynchronous.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888

8989
_READ_MODES: tuple[AccessModeLiteral, ...] = ("r", "r+", "a")
9090
_CREATE_MODES: tuple[AccessModeLiteral, ...] = ("a", "w", "w-")
91-
_OVERWRITE_MODES: tuple[AccessModeLiteral, ...] = ("a", "r+", "w")
91+
_OVERWRITE_MODES: tuple[AccessModeLiteral, ...] = ("w",)
9292

9393

9494
def _infer_overwrite(mode: AccessModeLiteral) -> bool:
@@ -817,7 +817,6 @@ async def open_group(
817817
warnings.warn("chunk_store is not yet implemented", RuntimeWarning, stacklevel=2)
818818

819819
store_path = await make_store_path(store, mode=mode, storage_options=storage_options, path=path)
820-
821820
if attributes is None:
822821
attributes = {}
823822

src/zarr/api/synchronous.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,7 @@ def create_array(
858858
Ignored otherwise.
859859
overwrite : bool, default False
860860
Whether to overwrite an array with the same name in the store, if one exists.
861+
If `True`, all existing paths in the store will be deleted.
861862
config : ArrayConfigLike, optional
862863
Runtime configuration for the array.
863864
write_data : bool

tests/test_api.py

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
from typing import TYPE_CHECKING
55

66
import zarr.codecs
7+
import zarr.storage
78

89
if TYPE_CHECKING:
910
import pathlib
1011

1112
from zarr.abc.store import Store
1213
from zarr.core.common import JSON, MemoryOrder, ZarrFormat
1314

15+
import contextlib
1416
import warnings
1517
from typing import Literal
1618

@@ -27,9 +29,9 @@
2729
create,
2830
create_array,
2931
create_group,
32+
from_array,
3033
group,
3134
load,
32-
open,
3335
open_group,
3436
save,
3537
save_array,
@@ -41,6 +43,10 @@
4143
from zarr.storage._utils import normalize_path
4244
from zarr.testing.utils import gpu_test
4345

46+
if TYPE_CHECKING:
47+
from collections.abc import Callable
48+
from pathlib import Path
49+
4450

4551
def test_create(memory_store: Store) -> None:
4652
store = memory_store
@@ -135,28 +141,28 @@ async def test_open_array(memory_store: MemoryStore, zarr_format: ZarrFormat) ->
135141
store = memory_store
136142

137143
# open array, create if doesn't exist
138-
z = open(store=store, shape=100, zarr_format=zarr_format)
144+
z = zarr.api.synchronous.open(store=store, shape=100, zarr_format=zarr_format)
139145
assert isinstance(z, Array)
140146
assert z.shape == (100,)
141147

142148
# open array, overwrite
143149
# store._store_dict = {}
144150
store = MemoryStore()
145-
z = open(store=store, shape=200, zarr_format=zarr_format)
151+
z = zarr.api.synchronous.open(store=store, shape=200, zarr_format=zarr_format)
146152
assert isinstance(z, Array)
147153
assert z.shape == (200,)
148154

149155
# open array, read-only
150156
store_cls = type(store)
151157
ro_store = await store_cls.open(store_dict=store._store_dict, read_only=True)
152-
z = open(store=ro_store, mode="r")
158+
z = zarr.api.synchronous.open(store=ro_store, mode="r")
153159
assert isinstance(z, Array)
154160
assert z.shape == (200,)
155161
assert z.read_only
156162

157163
# path not found
158164
with pytest.raises(FileNotFoundError):
159-
open(store="doesnotexist", mode="r", zarr_format=zarr_format)
165+
zarr.api.synchronous.open(store="doesnotexist", mode="r", zarr_format=zarr_format)
160166

161167

162168
@pytest.mark.parametrize("store", ["memory", "local", "zip"], indirect=True)
@@ -233,12 +239,12 @@ def test_save(store: Store, n_args: int, n_kwargs: int) -> None:
233239
save(store)
234240
elif n_args == 1 and n_kwargs == 0:
235241
save(store, *args)
236-
array = open(store)
242+
array = zarr.api.synchronous.open(store)
237243
assert isinstance(array, Array)
238244
assert_array_equal(array[:], data)
239245
else:
240246
save(store, *args, **kwargs) # type: ignore [arg-type]
241-
group = open(store)
247+
group = zarr.api.synchronous.open(store)
242248
assert isinstance(group, Group)
243249
for array in group.array_values():
244250
assert_array_equal(array[:], data)
@@ -1077,7 +1083,7 @@ def test_tree() -> None:
10771083
def test_open_positional_args_deprecated() -> None:
10781084
store = MemoryStore()
10791085
with pytest.warns(FutureWarning, match="pass"):
1080-
open(store, "w", shape=(1,))
1086+
zarr.api.synchronous.open(store, "w", shape=(1,))
10811087

10821088

10831089
def test_save_array_positional_args_deprecated() -> None:
@@ -1236,3 +1242,62 @@ def test_v2_with_v3_compressor() -> None:
12361242
zarr.create(
12371243
store={}, shape=(1), dtype="uint8", zarr_format=2, compressor=zarr.codecs.BloscCodec()
12381244
)
1245+
1246+
1247+
def add_empty_file(path: Path) -> Path:
1248+
fpath = path / "a.txt"
1249+
fpath.touch()
1250+
return fpath
1251+
1252+
1253+
@pytest.mark.parametrize("create_function", [create_array, from_array])
1254+
@pytest.mark.parametrize("overwrite", [True, False])
1255+
def test_no_overwrite_array(tmp_path: Path, create_function: Callable, overwrite: bool) -> None: # type:ignore[type-arg]
1256+
store = zarr.storage.LocalStore(tmp_path)
1257+
existing_fpath = add_empty_file(tmp_path)
1258+
1259+
assert existing_fpath.exists()
1260+
create_function(store=store, data=np.ones(shape=(1,)), overwrite=overwrite)
1261+
if overwrite:
1262+
assert not existing_fpath.exists()
1263+
else:
1264+
assert existing_fpath.exists()
1265+
1266+
1267+
@pytest.mark.parametrize("create_function", [create_group, group])
1268+
@pytest.mark.parametrize("overwrite", [True, False])
1269+
def test_no_overwrite_group(tmp_path: Path, create_function: Callable, overwrite: bool) -> None: # type:ignore[type-arg]
1270+
store = zarr.storage.LocalStore(tmp_path)
1271+
existing_fpath = add_empty_file(tmp_path)
1272+
1273+
assert existing_fpath.exists()
1274+
create_function(store=store, overwrite=overwrite)
1275+
if overwrite:
1276+
assert not existing_fpath.exists()
1277+
else:
1278+
assert existing_fpath.exists()
1279+
1280+
1281+
@pytest.mark.parametrize("open_func", [zarr.open, open_group])
1282+
@pytest.mark.parametrize("mode", ["r", "r+", "a", "w", "w-"])
1283+
def test_no_overwrite_open(tmp_path: Path, open_func: Callable, mode: str) -> None: # type:ignore[type-arg]
1284+
store = zarr.storage.LocalStore(tmp_path)
1285+
existing_fpath = add_empty_file(tmp_path)
1286+
1287+
assert existing_fpath.exists()
1288+
with contextlib.suppress(FileExistsError, FileNotFoundError):
1289+
open_func(store=store, mode=mode)
1290+
if mode == "w":
1291+
assert not existing_fpath.exists()
1292+
else:
1293+
assert existing_fpath.exists()
1294+
1295+
1296+
def test_no_overwrite_load(tmp_path: Path) -> None:
1297+
store = zarr.storage.LocalStore(tmp_path)
1298+
existing_fpath = add_empty_file(tmp_path)
1299+
1300+
assert existing_fpath.exists()
1301+
with contextlib.suppress(NotImplementedError):
1302+
zarr.load(store)
1303+
assert existing_fpath.exists()

0 commit comments

Comments
 (0)