Skip to content

Commit 955df1d

Browse files
authored
test: migrate object storage tests to rustfs (#732)
Replaces MinIO-backed object storage test setup with pytest-databases RustFS, including explicit test bucket creation for S3 round trips. Updates the local FileObject examples to use RustFS defaults instead of MinIO credentials.
1 parent a6e275d commit 955df1d

6 files changed

Lines changed: 915 additions & 618 deletions

File tree

examples/fastapi/fastapi_fileobject.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,15 @@
3737
app = FastAPI()
3838
alchemy = AdvancedAlchemy(config=alchemy_config, app=app)
3939
document_router = APIRouter()
40+
# For local development, run an S3-compatible storage service (e.g., RustFS or MinIO) on port 9000 and create the 'static-files' bucket first.
4041
s3_backend = ObstoreBackend(
4142
key="local",
4243
fs="s3://static-files/",
4344
aws_endpoint="http://localhost:9000",
44-
aws_access_key_id="minioadmin",
45-
aws_secret_access_key="minioadmin", # noqa: S106
45+
aws_access_key_id="rustfsadmin",
46+
aws_secret_access_key="rustfsadmin", # noqa: S106
47+
aws_virtual_hosted_style_request=False,
48+
client_options={"allow_http": True},
4649
)
4750
storages.register_backend(s3_backend)
4851

examples/litestar/litestar_fileobject.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@
2424
from advanced_alchemy.types.file_object.data_type import StoredObject
2525

2626
# Object storage backend
27+
# For local development, run an S3-compatible storage service (e.g., RustFS or MinIO) on port 9000 and create the 'static-files' bucket first.
2728
s3_backend = ObstoreBackend(
2829
key="local",
2930
fs="s3://static-files/",
3031
aws_endpoint="http://localhost:9000",
31-
aws_access_key_id="minioadmin",
32-
aws_secret_access_key="minioadmin", # noqa: S106
32+
aws_access_key_id="rustfsadmin",
33+
aws_secret_access_key="rustfsadmin", # noqa: S106
34+
aws_virtual_hosted_style_request=False,
35+
client_options={"allow_http": True},
3336
)
3437
storages.register_backend(s3_backend)
3538

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ test = [
162162
"pytest>=7.4.4",
163163
"pytest-asyncio>=0.23.8",
164164
"pytest-cov>=5.0.0",
165-
"pytest-databases[postgres,oracle,cockroachdb,mssql,bigquery,spanner,mysql,minio]",
165+
"pytest-databases[postgres,oracle,cockroachdb,bigquery,spanner]",
166166
"pytest-lazy-fixtures>=1.1.1",
167167
"pytest-rerunfailures",
168168
"pytest-mock>=3.14.0",
@@ -469,7 +469,6 @@ module = [
469469
"uuid_utils",
470470
"fsspec",
471471
"fsspec.*",
472-
"s3fs",
473472
"argon2",
474473
"argon2.*",
475474
]

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
pytest_plugins = [
2020
"pytest_databases.docker",
21-
"pytest_databases.docker.minio",
21+
"pytest_databases.docker.rustfs",
2222
"pytest_databases.docker.mysql",
2323
"pytest_databases.docker.oracle",
2424
"pytest_databases.docker.postgres",

tests/integration/test_file_object.py

Lines changed: 58 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
from collections.abc import AsyncGenerator, Generator
44
from contextlib import suppress
55
from pathlib import Path
6-
from typing import Optional
6+
from typing import Any, Optional
77

88
import pytest
99
import pytest_asyncio
10-
from minio import Minio # type: ignore[import-untyped]
11-
from pytest_databases.docker.minio import MinioService
10+
from pytest_databases.docker.rustfs import RustfsService
1211
from sqlalchemy import Engine, String, event
1312
from sqlalchemy.exc import InvalidRequestError
1413
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
@@ -59,6 +58,33 @@ def remove_listeners() -> None:
5958
set_async_context(False)
6059

6160

61+
def rustfs_endpoint_url(rustfs_service: RustfsService) -> str:
62+
scheme = "https" if rustfs_service.secure else "http"
63+
return f"{scheme}://{rustfs_service.endpoint}"
64+
65+
66+
def rustfs_s3_filesystem(rustfs_service: RustfsService, **kwargs: Any) -> Any:
67+
s3fs = pytest.importorskip("s3fs")
68+
return s3fs.S3FileSystem(
69+
anon=False,
70+
key=rustfs_service.access_key,
71+
secret=rustfs_service.secret_key,
72+
endpoint_url=rustfs_endpoint_url(rustfs_service),
73+
client_kwargs={
74+
"verify": rustfs_service.secure,
75+
"use_ssl": rustfs_service.secure,
76+
},
77+
config_kwargs={"s3": {"addressing_style": "path"}},
78+
**kwargs,
79+
)
80+
81+
82+
def ensure_rustfs_bucket(rustfs_service: RustfsService, bucket_name: str) -> None:
83+
fs = rustfs_s3_filesystem(rustfs_service, asynchronous=False, loop=None)
84+
with suppress(FileExistsError):
85+
fs.mkdir(bucket_name)
86+
87+
6288
# --- Fixtures ---
6389
orm_registry = create_registry()
6490

@@ -229,37 +255,20 @@ async def async_session(
229255
)
230256
async def test_fsspec_s3_basic_operations_async(
231257
storage_registry: StorageRegistry,
232-
minio_client: "Minio",
233-
minio_service: "MinioService",
234-
minio_default_bucket_name: str,
258+
rustfs_service: RustfsService,
259+
rustfs_default_bucket_name: str,
235260
) -> None:
236261
"""Test basic save, get_content, delete via backend and FileObject with prefix."""
237262
remove_listeners()
238-
try:
239-
import s3fs
240-
except ImportError:
241-
pytest.skip("s3fs not installed")
242263

243-
assert minio_client.bucket_exists(minio_default_bucket_name)
244-
_ = minio_client
245-
246-
# Create s3fs filesystem instance without bucket info
247-
fs = s3fs.S3FileSystem(
248-
anon=False,
249-
key=minio_service.access_key,
250-
secret=minio_service.secret_key,
251-
endpoint_url=f"http://{minio_service.endpoint}",
252-
client_kwargs={
253-
"verify": False,
254-
"use_ssl": False,
255-
},
256-
)
264+
ensure_rustfs_bucket(rustfs_service, rustfs_default_bucket_name)
265+
fs = rustfs_s3_filesystem(rustfs_service)
257266

258267
# Initialize backend with prefix
259268
backend = FSSpecBackend(
260269
key="s3_test_store",
261270
fs=fs,
262-
prefix=minio_default_bucket_name,
271+
prefix=rustfs_default_bucket_name,
263272
)
264273

265274
test_content = b"Hello Storage!"
@@ -311,39 +320,24 @@ async def test_fsspec_s3_basic_operations_async(
311320
)
312321
def test_fsspec_s3_basic_operations_sync(
313322
storage_registry: StorageRegistry,
314-
minio_client: "Minio",
315-
minio_service: "MinioService",
316-
minio_default_bucket_name: str,
323+
rustfs_service: RustfsService,
324+
rustfs_default_bucket_name: str,
317325
) -> None:
318326
"""Test basic save, get_content, delete via backend and FileObject with prefix."""
319327
remove_listeners()
320-
try:
321-
import s3fs
322-
except ImportError:
323-
pytest.skip("s3fs not installed")
324-
325-
assert minio_client.bucket_exists(minio_default_bucket_name)
326-
_ = minio_client
327328

328-
# Create s3fs filesystem instance without bucket info
329-
fs = s3fs.S3FileSystem(
330-
anon=False,
331-
key=minio_service.access_key,
332-
secret=minio_service.secret_key,
333-
endpoint_url=f"http://{minio_service.endpoint}",
334-
client_kwargs={
335-
"verify": False,
336-
"use_ssl": False,
337-
},
329+
fs = rustfs_s3_filesystem(
330+
rustfs_service,
338331
asynchronous=False,
339332
loop=None,
340333
)
334+
ensure_rustfs_bucket(rustfs_service, rustfs_default_bucket_name)
341335

342336
# Initialize backend with prefix
343337
backend = FSSpecBackend(
344338
key="s3_test_store",
345339
fs=fs,
346-
prefix=minio_default_bucket_name,
340+
prefix=rustfs_default_bucket_name,
347341
)
348342

349343
test_content = b"Hello Storage!"
@@ -391,22 +385,21 @@ def test_fsspec_s3_basic_operations_sync(
391385
@pytest.mark.xdist_group("file_object")
392386
async def test_obstore_s3_basic_operations_async(
393387
storage_registry: StorageRegistry,
394-
minio_client: "Minio",
395-
minio_service: "MinioService",
396-
minio_default_bucket_name: str,
388+
rustfs_service: RustfsService,
389+
rustfs_default_bucket_name: str,
397390
) -> None:
398391
"""Test basic save, get_content, delete via backend and FileObject."""
399392
remove_listeners()
400-
assert minio_client.bucket_exists(minio_default_bucket_name)
401-
_ = minio_client
393+
endpoint_url = rustfs_endpoint_url(rustfs_service)
394+
ensure_rustfs_bucket(rustfs_service, rustfs_default_bucket_name)
402395
backend = ObstoreBackend(
403396
key="s3_test_store",
404-
fs=f"s3://{minio_default_bucket_name}/",
405-
aws_endpoint=f"http://{minio_service.endpoint}/",
406-
aws_access_key_id=minio_service.access_key,
407-
aws_secret_access_key=minio_service.secret_key,
397+
fs=f"s3://{rustfs_default_bucket_name}/",
398+
aws_endpoint=f"{endpoint_url}/",
399+
aws_access_key_id=rustfs_service.access_key,
400+
aws_secret_access_key=rustfs_service.secret_key,
408401
aws_virtual_hosted_style_request=False,
409-
client_options={"allow_http": True},
402+
client_options={"allow_http": not rustfs_service.secure},
410403
)
411404

412405
test_content = b"Hello Storage!"
@@ -456,22 +449,21 @@ async def test_obstore_s3_basic_operations_async(
456449
@pytest.mark.xdist_group("file_object")
457450
def test_obstore_s3_basic_operations_sync(
458451
storage_registry: StorageRegistry,
459-
minio_client: "Minio",
460-
minio_service: "MinioService",
461-
minio_default_bucket_name: str,
452+
rustfs_service: RustfsService,
453+
rustfs_default_bucket_name: str,
462454
) -> None:
463455
"""Test basic save, get_content, delete via backend and FileObject."""
464456
remove_listeners()
465-
assert minio_client.bucket_exists(minio_default_bucket_name)
466-
_ = minio_client
457+
endpoint_url = rustfs_endpoint_url(rustfs_service)
458+
ensure_rustfs_bucket(rustfs_service, rustfs_default_bucket_name)
467459
backend = ObstoreBackend(
468460
key="s3_test_store",
469-
fs=f"s3://{minio_default_bucket_name}/",
470-
aws_endpoint=f"http://{minio_service.endpoint}/",
471-
aws_access_key_id=minio_service.access_key,
472-
aws_secret_access_key=minio_service.secret_key,
461+
fs=f"s3://{rustfs_default_bucket_name}/",
462+
aws_endpoint=f"{endpoint_url}/",
463+
aws_access_key_id=rustfs_service.access_key,
464+
aws_secret_access_key=rustfs_service.secret_key,
473465
aws_virtual_hosted_style_request=False,
474-
client_options={"allow_http": True},
466+
client_options={"allow_http": not rustfs_service.secure},
475467
)
476468

477469
test_content = b"Hello Storage!"

0 commit comments

Comments
 (0)