5
5
import re
6
6
from typing import TYPE_CHECKING , Any
7
7
8
+ import numpy as np
8
9
import pytest
9
10
from packaging .version import parse as parse_version
10
11
11
12
import zarr .api .asynchronous
13
+ from zarr import Array
12
14
from zarr .abc .store import OffsetByteRequest
13
15
from zarr .core .buffer import Buffer , cpu , default_buffer_prototype
14
16
from zarr .core .sync import _collect_aiterator , sync
15
17
from zarr .storage import FsspecStore
18
+ from zarr .storage ._fsspec import _make_async
16
19
from zarr .testing .store import StoreTests
17
20
18
21
if TYPE_CHECKING :
22
+ import pathlib
19
23
from collections .abc import Generator
20
24
from pathlib import Path
21
25
@@ -191,7 +195,11 @@ async def test_fsspec_store_from_uri(self, store: FsspecStore) -> None:
191
195
)
192
196
assert dict (group .attrs ) == {"key" : "value" }
193
197
194
- meta ["attributes" ]["key" ] = "value-2" # type: ignore[index]
198
+ meta = {
199
+ "attributes" : {"key" : "value-2" },
200
+ "zarr_format" : 3 ,
201
+ "node_type" : "group" ,
202
+ }
195
203
await store .set (
196
204
"directory-2/zarr.json" ,
197
205
self .buffer_cls .from_bytes (json .dumps (meta ).encode ()),
@@ -201,7 +209,11 @@ async def test_fsspec_store_from_uri(self, store: FsspecStore) -> None:
201
209
)
202
210
assert dict (group .attrs ) == {"key" : "value-2" }
203
211
204
- meta ["attributes" ]["key" ] = "value-3" # type: ignore[index]
212
+ meta = {
213
+ "attributes" : {"key" : "value-3" },
214
+ "zarr_format" : 3 ,
215
+ "node_type" : "group" ,
216
+ }
205
217
await store .set (
206
218
"directory-3/zarr.json" ,
207
219
self .buffer_cls .from_bytes (json .dumps (meta ).encode ()),
@@ -264,32 +276,131 @@ async def test_delete_dir_unsupported_deletes(self, store: FsspecStore) -> None:
264
276
await store .delete_dir ("test_prefix" )
265
277
266
278
279
+ def array_roundtrip (store : FsspecStore ) -> None :
280
+ """
281
+ Round trip an array using a Zarr store
282
+
283
+ Args:
284
+ store: FsspecStore
285
+ """
286
+ data = np .ones ((3 , 3 ))
287
+ arr = zarr .create_array (store = store , overwrite = True , data = data )
288
+ assert isinstance (arr , Array )
289
+ # Read set values
290
+ arr2 = zarr .open_array (store = store )
291
+ assert isinstance (arr2 , Array )
292
+ np .testing .assert_array_equal (arr [:], data )
293
+
294
+
267
295
@pytest .mark .skipif (
268
296
parse_version (fsspec .__version__ ) < parse_version ("2024.12.0" ),
269
297
reason = "No AsyncFileSystemWrapper" ,
270
298
)
271
- def test_wrap_sync_filesystem () -> None :
299
+ def test_wrap_sync_filesystem (tmp_path : pathlib . Path ) -> None :
272
300
"""The local fs is not async so we should expect it to be wrapped automatically"""
273
301
from fsspec .implementations .asyn_wrapper import AsyncFileSystemWrapper
274
302
275
- store = FsspecStore .from_url ("local://test/path" )
276
-
303
+ store = FsspecStore .from_url (f"file://{ tmp_path } " , storage_options = {"auto_mkdir" : True })
277
304
assert isinstance (store .fs , AsyncFileSystemWrapper )
278
305
assert store .fs .async_impl
306
+ array_roundtrip (store )
307
+
308
+
309
+ @pytest .mark .skipif (
310
+ parse_version (fsspec .__version__ ) >= parse_version ("2024.12.0" ),
311
+ reason = "No AsyncFileSystemWrapper" ,
312
+ )
313
+ def test_wrap_sync_filesystem_raises (tmp_path : pathlib .Path ) -> None :
314
+ """The local fs is not async so we should expect it to be wrapped automatically"""
315
+ with pytest .raises (ImportError , match = "The filesystem .*" ):
316
+ FsspecStore .from_url (f"file://{ tmp_path } " , storage_options = {"auto_mkdir" : True })
279
317
280
318
281
319
@pytest .mark .skipif (
282
320
parse_version (fsspec .__version__ ) < parse_version ("2024.12.0" ),
283
321
reason = "No AsyncFileSystemWrapper" ,
284
322
)
285
323
def test_no_wrap_async_filesystem () -> None :
286
- """An async fs should not be wrapped automatically; fsspec's https filesystem is such an fs"""
324
+ """An async fs should not be wrapped automatically; fsspec's s3 filesystem is such an fs"""
287
325
from fsspec .implementations .asyn_wrapper import AsyncFileSystemWrapper
288
326
289
- store = FsspecStore .from_url ("https://test/path" )
290
-
327
+ store = FsspecStore .from_url (
328
+ f"s3://{ test_bucket_name } /foo/spam/" ,
329
+ storage_options = {"endpoint_url" : endpoint_url , "anon" : False , "asynchronous" : True },
330
+ read_only = False ,
331
+ )
291
332
assert not isinstance (store .fs , AsyncFileSystemWrapper )
292
333
assert store .fs .async_impl
334
+ array_roundtrip (store )
335
+
336
+
337
+ @pytest .mark .skipif (
338
+ parse_version (fsspec .__version__ ) < parse_version ("2024.12.0" ),
339
+ reason = "No AsyncFileSystemWrapper" ,
340
+ )
341
+ def test_open_fsmap_file (tmp_path : pathlib .Path ) -> None :
342
+ min_fsspec_with_async_wrapper = parse_version ("2024.12.0" )
343
+ current_version = parse_version (fsspec .__version__ )
344
+
345
+ fs = fsspec .filesystem ("file" , auto_mkdir = True )
346
+ mapper = fs .get_mapper (tmp_path )
347
+
348
+ if current_version < min_fsspec_with_async_wrapper :
349
+ # Expect ImportError for older versions
350
+ with pytest .raises (
351
+ ImportError ,
352
+ match = r"The filesystem .* is synchronous, and the required AsyncFileSystemWrapper is not available.*" ,
353
+ ):
354
+ array_roundtrip (mapper )
355
+ else :
356
+ # Newer versions should work
357
+ array_roundtrip (mapper )
358
+
359
+
360
+ @pytest .mark .skipif (
361
+ parse_version (fsspec .__version__ ) < parse_version ("2024.12.0" ),
362
+ reason = "No AsyncFileSystemWrapper" ,
363
+ )
364
+ def test_open_fsmap_file_raises (tmp_path : pathlib .Path ) -> None :
365
+ fsspec = pytest .importorskip ("fsspec.implementations.local" )
366
+ fs = fsspec .LocalFileSystem (auto_mkdir = False )
367
+ mapper = fs .get_mapper (tmp_path )
368
+ with pytest .raises (ValueError , match = "LocalFilesystem .*" ):
369
+ array_roundtrip (mapper )
370
+
371
+
372
+ @pytest .mark .parametrize ("asynchronous" , [True , False ])
373
+ def test_open_fsmap_s3 (asynchronous : bool ) -> None :
374
+ s3_filesystem = s3fs .S3FileSystem (
375
+ asynchronous = asynchronous , endpoint_url = endpoint_url , anon = False
376
+ )
377
+ mapper = s3_filesystem .get_mapper (f"s3://{ test_bucket_name } /map/foo/" )
378
+ array_roundtrip (mapper )
379
+
380
+
381
+ def test_open_s3map_raises () -> None :
382
+ with pytest .raises (TypeError , match = "Unsupported type for store_like:.*" ):
383
+ zarr .open (store = 0 , mode = "w" , shape = (3 , 3 ))
384
+ s3_filesystem = s3fs .S3FileSystem (asynchronous = True , endpoint_url = endpoint_url , anon = False )
385
+ mapper = s3_filesystem .get_mapper (f"s3://{ test_bucket_name } /map/foo/" )
386
+ with pytest .raises (
387
+ ValueError , match = "'path' was provided but is not used for FSMap store_like objects"
388
+ ):
389
+ zarr .open (store = mapper , path = "bar" , mode = "w" , shape = (3 , 3 ))
390
+ with pytest .raises (
391
+ ValueError ,
392
+ match = "'storage_options was provided but is not used for FSMap store_like objects" ,
393
+ ):
394
+ zarr .open (store = mapper , storage_options = {"anon" : True }, mode = "w" , shape = (3 , 3 ))
395
+
396
+
397
+ @pytest .mark .parametrize ("asynchronous" , [True , False ])
398
+ def test_make_async (asynchronous : bool ) -> None :
399
+ s3_filesystem = s3fs .S3FileSystem (
400
+ asynchronous = asynchronous , endpoint_url = endpoint_url , anon = False
401
+ )
402
+ fs = _make_async (s3_filesystem )
403
+ assert fs .asynchronous
293
404
294
405
295
406
@pytest .mark .skipif (
0 commit comments