18
18
from zarr .core .buffer import default_buffer_prototype
19
19
from zarr .registry import get_store_adapter , register_store_adapter
20
20
from zarr .storage import FsspecStore , LocalStore , LoggingStore , MemoryStore , ZipStore
21
- from zarr .storage ._builtin_adapters import GSAdapter , HttpsAdapter , LoggingAdapter , S3Adapter
21
+ from zarr .storage ._builtin_adapters import (
22
+ GSAdapter ,
23
+ HttpsAdapter ,
24
+ LoggingAdapter ,
25
+ S3Adapter ,
26
+ S3HttpAdapter ,
27
+ S3HttpsAdapter ,
28
+ )
22
29
from zarr .storage ._common import make_store_path
23
30
from zarr .storage ._zep8 import URLParser , URLStoreResolver , ZEP8URLError , is_zep8_url
24
31
@@ -513,6 +520,8 @@ def test_fsspec_zep8_url_detection() -> None:
513
520
# These should be detected as ZEP 8 URLs
514
521
zep8_urls = [
515
522
"s3://bucket/data.zip|zip:" ,
523
+ "s3+http://minio.local:9000/bucket/data.zip|zip:" ,
524
+ "s3+https://storage.example.com/bucket/data.zarr|zarr3:" ,
516
525
"https://example.com/data|zip:|zarr3:" ,
517
526
"gs://bucket/data.zarr|zarr2:" ,
518
527
]
@@ -538,7 +547,7 @@ async def test_fsspec_adapter_error_handling() -> None:
538
547
# Test S3 adapter with invalid URL
539
548
segment = URLSegment (scheme = "s3" , path = "bucket/data" , adapter = None )
540
549
541
- with pytest .raises (ValueError , match = "Unsupported scheme " ):
550
+ with pytest .raises (ValueError , match = "Unsupported S3 URL format " ):
542
551
await S3Adapter .from_url_segment (segment , "invalid://url" )
543
552
544
553
# Test HTTPS adapter with invalid URL
@@ -566,7 +575,7 @@ def test_fsspec_schemes_support() -> None:
566
575
567
576
# Test S3 adapter
568
577
assert S3Adapter .can_handle_scheme ("s3" )
569
- assert S3Adapter .get_supported_schemes () == ["s3" ]
578
+ assert S3Adapter .get_supported_schemes () == ["s3" , "s3+http" , "s3+https" ]
570
579
571
580
# Test HTTPS adapter
572
581
assert HttpsAdapter .can_handle_scheme ("https" )
@@ -589,6 +598,8 @@ async def test_fsspec_url_chain_parsing() -> None:
589
598
# Test complex chained URLs
590
599
complex_urls = [
591
600
"s3://bucket/archive.zip|zip:data/|zarr3:group" ,
601
+ "s3+http://minio.local:9000/bucket/archive.zip|zip:data/|zarr3:group" ,
602
+ "s3+https://storage.example.com/bucket/nested.zip|zip:inner/|zarr2:" ,
592
603
"https://example.com/data.tar.gz|tar:|zip:|zarr2:" ,
593
604
"gs://bucket/dataset.zarr|zarr3:array/subarray" ,
594
605
]
@@ -1223,16 +1234,149 @@ async def test_remote_adapter_comprehensive() -> None:
1223
1234
1224
1235
1225
1236
async def test_s3_adapter_functionality () -> None :
1226
- """Test S3Adapter functionality."""
1237
+ """Test S3Adapter functionality including custom endpoints ."""
1227
1238
from zarr .storage ._builtin_adapters import S3Adapter
1228
1239
1229
- # Test can_handle_scheme
1240
+ # Test can_handle_scheme for all supported schemes
1230
1241
assert S3Adapter .can_handle_scheme ("s3" )
1242
+ assert S3Adapter .can_handle_scheme ("s3+http" )
1243
+ assert S3Adapter .can_handle_scheme ("s3+https" )
1231
1244
assert not S3Adapter .can_handle_scheme ("gs" )
1245
+ assert not S3Adapter .can_handle_scheme ("http" )
1232
1246
1233
1247
# Test get_supported_schemes
1234
1248
schemes = S3Adapter .get_supported_schemes ()
1235
1249
assert "s3" in schemes
1250
+ assert "s3+http" in schemes
1251
+ assert "s3+https" in schemes
1252
+ assert len (schemes ) == 3
1253
+
1254
+
1255
+ async def test_s3_custom_endpoint_url_parsing () -> None :
1256
+ """Test S3Adapter URL parsing for custom endpoints."""
1257
+ from zarr .storage ._builtin_adapters import S3Adapter
1258
+
1259
+ # Test standard AWS S3 URL parsing
1260
+ s3_url , endpoint_url , storage_options = S3Adapter ._parse_s3_url ("s3://my-bucket/path/to/data" )
1261
+ assert s3_url == "s3://my-bucket/path/to/data"
1262
+ assert endpoint_url is None
1263
+ assert storage_options == {}
1264
+
1265
+ # Test custom HTTP endpoint parsing
1266
+ s3_url , endpoint_url , storage_options = S3Adapter ._parse_s3_url (
1267
+ "s3+http://minio.local:9000/my-bucket/data"
1268
+ )
1269
+ assert s3_url == "s3://my-bucket/data"
1270
+ assert endpoint_url == "http://minio.local:9000"
1271
+ assert storage_options == {"endpoint_url" : "http://minio.local:9000" , "use_ssl" : False }
1272
+
1273
+ # Test custom HTTPS endpoint parsing
1274
+ s3_url , endpoint_url , storage_options = S3Adapter ._parse_s3_url (
1275
+ "s3+https://storage.example.com/bucket/path/file.zarr"
1276
+ )
1277
+ assert s3_url == "s3://bucket/path/file.zarr"
1278
+ assert endpoint_url == "https://storage.example.com"
1279
+ assert storage_options == {"endpoint_url" : "https://storage.example.com" , "use_ssl" : True }
1280
+
1281
+ # Test custom HTTP endpoint with port
1282
+ s3_url , endpoint_url , storage_options = S3Adapter ._parse_s3_url (
1283
+ "s3+http://localhost:9000/test-bucket"
1284
+ )
1285
+ assert s3_url == "s3://test-bucket"
1286
+ assert endpoint_url == "http://localhost:9000"
1287
+ assert storage_options ["endpoint_url" ] == "http://localhost:9000"
1288
+ assert storage_options ["use_ssl" ] is False
1289
+
1290
+ # Test edge case: endpoint without path
1291
+ s3_url , endpoint_url , storage_options = S3Adapter ._parse_s3_url ("s3+https://minio.example.com" )
1292
+ assert s3_url == "s3://"
1293
+ assert endpoint_url == "https://minio.example.com"
1294
+
1295
+
1296
+ async def test_s3_custom_endpoint_scheme_extraction () -> None :
1297
+ """Test S3Adapter scheme extraction for custom endpoints."""
1298
+ from zarr .storage ._builtin_adapters import S3Adapter
1299
+
1300
+ # Test scheme extraction
1301
+ assert S3Adapter ._extract_scheme ("s3://bucket/path" ) == "s3"
1302
+ assert S3Adapter ._extract_scheme ("s3+http://minio.local:9000/bucket" ) == "s3+http"
1303
+ assert S3Adapter ._extract_scheme ("s3+https://storage.example.com/bucket" ) == "s3+https"
1304
+
1305
+
1306
+ async def test_s3_custom_endpoint_error_handling () -> None :
1307
+ """Test S3Adapter error handling for invalid URLs."""
1308
+ from zarr .storage ._builtin_adapters import S3Adapter
1309
+
1310
+ # Test unsupported URL format
1311
+ with pytest .raises (ValueError , match = "Unsupported S3 URL format" ):
1312
+ S3Adapter ._parse_s3_url ("invalid://not-s3" )
1313
+
1314
+ with pytest .raises (ValueError , match = "Unsupported S3 URL format" ):
1315
+ S3Adapter ._parse_s3_url ("gs://bucket/path" )
1316
+
1317
+
1318
+ async def test_s3_custom_endpoint_registration () -> None :
1319
+ """Test that custom S3 endpoint schemes are properly registered."""
1320
+ from zarr .registry import get_store_adapter
1321
+
1322
+ # Test that all S3 schemes can be retrieved
1323
+ s3_adapter = get_store_adapter ("s3" )
1324
+ assert s3_adapter is not None
1325
+ assert s3_adapter == S3Adapter
1326
+
1327
+ s3_http_adapter = get_store_adapter ("s3+http" )
1328
+ assert s3_http_adapter is not None
1329
+ assert s3_http_adapter == S3HttpAdapter
1330
+
1331
+ s3_https_adapter = get_store_adapter ("s3+https" )
1332
+ assert s3_https_adapter is not None
1333
+ assert s3_https_adapter == S3HttpsAdapter
1334
+
1335
+
1336
+ async def test_s3_http_adapter_functionality () -> None :
1337
+ """Test S3HttpAdapter specific functionality."""
1338
+ # Test adapter name
1339
+ assert S3HttpAdapter .adapter_name == "s3+http"
1340
+
1341
+ # Test supported schemes
1342
+ schemes = S3HttpAdapter .get_supported_schemes ()
1343
+ assert schemes == ["s3+http" ]
1344
+
1345
+ # Test can_handle_scheme
1346
+ assert S3HttpAdapter .can_handle_scheme ("s3+http" )
1347
+ assert not S3HttpAdapter .can_handle_scheme ("s3" )
1348
+ assert not S3HttpAdapter .can_handle_scheme ("s3+https" )
1349
+
1350
+
1351
+ async def test_s3_https_adapter_functionality () -> None :
1352
+ """Test S3HttpsAdapter specific functionality."""
1353
+ # Test adapter name
1354
+ assert S3HttpsAdapter .adapter_name == "s3+https"
1355
+
1356
+ # Test supported schemes
1357
+ schemes = S3HttpsAdapter .get_supported_schemes ()
1358
+ assert schemes == ["s3+https" ]
1359
+
1360
+ # Test can_handle_scheme
1361
+ assert S3HttpsAdapter .can_handle_scheme ("s3+https" )
1362
+ assert not S3HttpsAdapter .can_handle_scheme ("s3" )
1363
+ assert not S3HttpsAdapter .can_handle_scheme ("s3+http" )
1364
+
1365
+
1366
+ async def test_s3_custom_endpoint_zep8_url_detection () -> None :
1367
+ """Test ZEP 8 URL detection with custom S3 endpoints."""
1368
+ from zarr .storage ._zep8 import is_zep8_url
1369
+
1370
+ # Standard S3 URLs (not ZEP 8)
1371
+ assert not is_zep8_url ("s3://bucket/data" )
1372
+ assert not is_zep8_url ("s3+http://minio.local:9000/bucket/data" )
1373
+ assert not is_zep8_url ("s3+https://storage.example.com/bucket/data" )
1374
+
1375
+ # ZEP 8 URLs with custom S3 endpoints
1376
+ assert is_zep8_url ("s3://bucket/data.zip|zip:" )
1377
+ assert is_zep8_url ("s3+http://minio.local:9000/bucket/data.zip|zip:" )
1378
+ assert is_zep8_url ("s3+https://storage.example.com/bucket/data|zarr3:" )
1379
+ assert is_zep8_url ("s3+http://localhost:9000/bucket/archive.zip|zip:data/|zarr2:" )
1236
1380
1237
1381
1238
1382
async def test_gcs_adapter_functionality () -> None :
@@ -1346,6 +1490,8 @@ def test_builtin_adapters_imports_and_module_structure() -> None:
1346
1490
MemoryAdapter ,
1347
1491
RemoteAdapter ,
1348
1492
S3Adapter ,
1493
+ S3HttpAdapter ,
1494
+ S3HttpsAdapter ,
1349
1495
ZipAdapter ,
1350
1496
)
1351
1497
@@ -1354,13 +1500,17 @@ def test_builtin_adapters_imports_and_module_structure() -> None:
1354
1500
assert MemoryAdapter .adapter_name == "memory"
1355
1501
assert RemoteAdapter .adapter_name == "remote"
1356
1502
assert S3Adapter .adapter_name == "s3"
1503
+ assert S3HttpAdapter .adapter_name == "s3+http"
1504
+ assert S3HttpsAdapter .adapter_name == "s3+https"
1357
1505
assert GSAdapter .adapter_name == "gs"
1358
1506
assert HttpsAdapter .adapter_name == "https"
1359
1507
assert LoggingAdapter .adapter_name == "log"
1360
1508
assert ZipAdapter .adapter_name == "zip"
1361
1509
1362
1510
# Test inheritance relationships
1363
1511
assert issubclass (S3Adapter , RemoteAdapter )
1512
+ assert issubclass (S3HttpAdapter , S3Adapter )
1513
+ assert issubclass (S3HttpsAdapter , S3Adapter )
1364
1514
assert issubclass (GSAdapter , RemoteAdapter )
1365
1515
assert issubclass (HttpsAdapter , RemoteAdapter )
1366
1516
0 commit comments