@@ -109,37 +109,52 @@ async def open(
109
109
cls ,
110
110
store : StoreLike ,
111
111
runtime_configuration : RuntimeConfiguration = RuntimeConfiguration (),
112
- zarr_format : Literal [2 , 3 ] = 3 ,
112
+ zarr_format : Literal [2 , 3 , None ] = 3 ,
113
113
) -> AsyncGroup :
114
114
store_path = make_store_path (store )
115
- zarr_json_bytes = await (store_path / ZARR_JSON ).get ()
116
- assert zarr_json_bytes is not None
117
-
118
- # TODO: consider trying to autodiscover the zarr-format here
119
- if zarr_format == 3 :
120
- # V3 groups are comprised of a zarr.json object
121
- # (it is optional in the case of implicit groups)
122
- zarr_json_bytes = await (store_path / ZARR_JSON ).get ()
123
- zarr_json = (
124
- json .loads (zarr_json_bytes ) if zarr_json_bytes is not None else {"zarr_format" : 3 }
125
- )
126
115
127
- elif zarr_format == 2 :
128
- # V2 groups are comprised of a .zgroup and .zattrs objects
129
- # (both are optional in the case of implicit groups)
116
+ if zarr_format == 2 :
130
117
zgroup_bytes , zattrs_bytes = await asyncio .gather (
131
118
(store_path / ZGROUP_JSON ).get (), (store_path / ZATTRS_JSON ).get ()
132
119
)
133
- zgroup = (
134
- json .loads (json .loads (zgroup_bytes ))
135
- if zgroup_bytes is not None
136
- else {"zarr_format" : 2 }
120
+ if zgroup_bytes is None :
121
+ raise KeyError (store_path ) # filenotfounderror?
122
+ elif zarr_format == 3 :
123
+ zarr_json_bytes = await (store_path / ZARR_JSON ).get ()
124
+ if zarr_json_bytes is None :
125
+ raise KeyError (store_path ) # filenotfounderror?
126
+ elif zarr_format is None :
127
+ zarr_json_bytes , zgroup_bytes , zattrs_bytes = await asyncio .gather (
128
+ (store_path / ZARR_JSON ).get (),
129
+ (store_path / ZGROUP_JSON ).get (),
130
+ (store_path / ZATTRS_JSON ).get (),
137
131
)
138
- zattrs = json .loads (json .loads (zattrs_bytes )) if zattrs_bytes is not None else {}
139
- zarr_json = {** zgroup , "attributes" : zattrs }
132
+ if zarr_json_bytes is not None and zgroup_bytes is not None :
133
+ # TODO: revisit this exception type
134
+ # alternatively, we could warn and favor v3
135
+ raise ValueError ("Both zarr.json and .zgroup objects exist" )
136
+ if zarr_json_bytes is None and zgroup_bytes is None :
137
+ raise KeyError (store_path ) # filenotfounderror?
138
+ # set zarr_format based on which keys were found
139
+ if zarr_json_bytes is not None :
140
+ zarr_format = 3
141
+ else :
142
+ zarr_format = 2
140
143
else :
141
144
raise ValueError (f"unexpected zarr_format: { zarr_format } " )
142
- return cls .from_dict (store_path , zarr_json , runtime_configuration )
145
+
146
+ if zarr_format == 2 :
147
+ # V2 groups are comprised of a .zgroup and .zattrs objects
148
+ assert zgroup_bytes is not None
149
+ zgroup = json .loads (zgroup_bytes )
150
+ zattrs = json .loads (zattrs_bytes ) if zattrs_bytes is not None else {}
151
+ group_metadata = {** zgroup , "attributes" : zattrs }
152
+ else :
153
+ # V3 groups are comprised of a zarr.json object
154
+ assert zarr_json_bytes is not None
155
+ group_metadata = json .loads (zarr_json_bytes )
156
+
157
+ return cls .from_dict (store_path , group_metadata , runtime_configuration )
143
158
144
159
@classmethod
145
160
def from_dict (
@@ -174,13 +189,7 @@ async def getitem(
174
189
if self .metadata .zarr_format == 3 :
175
190
zarr_json_bytes = await (store_path / ZARR_JSON ).get ()
176
191
if zarr_json_bytes is None :
177
- # implicit group?
178
- logger .warning ("group at %s is an implicit group" , store_path )
179
- zarr_json = {
180
- "zarr_format" : self .metadata .zarr_format ,
181
- "node_type" : "group" ,
182
- "attributes" : {},
183
- }
192
+ raise KeyError (key )
184
193
else :
185
194
zarr_json = json .loads (zarr_json_bytes )
186
195
if zarr_json ["node_type" ] == "group" :
@@ -200,6 +209,9 @@ async def getitem(
200
209
(store_path / ZATTRS_JSON ).get (),
201
210
)
202
211
212
+ if zgroup_bytes is None and zarray_bytes is None :
213
+ raise KeyError (key )
214
+
203
215
# unpack the zarray, if this is None then we must be opening a group
204
216
zarray = json .loads (zarray_bytes ) if zarray_bytes else None
205
217
# unpack the zattrs, this can be None if no attrs were written
@@ -212,9 +224,6 @@ async def getitem(
212
224
store_path , zarray , runtime_configuration = self .runtime_configuration
213
225
)
214
226
else :
215
- if zgroup_bytes is None :
216
- # implicit group?
217
- logger .warning ("group at %s is an implicit group" , store_path )
218
227
zgroup = (
219
228
json .loads (zgroup_bytes )
220
229
if zgroup_bytes is not None
@@ -288,7 +297,12 @@ def __repr__(self):
288
297
return f"<AsyncGroup { self .store_path } >"
289
298
290
299
async def nmembers (self ) -> int :
291
- raise NotImplementedError
300
+ # TODO: consider using aioitertools.builtins.sum for this
301
+ # return await aioitertools.builtins.sum((1 async for _ in self.members()), start=0)
302
+ n = 0
303
+ async for _ in self .members ():
304
+ n += 1
305
+ return n
292
306
293
307
async def members (self ) -> AsyncGenerator [tuple [str , AsyncArray | AsyncGroup ], None ]:
294
308
"""
@@ -321,10 +335,14 @@ async def members(self) -> AsyncGenerator[tuple[str, AsyncArray | AsyncGroup], N
321
335
logger .warning (
322
336
"Object at %s is not recognized as a component of a Zarr hierarchy." , subkey
323
337
)
324
- pass
325
338
326
339
async def contains (self , member : str ) -> bool :
327
- raise NotImplementedError
340
+ # TODO: this can be made more efficient.
341
+ try :
342
+ await self .getitem (member )
343
+ return True
344
+ except KeyError :
345
+ return False
328
346
329
347
# todo: decide if this method should be separate from `groups`
330
348
async def group_keys (self ) -> AsyncGenerator [str , None ]:
@@ -493,26 +511,18 @@ def members(self) -> tuple[tuple[str, Array | Group], ...]:
493
511
def __contains__ (self , member ) -> bool :
494
512
return self ._sync (self ._async_group .contains (member ))
495
513
496
- def group_keys (self ) -> list [str ]:
497
- # uncomment with AsyncGroup implements this method
498
- # return self._sync_iter(self._async_group.group_keys())
499
- raise NotImplementedError
514
+ def group_keys (self ) -> tuple [str , ...]:
515
+ return tuple (self ._sync_iter (self ._async_group .group_keys ()))
500
516
501
- def groups (self ) -> list [Group ]:
517
+ def groups (self ) -> tuple [Group , ... ]:
502
518
# TODO: in v2 this was a generator that return key: Group
503
- # uncomment with AsyncGroup implements this method
504
- # return [Group(obj) for obj in self._sync_iter(self._async_group.groups())]
505
- raise NotImplementedError
519
+ return tuple (Group (obj ) for obj in self ._sync_iter (self ._async_group .groups ()))
506
520
507
- def array_keys (self ) -> list [str ]:
508
- # uncomment with AsyncGroup implements this method
509
- # return self._sync_iter(self._async_group.array_keys)
510
- raise NotImplementedError
521
+ def array_keys (self ) -> tuple [str , ...]:
522
+ return tuple (self ._sync_iter (self ._async_group .array_keys ()))
511
523
512
- def arrays (self ) -> list [Array ]:
513
- # uncomment with AsyncGroup implements this method
514
- # return [Array(obj) for obj in self._sync_iter(self._async_group.arrays)]
515
- raise NotImplementedError
524
+ def arrays (self ) -> tuple [Array , ...]:
525
+ return tuple (Array (obj ) for obj in self ._sync_iter (self ._async_group .arrays ()))
516
526
517
527
def tree (self , expand = False , level = None ) -> Any :
518
528
return self ._sync (self ._async_group .tree (expand = expand , level = level ))
0 commit comments