Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ test = [
"pytest>=8.0.0",

"pytest-cov>=5.0.0",
"pytest-databases[postgres,oracle,mysql,bigquery,spanner,minio]>=0.17.0",
"pytest-databases[postgres,oracle,bigquery,spanner]>=0.18.0",
"pytest-mock>=3.14.0",
"pytest-sugar>=1.0.0",
"pytest-xdist>=3.6.1",
Expand All @@ -164,7 +164,7 @@ sqlspec-light = "tools.sphinx_ext.pygments_styles:SQLSpecLightStyle"
# mysql-connector-python 9.7.0 dropped cp312/cp313/cp314 wheels (regression vs 9.6.0).
# Override dependency metadata for every source that requests mysql-connector-python.
# Keep the uncapped dependency on Python <3.12, and force the cap on Python >=3.12
# so transitive pulls (e.g., pytest-databases[mysql]) resolve to an existing wheel.
# so the SQLSpec mysql-connector extra resolves to an existing wheel.
override-dependencies = [
"mysql-connector-python; python_version < '3.12'",
"mysql-connector-python<9.7.0; python_version >= '3.12'",
Expand Down Expand Up @@ -469,7 +469,6 @@ module = [
"fsspec.*",
"sqlglot",
"sqlglot.*",
"minio",
]

[[tool.mypy.overrides]]
Expand Down
24 changes: 0 additions & 24 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@

from collections.abc import Generator # noqa: E402
from pathlib import Path # noqa: E402
from typing import TYPE_CHECKING # noqa: E402

import pytest # noqa: E402
from minio import Minio # noqa: E402

if TYPE_CHECKING:
from pytest_databases.docker.minio import MinioService


def is_compiled() -> bool:
Expand Down Expand Up @@ -45,7 +40,6 @@ def is_compiled() -> bool:
"pytest_databases.docker.mysql",
"pytest_databases.docker.bigquery",
"pytest_databases.docker.spanner",
"pytest_databases.docker.minio",
"pytest_databases.docker.cockroachdb",
]

Expand Down Expand Up @@ -84,24 +78,6 @@ def suppress_noisy_test_loggers() -> "Generator[None, None, None]":
logging.getLogger(name).setLevel(level)


@pytest.fixture(scope="session")
def minio_client(minio_service: "MinioService", minio_default_bucket_name: str) -> Generator[Minio, None, None]:
"""Override pytest-databases minio_client to use new minio API with keyword arguments."""
client = Minio(
endpoint=minio_service.endpoint,
access_key=minio_service.access_key,
secret_key=minio_service.secret_key,
secure=minio_service.secure,
)
try:
if not client.bucket_exists(bucket_name=minio_default_bucket_name):
client.make_bucket(bucket_name=minio_default_bucket_name)
except Exception as e:
msg = f"Failed to create bucket {minio_default_bucket_name}"
raise RuntimeError(msg) from e
yield client


def pytest_addoption(parser: pytest.Parser) -> None:
"""Add custom pytest command line options."""
parser.addoption(
Expand Down
62 changes: 62 additions & 0 deletions tests/fixtures/rustfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""RustFS-backed S3-compatible test helpers."""

from typing import TYPE_CHECKING, Any

import pytest

if TYPE_CHECKING:
from pytest_databases.docker.rustfs import RustfsService


def rustfs_endpoint_url(rustfs_service: "RustfsService") -> str:
"""Return the S3 endpoint URL for a pytest-databases RustFS service."""
scheme = "https" if rustfs_service.secure else "http"
return f"{scheme}://{rustfs_service.endpoint}"


def rustfs_fsspec_kwargs(rustfs_service: "RustfsService") -> dict[str, Any]:
"""Return FSSpec S3 options for a pytest-databases RustFS service."""
endpoint_url = rustfs_endpoint_url(rustfs_service)
return {
"endpoint_url": endpoint_url,
"key": rustfs_service.access_key,
"secret": rustfs_service.secret_key,
"use_ssl": rustfs_service.secure,
"client_kwargs": {"endpoint_url": endpoint_url, "verify": False},
"config_kwargs": {"s3": {"addressing_style": "path"}},
}


def rustfs_obstore_kwargs(rustfs_service: "RustfsService") -> dict[str, Any]:
"""Return ObStore S3 options for a pytest-databases RustFS service."""
return {
"aws_endpoint": rustfs_endpoint_url(rustfs_service),
"aws_access_key_id": rustfs_service.access_key,
"aws_secret_access_key": rustfs_service.secret_key,
"aws_virtual_hosted_style_request": False,
"client_options": {"allow_http": not rustfs_service.secure},
}


def rustfs_filesystem(rustfs_service: "RustfsService") -> Any:
"""Create an S3 filesystem connected to the RustFS service."""
pytest.importorskip("s3fs", reason="s3fs is required for RustFS S3 test setup")
fsspec = pytest.importorskip("fsspec", reason="fsspec is required for RustFS S3 test setup")
return fsspec.filesystem("s3", anon=False, **rustfs_fsspec_kwargs(rustfs_service))


def ensure_rustfs_bucket(rustfs_service: "RustfsService", bucket_name: str) -> str:
"""Ensure the default RustFS bucket exists and return its name."""
fs = rustfs_filesystem(rustfs_service)
if not fs.exists(bucket_name):
fs.mkdir(bucket_name)
return bucket_name


def rustfs_object_size(rustfs_service: "RustfsService", bucket_name: str, object_name: str) -> int:
"""Return the size of an object stored in RustFS."""
info = rustfs_filesystem(rustfs_service).info(f"{bucket_name}/{object_name}")
size = info.get("size")
if isinstance(size, int):
return size
return 0
20 changes: 7 additions & 13 deletions tests/integration/adapters/_storage_bridge_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,20 @@
from typing import TYPE_CHECKING

from sqlspec.storage.registry import storage_registry
from tests.fixtures.rustfs import rustfs_fsspec_kwargs

if TYPE_CHECKING: # pragma: no cover
from pytest_databases.docker.minio import MinioService
from pytest_databases.docker.rustfs import RustfsService

__all__ = ("register_minio_alias",)
__all__ = ("register_rustfs_alias",)


def register_minio_alias(
alias: str, minio_service: "MinioService", bucket: str, *, prefix: str = "storage-bridge"
def register_rustfs_alias(
alias: str, rustfs_service: "RustfsService", bucket: str, *, prefix: str = "storage-bridge"
) -> str:
"""Register a storage registry alias backed by the pytest-databases MinIO service."""
"""Register a storage registry alias backed by the pytest-databases RustFS service."""

storage_registry.register_alias(
alias,
f"s3://{bucket}/{prefix}",
backend="fsspec",
endpoint_url=f"http://{minio_service.endpoint}",
key=minio_service.access_key,
secret=minio_service.secret_key,
use_ssl=False,
client_kwargs={"endpoint_url": f"http://{minio_service.endpoint}", "verify": False},
alias, f"s3://{bucket}/{prefix}", backend="fsspec", **rustfs_fsspec_kwargs(rustfs_service)
)
return prefix
21 changes: 8 additions & 13 deletions tests/integration/adapters/asyncpg/test_storage_bridge.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Storage bridge integration tests for AsyncPG using MinIO."""
"""Storage bridge integration tests for AsyncPG using RustFS."""

from typing import TYPE_CHECKING

Expand All @@ -7,11 +7,11 @@
from sqlspec.adapters.asyncpg import AsyncpgDriver
from sqlspec.storage.registry import storage_registry
from sqlspec.typing import FSSPEC_INSTALLED, PYARROW_INSTALLED
from tests.integration.adapters._storage_bridge_helpers import register_minio_alias
from tests.fixtures.rustfs import rustfs_object_size
from tests.integration.adapters._storage_bridge_helpers import register_rustfs_alias

if TYPE_CHECKING: # pragma: no cover
from minio import Minio
from pytest_databases.docker.minio import MinioService
from pytest_databases.docker.rustfs import RustfsService

pytestmark = [
pytest.mark.asyncpg,
Expand All @@ -21,11 +21,8 @@
]


async def test_asyncpg_storage_bridge_with_minio(
asyncpg_async_driver: AsyncpgDriver,
minio_service: "MinioService",
minio_client: "Minio",
minio_default_bucket_name: str,
async def test_asyncpg_storage_bridge_with_rustfs(
asyncpg_async_driver: AsyncpgDriver, rustfs_service: "RustfsService", rustfs_bucket_name: str
) -> None:
alias = "storage_bridge_asyncpg"
destination_path = "alias://storage_bridge_asyncpg/asyncpg/export.parquet"
Expand All @@ -34,7 +31,7 @@ async def test_asyncpg_storage_bridge_with_minio(

storage_registry.clear()
try:
prefix = register_minio_alias(alias, minio_service, minio_default_bucket_name)
prefix = register_rustfs_alias(alias, rustfs_service, rustfs_bucket_name)

await asyncpg_async_driver.execute(f"DROP TABLE IF EXISTS {source_table} CASCADE")
await asyncpg_async_driver.execute(f"DROP TABLE IF EXISTS {target_table} CASCADE")
Expand All @@ -59,9 +56,7 @@ async def test_asyncpg_storage_bridge_with_minio(
assert rows == [(1, "north"), (2, "south"), (3, "east")]

object_name = f"{prefix}/asyncpg/export.parquet"
stat = minio_client.stat_object(bucket_name=minio_default_bucket_name, object_name=object_name)
object_size = stat.size if stat.size is not None else 0
assert object_size > 0
assert rustfs_object_size(rustfs_service, rustfs_bucket_name, object_name) > 0
finally:
storage_registry.clear()
await asyncpg_async_driver.execute(f"DROP TABLE IF EXISTS {source_table} CASCADE")
Expand Down
21 changes: 8 additions & 13 deletions tests/integration/adapters/duckdb/test_storage_bridge.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Storage bridge integration tests for DuckDB using MinIO."""
"""Storage bridge integration tests for DuckDB using RustFS."""

from typing import TYPE_CHECKING

Expand All @@ -7,11 +7,11 @@
from sqlspec.adapters.duckdb import DuckDBDriver
from sqlspec.storage.registry import storage_registry
from sqlspec.typing import FSSPEC_INSTALLED, PYARROW_INSTALLED
from tests.integration.adapters._storage_bridge_helpers import register_minio_alias
from tests.fixtures.rustfs import rustfs_object_size
from tests.integration.adapters._storage_bridge_helpers import register_rustfs_alias

if TYPE_CHECKING: # pragma: no cover
from minio import Minio
from pytest_databases.docker.minio import MinioService
from pytest_databases.docker.rustfs import RustfsService

pytestmark = [
pytest.mark.xdist_group("storage"),
Expand All @@ -20,18 +20,15 @@
]


def test_duckdb_storage_bridge_with_minio(
duckdb_basic_session: DuckDBDriver,
minio_service: "MinioService",
minio_client: "Minio",
minio_default_bucket_name: str,
def test_duckdb_storage_bridge_with_rustfs(
duckdb_basic_session: DuckDBDriver, rustfs_service: "RustfsService", rustfs_bucket_name: str
) -> None:
alias = "storage_bridge_duckdb"
destination_path = "alias://storage_bridge_duckdb/duckdb/export.parquet"

storage_registry.clear()
try:
prefix = register_minio_alias(alias, minio_service, minio_default_bucket_name)
prefix = register_rustfs_alias(alias, rustfs_service, rustfs_bucket_name)

duckdb_basic_session.execute("DROP TABLE IF EXISTS storage_bridge_duckdb_source")
duckdb_basic_session.execute("DROP TABLE IF EXISTS storage_bridge_duckdb_target")
Expand Down Expand Up @@ -60,9 +57,7 @@ def test_duckdb_storage_bridge_with_minio(
assert rows == [(1, "alpha"), (2, "beta"), (3, "gamma")]

object_name = f"{prefix}/duckdb/export.parquet"
stat = minio_client.stat_object(bucket_name=minio_default_bucket_name, object_name=object_name)
object_size = stat.size if stat.size is not None else 0
assert object_size > 0
assert rustfs_object_size(rustfs_service, rustfs_bucket_name, object_name) > 0
finally:
storage_registry.clear()
duckdb_basic_session.execute("DROP TABLE IF EXISTS storage_bridge_duckdb_source")
Expand Down
16 changes: 7 additions & 9 deletions tests/integration/adapters/psqlpy/test_storage_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from sqlspec.adapters.psqlpy import PsqlpyDriver
from sqlspec.storage.registry import storage_registry
from sqlspec.typing import FSSPEC_INSTALLED, PYARROW_INSTALLED
from tests.integration.adapters._storage_bridge_helpers import register_minio_alias
from tests.fixtures.rustfs import rustfs_object_size
from tests.integration.adapters._storage_bridge_helpers import register_rustfs_alias

if TYPE_CHECKING: # pragma: no cover
from minio import Minio
from pytest_databases.docker.minio import MinioService
from pytest_databases.docker.rustfs import RustfsService

pytestmark = [
pytest.mark.xdist_group("postgres"),
Expand All @@ -21,8 +21,8 @@


@pytest.mark.anyio
async def test_psqlpy_storage_bridge_with_minio(
psqlpy_driver: PsqlpyDriver, minio_service: "MinioService", minio_client: "Minio", minio_default_bucket_name: str
async def test_psqlpy_storage_bridge_with_rustfs(
psqlpy_driver: PsqlpyDriver, rustfs_service: "RustfsService", rustfs_bucket_name: str
) -> None:
alias = "storage_bridge_psqlpy"
destination = f"alias://{alias}/psqlpy/export.parquet"
Expand All @@ -31,7 +31,7 @@ async def test_psqlpy_storage_bridge_with_minio(

storage_registry.clear()
try:
prefix = register_minio_alias(alias, minio_service, minio_default_bucket_name)
prefix = register_rustfs_alias(alias, rustfs_service, rustfs_bucket_name)

await psqlpy_driver.execute(f"DROP TABLE IF EXISTS {source_table}")
await psqlpy_driver.execute(f"DROP TABLE IF EXISTS {target_table}")
Expand All @@ -54,9 +54,7 @@ async def test_psqlpy_storage_bridge_with_minio(
assert rows == [{"id": 1, "label": "delta"}, {"id": 2, "label": "omega"}, {"id": 3, "label": "zeta"}]

object_name = f"{prefix}/psqlpy/export.parquet"
stat = minio_client.stat_object(bucket_name=minio_default_bucket_name, object_name=object_name)
object_size = stat.size if stat.size is not None else 0
assert object_size > 0
assert rustfs_object_size(rustfs_service, rustfs_bucket_name, object_name) > 0
finally:
storage_registry.clear()
await psqlpy_driver.execute(f"DROP TABLE IF EXISTS {source_table}")
Expand Down
Loading
Loading