diff --git a/docs/supported-databases/valkey.rst b/docs/supported-databases/valkey.rst
index 37a0abc..639683a 100644
--- a/docs/supported-databases/valkey.rst
+++ b/docs/supported-databases/valkey.rst
@@ -1,14 +1,16 @@
Valkey
======
-Integration with `Valkey `_ using the `Valkey Docker Image `_
+Integration with `Valkey `_ using the `Valkey Docker Image `_.
Installation
------------
.. code-block:: bash
- pip install pytest-databases[valkey]
+ pip install pytest-databases[valkey] valkey
+
+The ``valkey`` Python client is no longer pulled by ``pytest-databases[valkey]`` — the fixture validates the container via the bundled ``valkey-cli`` invoked through ``container.exec_run`` — so install your own client alongside ``pytest-databases``.
Usage Example
-------------
@@ -25,23 +27,18 @@ Usage Example
client = Valkey(
host=valkey_service.host,
port=valkey_service.port,
- db=valkey_service.db
+ db=valkey_service.db,
)
client.set("test_key", "test_value")
assert client.get("test_key") == b"test_value"
- def test(valkey_connection: Valkey) -> None:
- valkey_connection.set("test_key", "test_value")
- assert valkey_connection.get("test_key") == b"test_value"
-
Available Fixtures
------------------
* ``valkey_port``: The port number for the Valkey service.
* ``valkey_host``: The host name for the Valkey service.
* ``valkey_image``: The Docker image to use for Valkey.
-* ``valkey_service``: A fixture that provides a Valkey service.
-* ``valkey_connection``: A fixture that provides a Valkey connection.
+* ``valkey_service``: A fixture that provides a ``ValkeyService`` (``host``, ``port``, ``container``, ``db``).
Service API
-----------
diff --git a/pyproject.toml b/pyproject.toml
index af70e1f..31e33f5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -75,7 +75,7 @@ oracle = ["oracledb"]
postgres = ["psycopg>=3"]
redis = ["redis"]
spanner = ["google-cloud-spanner"]
-valkey = ["valkey"]
+valkey = []
yugabyte = []
[dependency-groups]
@@ -385,7 +385,9 @@ line-length = 120
[tool.pytest.ini_options]
addopts = "--doctest-glob='*.md' --dist=loadgroup"
-cdist-group-steal = "3:10"
+# Keep the middle shard non-empty when file justification moves a large test file
+# across cdist chunk boundaries. pytest-cdist 0.3.x accepts only one steal entry.
+cdist-group-steal = "2:10"
cdist-justify-items = "file"
filterwarnings = [
"ignore::DeprecationWarning:pkg_resources",
diff --git a/src/pytest_databases/docker/valkey.py b/src/pytest_databases/docker/valkey.py
index 2604fbc..53f6bb3 100644
--- a/src/pytest_databases/docker/valkey.py
+++ b/src/pytest_databases/docker/valkey.py
@@ -1,21 +1,43 @@
from __future__ import annotations
import dataclasses
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING
import pytest
-from valkey import Valkey
-from valkey.exceptions import ConnectionError as ValkeyConnectionError
from pytest_databases.helpers import get_xdist_worker_num
from pytest_databases.types import ServiceContainer, XdistIsolationLevel
if TYPE_CHECKING:
- from collections.abc import Generator
+ from collections.abc import Generator, Iterator
+
+ from docker.models.containers import Container
from pytest_databases._service import DockerService
+def _output_to_bytes(output: bytes | str | Iterator[bytes]) -> bytes:
+ if isinstance(output, bytes):
+ return output
+ if isinstance(output, str):
+ return output.encode()
+ return b"".join(output)
+
+
+def _exec_valkey_cli(container: Container, *args: str, db: int = 0) -> tuple[int, bytes]:
+ result = container.exec_run([
+ "valkey-cli",
+ "-h",
+ "localhost",
+ "-p",
+ "6379",
+ "-n",
+ str(db),
+ *args,
+ ])
+ return result.exit_code if result.exit_code is not None else -1, _output_to_bytes(result.output)
+
+
@dataclasses.dataclass
class ValkeyService(ServiceContainer):
db: int
@@ -26,16 +48,6 @@ def xdist_valkey_isolation_level() -> XdistIsolationLevel:
return "database"
-def valkey_responsive(service_container: ServiceContainer) -> bool:
- client = Valkey(host=service_container.host, port=service_container.port)
- try:
- return cast("bool", client.ping())
- except (ConnectionError, ValkeyConnectionError):
- return False
- finally:
- client.close()
-
-
@pytest.fixture(autouse=False, scope="session")
def valkey_port(valkey_service: ValkeyService) -> int:
return valkey_service.port
@@ -68,9 +80,13 @@ def valkey_service(
else:
name += f"_{worker_num + 1}"
+ def _responsive(_service: ServiceContainer) -> bool:
+ exit_code, output = _exec_valkey_cli(_service.container, "PING")
+ return exit_code == 0 and output.strip().endswith(b"PONG")
+
with docker_service.run(
valkey_image,
- check=valkey_responsive,
+ check=_responsive,
container_port=6379,
name=name,
transient=xdist_valkey_isolation_level == "server",
diff --git a/tests/test_valkey.py b/tests/test_valkey.py
index 81d43f7..ce4d892 100644
--- a/tests/test_valkey.py
+++ b/tests/test_valkey.py
@@ -3,6 +3,53 @@
import pytest
+def test_plugin_imports_without_valkey(pytester: pytest.Pytester) -> None:
+ pytester.makepyfile("""
+ import builtins
+
+ def test_import() -> None:
+ original_import = builtins.__import__
+
+ def blocked_import(name, globals=None, locals=None, fromlist=(), level=0):
+ if name == "valkey" or name.startswith("valkey."):
+ raise ModuleNotFoundError(name)
+ return original_import(name, globals, locals, fromlist, level)
+
+ builtins.__import__ = blocked_import
+ try:
+ import pytest_databases.docker.valkey
+ finally:
+ builtins.__import__ = original_import
+ """)
+
+ result = pytester.runpytest_subprocess("-p", "pytest_databases", "-vv")
+ result.assert_outcomes(passed=1)
+
+
+VALKEY_TEST_HELPERS = """
+def run_valkey(service, *args, db=None):
+ if db is None:
+ db = service.db
+ result = service.container.exec_run([
+ "valkey-cli",
+ "-h",
+ "localhost",
+ "-p",
+ "6379",
+ "-n",
+ str(db),
+ *args,
+ ])
+ assert result.exit_code == 0, result.output.decode(errors="replace")
+ output = result.output
+ if isinstance(output, bytes):
+ return output.decode().strip()
+ if isinstance(output, str):
+ return output.strip()
+ return b"".join(output).decode().strip()
+"""
+
+
@pytest.fixture(params=[pytest.param("valkey_service", id="valkey")])
def valkey_compatible_service(request: pytest.FixtureRequest) -> str:
return request.param
@@ -10,17 +57,14 @@ def valkey_compatible_service(request: pytest.FixtureRequest) -> str:
def test_default_no_xdist(pytester: pytest.Pytester, valkey_compatible_service: str) -> None:
pytester.makepyfile(f"""
-import pytest
-import valkey
from pytest_databases.docker.valkey import ValkeyService
-from pytest_databases.helpers import get_xdist_worker_num
-pytest_plugins = [
- "pytest_databases.docker.valkey",
-]
+pytest_plugins = ["pytest_databases.docker.valkey"]
+
+{VALKEY_TEST_HELPERS}
def test_valkey_service({valkey_compatible_service}: ValkeyService) -> None:
- assert valkey.Valkey.from_url("valkey://", host={valkey_compatible_service}.host, port={valkey_compatible_service}.port).ping()
+ assert run_valkey({valkey_compatible_service}, "PING") == "PONG"
""")
result = pytester.runpytest_subprocess("-p", "pytest_databases")
result.assert_outcomes(passed=1)
@@ -28,25 +72,21 @@ def test_valkey_service({valkey_compatible_service}: ValkeyService) -> None:
def test_xdist_isolate_database(pytester: pytest.Pytester, valkey_compatible_service: str) -> None:
pytester.makepyfile(f"""
-import pytest
-import valkey
from pytest_databases.docker.valkey import ValkeyService
from pytest_databases.helpers import get_xdist_worker_num
-pytest_plugins = [
- "pytest_databases.docker.valkey",
-]
+pytest_plugins = ["pytest_databases.docker.valkey"]
+
+{VALKEY_TEST_HELPERS}
def test_one({valkey_compatible_service}: ValkeyService) -> None:
- client = valkey.Valkey.from_url("valkey://", host={valkey_compatible_service}.host, port={valkey_compatible_service}.port)
- assert client.ping()
+ assert run_valkey({valkey_compatible_service}, "PING") == "PONG"
assert {valkey_compatible_service}.db == get_xdist_worker_num()
def test_two({valkey_compatible_service}: ValkeyService) -> None:
- client = valkey.Valkey.from_url("valkey://", host={valkey_compatible_service}.host, port={valkey_compatible_service}.port)
- assert not client.get("one")
- client.set("one", "1")
+ assert run_valkey({valkey_compatible_service}, "GET", "one") in ("", "(nil)")
+ assert run_valkey({valkey_compatible_service}, "SET", "one", "1") == "OK"
assert {valkey_compatible_service}.db == get_xdist_worker_num()
""")
result = pytester.runpytest_subprocess("-p", "pytest_databases", "-n", "2")
@@ -56,29 +96,25 @@ def test_two({valkey_compatible_service}: ValkeyService) -> None:
def test_xdist_isolate_server(pytester: pytest.Pytester, valkey_compatible_service: str) -> None:
pytester.makepyfile(f"""
import pytest
-import valkey
from pytest_databases.docker.valkey import ValkeyService
-from pytest_databases.helpers import get_xdist_worker_num
-pytest_plugins = [
- "pytest_databases.docker.valkey",
-]
+pytest_plugins = ["pytest_databases.docker.valkey"]
+
@pytest.fixture(scope="session")
def xdist_valkey_isolation_level():
return "server"
+{VALKEY_TEST_HELPERS}
def test_one({valkey_compatible_service}: ValkeyService) -> None:
- client = valkey.Valkey.from_url("valkey://", host={valkey_compatible_service}.host, port={valkey_compatible_service}.port)
- assert client.ping()
+ assert run_valkey({valkey_compatible_service}, "PING") == "PONG"
assert {valkey_compatible_service}.db == 0
def test_two({valkey_compatible_service}: ValkeyService) -> None:
- client = valkey.Valkey.from_url("valkey://", host={valkey_compatible_service}.host, port={valkey_compatible_service}.port)
- assert not client.get("one")
- client.set("one", "1")
+ assert run_valkey({valkey_compatible_service}, "GET", "one") in ("", "(nil)")
+ assert run_valkey({valkey_compatible_service}, "SET", "one", "1") == "OK"
assert {valkey_compatible_service}.db == 0
""")
result = pytester.runpytest_subprocess("-p", "pytest_databases", "-n", "2")
diff --git a/uv.lock b/uv.lock
index 7c7eeef..2c48e7a 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1345,7 +1345,7 @@ name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
@@ -4026,9 +4026,6 @@ redis = [
spanner = [
{ name = "google-cloud-spanner" },
]
-valkey = [
- { name = "valkey" },
-]
[package.dev-dependencies]
build = [
@@ -4183,7 +4180,6 @@ requires-dist = [
{ name = "redis", marker = "extra == 'dragonfly'" },
{ name = "redis", marker = "extra == 'keydb'" },
{ name = "redis", marker = "extra == 'redis'" },
- { name = "valkey", marker = "extra == 'valkey'" },
]
provides-extras = ["azure-storage", "bigquery", "cockroachdb", "dragonfly", "elasticsearch7", "elasticsearch8", "gizmosql", "keydb", "mariadb", "mongodb", "mssql", "mysql", "oracle", "postgres", "redis", "spanner", "valkey", "yugabyte"]
@@ -6032,18 +6028,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/15/41/ac2dfdbc1f60c7af4f994c7a335cfa7040c01642b605d65f611cecc2a1e4/uvicorn-0.47.0-py3-none-any.whl", hash = "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", size = 71301, upload-time = "2026-05-14T18:16:51.762Z" },
]
-[[package]]
-name = "valkey"
-version = "6.1.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "async-timeout", marker = "python_full_version < '3.11.3'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/c3/ee/7fd930fc712275084722ddd464a0ea296abdb997d2da396320507968daeb/valkey-6.1.1.tar.gz", hash = "sha256:5880792990c6c2b5eb604a5ed5f98f300880b6dd92d123819b66ed54bb259731", size = 4601372, upload-time = "2025-08-11T06:41:10.63Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d0/a2/252afa4da08c714460f49e943070f86a02931f99f886182765194002fe33/valkey-6.1.1-py3-none-any.whl", hash = "sha256:e2691541c6e1503b53c714ad9a35551ac9b7c0bbac93865f063dbc859a46de92", size = 259474, upload-time = "2025-08-11T06:41:08.769Z" },
-]
-
[[package]]
name = "virtualenv"
version = "21.3.3"