|
7 | 7 |
|
8 | 8 | from zarr.abc.store import ByteRequest, Store
|
9 | 9 | from zarr.core.buffer import Buffer, default_buffer_prototype
|
10 |
| -from zarr.core.common import ZARR_JSON, ZARRAY_JSON, ZGROUP_JSON, AccessModeLiteral, ZarrFormat |
| 10 | +from zarr.core.common import ( |
| 11 | + ANY_ACCESS_MODE, |
| 12 | + ZARR_JSON, |
| 13 | + ZARRAY_JSON, |
| 14 | + ZGROUP_JSON, |
| 15 | + AccessModeLiteral, |
| 16 | + ZarrFormat, |
| 17 | +) |
11 | 18 | from zarr.errors import ContainsArrayAndGroupError, ContainsArrayError, ContainsGroupError
|
12 | 19 | from zarr.storage._local import LocalStore
|
13 | 20 | from zarr.storage._memory import MemoryStore
|
@@ -54,59 +61,82 @@ def __init__(self, store: Store, path: str = "") -> None:
|
54 | 61 | def read_only(self) -> bool:
|
55 | 62 | return self.store.read_only
|
56 | 63 |
|
| 64 | + @classmethod |
| 65 | + async def _create_open_instance(cls, store: Store, path: str) -> Self: |
| 66 | + """Helper to create and return a StorePath instance.""" |
| 67 | + await store._ensure_open() |
| 68 | + return cls(store, path) |
| 69 | + |
57 | 70 | @classmethod
|
58 | 71 | async def open(cls, store: Store, path: str, mode: AccessModeLiteral | None = None) -> Self:
|
59 | 72 | """
|
60 | 73 | Open StorePath based on the provided mode.
|
61 | 74 |
|
62 |
| - * If the mode is 'w-' and the StorePath contains keys, raise a FileExistsError. |
63 |
| - * If the mode is 'w', delete all keys nested within the StorePath |
64 |
| - * If the mode is 'a', 'r', or 'r+', do nothing |
| 75 | + * If the mode is None, return an opened version of the store with no changes. |
| 76 | + * If the mode is 'r+', 'w-', 'w', or 'a' and the store is read-only, raise a ValueError. |
| 77 | + * If the mode is 'r' and the store is not read-only, return a copy of the store with read_only set to True. |
| 78 | + * If the mode is 'w-' and the store is not read-only and the StorePath contains keys, raise a FileExistsError. |
| 79 | + * If the mode is 'w' and the store is not read-only, delete all keys nested within the StorePath. |
65 | 80 |
|
66 | 81 | Parameters
|
67 | 82 | ----------
|
68 | 83 | mode : AccessModeLiteral
|
69 | 84 | The mode to use when initializing the store path.
|
70 | 85 |
|
| 86 | + The accepted values are: |
| 87 | +
|
| 88 | + - ``'r'``: read only (must exist) |
| 89 | + - ``'r+'``: read/write (must exist) |
| 90 | + - ``'a'``: read/write (create if doesn't exist) |
| 91 | + - ``'w'``: read/write (overwrite if exists) |
| 92 | + - ``'w-'``: read/write (create if doesn't exist). |
| 93 | +
|
71 | 94 | Raises
|
72 | 95 | ------
|
73 | 96 | FileExistsError
|
74 | 97 | If the mode is 'w-' and the store path already exists.
|
75 | 98 | ValueError
|
76 | 99 | If the mode is not "r" and the store is read-only, or
|
77 |
| - if the mode is "r" and the store is not read-only. |
78 | 100 | """
|
79 | 101 |
|
80 |
| - await store._ensure_open() |
81 |
| - self = cls(store, path) |
82 |
| - |
83 | 102 | # fastpath if mode is None
|
84 | 103 | if mode is None:
|
85 |
| - return self |
| 104 | + return await cls._create_open_instance(store, path) |
86 | 105 |
|
87 |
| - if store.read_only and mode != "r": |
88 |
| - raise ValueError(f"Store is read-only but mode is '{mode}'") |
89 |
| - if not store.read_only and mode == "r": |
90 |
| - raise ValueError(f"Store is not read-only but mode is '{mode}'") |
| 106 | + if mode not in ANY_ACCESS_MODE: |
| 107 | + raise ValueError(f"Invalid mode: {mode}, expected one of {ANY_ACCESS_MODE}") |
91 | 108 |
|
| 109 | + if store.read_only: |
| 110 | + # Don't allow write operations on a read-only store |
| 111 | + if mode != "r": |
| 112 | + raise ValueError( |
| 113 | + f"Store is read-only but mode is {mode!r}. Create a writable store or use 'r' mode." |
| 114 | + ) |
| 115 | + self = await cls._create_open_instance(store, path) |
| 116 | + elif mode == "r": |
| 117 | + # Create read-only copy for read mode on writable store |
| 118 | + try: |
| 119 | + read_only_store = store.with_read_only(True) |
| 120 | + except NotImplementedError as e: |
| 121 | + raise ValueError( |
| 122 | + "Store is not read-only but mode is 'r'. Unable to create a read-only copy of the store. " |
| 123 | + "Please use a read-only store or a storage class that implements .with_read_only()." |
| 124 | + ) from e |
| 125 | + self = await cls._create_open_instance(read_only_store, path) |
| 126 | + else: |
| 127 | + # writable store and writable mode |
| 128 | + self = await cls._create_open_instance(store, path) |
| 129 | + |
| 130 | + # Handle mode-specific operations |
92 | 131 | match mode:
|
93 | 132 | case "w-":
|
94 | 133 | if not await self.is_empty():
|
95 |
| - msg = ( |
96 |
| - f"{self} is not empty, but `mode` is set to 'w-'." |
97 |
| - "Either remove the existing objects in storage," |
98 |
| - "or set `mode` to a value that handles pre-existing objects" |
99 |
| - "in storage, like `a` or `w`." |
| 134 | + raise FileExistsError( |
| 135 | + f"Cannot create '{path}' with mode 'w-' because it already contains data. " |
| 136 | + f"Use mode 'w' to overwrite or 'a' to append." |
100 | 137 | )
|
101 |
| - raise FileExistsError(msg) |
102 | 138 | case "w":
|
103 | 139 | await self.delete_dir()
|
104 |
| - case "a" | "r" | "r+": |
105 |
| - # No init action |
106 |
| - pass |
107 |
| - case _: |
108 |
| - raise ValueError(f"Invalid mode: {mode}") |
109 |
| - |
110 | 140 | return self
|
111 | 141 |
|
112 | 142 | async def get(
|
|
0 commit comments