From 435c8e9e6570aaaca9c1d1995ecfd4ee9b72e2ed Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 24 May 2026 16:06:14 +0000 Subject: [PATCH 1/2] feat: move Redis family to clientless validation --- docs/supported-databases/redis.rst | 31 ++++-- pyproject.toml | 7 +- src/pytest_databases/docker/redis.py | 88 ++++++++++++--- tests/test_redis.py | 131 +++++++++++++--------- uv.lock | 160 --------------------------- 5 files changed, 173 insertions(+), 244 deletions(-) diff --git a/docs/supported-databases/redis.rst b/docs/supported-databases/redis.rst index 60940041..01289e06 100644 --- a/docs/supported-databases/redis.rst +++ b/docs/supported-databases/redis.rst @@ -1,14 +1,16 @@ Redis ===== -Integration with `Redis `_ using the `Redis Docker Image `_, Snap's `Key DB` or `Dragonfly `_. +Integration with `Redis `_ using the `Redis Docker Image `_, `KeyDB `_, or `Dragonfly `_. KeyDB and Dragonfly are wire-compatible with Redis, so a ``redis.Redis`` client works against all three services. Installation ------------ .. code-block:: bash - pip install pytest-databases[redis] + pip install pytest-databases[redis] redis + +The ``redis`` package is no longer pulled by the ``pytest-databases[redis]`` extra — the fixtures validate the container via ``redis-cli`` invoked from a short-lived sidecar — so install your own client (``redis``, ``redis[hiredis]``, etc.) alongside ``pytest-databases``. Usage Example ------------- @@ -21,18 +23,24 @@ Usage Example pytest_plugins = ["pytest_databases.docker.redis"] - def test(redis_service: RedisService) -> None: + def test_redis(redis_service: RedisService) -> None: client = redis.Redis( host=redis_service.host, port=redis_service.port, - db=redis_service.db + db=redis_service.db, ) client.set("test_key", "test_value") assert client.get("test_key") == b"test_value" - def test(redis_connection: redis.Redis) -> None: - redis_connection.set("test_key", "test_value") - assert redis_connection.get("test_key") == b"test_value" + def test_keydb(keydb_service: RedisService) -> None: + client = redis.Redis(host=keydb_service.host, port=keydb_service.port, db=keydb_service.db) + client.set("test_key", "test_value") + assert client.get("test_key") == b"test_value" + + def test_dragonfly(dragonfly_service: RedisService) -> None: + client = redis.Redis(host=dragonfly_service.host, port=dragonfly_service.port, db=dragonfly_service.db) + client.set("test_key", "test_value") + assert client.get("test_key") == b"test_value" Available Fixtures ------------------ @@ -40,13 +48,12 @@ Available Fixtures * ``redis_port``: The port number for the Redis service. * ``redis_host``: The host name for the Redis service. * ``redis_image``: The Docker image to use for Redis. -* ``redis_service``: A fixture that provides a Redis service. -* ``redis_connection``: A fixture that provides a Redis connection. +* ``redis_service``: A fixture that provides a ``RedisService`` (``host``, ``port``, ``container``, ``db``). -The following version-specific fixtures are also available: +The following compatible-service fixtures are also available: -* ``dragonflydb_port``, ``dragonflydb_host``, ``dragonflydb_image``, ``dragonflydb_service``, ``dragonflydb_connection``: Latest Available DragonflyDB Docker image. -* ``keydb_port``, ``keydb_host``, ``keydb_image``, ``keydb_service``, ``keydb_connection``: Latest Available KeyDB Docker image. +* ``dragonfly_port``, ``dragonfly_host``, ``dragonfly_image``, ``dragonfly_service``: Latest available DragonflyDB Docker image. +* ``keydb_port``, ``keydb_host``, ``keydb_image``, ``keydb_service``: Latest available KeyDB Docker image. Service API ----------- diff --git a/pyproject.toml b/pyproject.toml index 830b7d5c..a68fecd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,18 +62,18 @@ Source = "https://github.com/litestar-org/pytest-databases" azure-storage = ["azure-storage-blob"] bigquery = ["google-cloud-bigquery"] cockroachdb = [] -dragonfly = ["redis"] +dragonfly = [] elasticsearch7 = [] elasticsearch8 = [] gizmosql = ["adbc-driver-flightsql", "pyarrow"] -keydb = ["redis"] +keydb = [] mariadb = [] mongodb = [] mssql = [] mysql = [] oracle = [] postgres = ["psycopg>=3"] -redis = ["redis"] +redis = [] spanner = [] valkey = [] yugabyte = [] @@ -115,7 +115,6 @@ lint = [ "types-decorator", "types-pyyaml", "types-docutils", - "types-redis", "types-pymysql", "slotscheck", ] diff --git a/src/pytest_databases/docker/redis.py b/src/pytest_databases/docker/redis.py index 0aee10ca..9c91eb62 100644 --- a/src/pytest_databases/docker/redis.py +++ b/src/pytest_databases/docker/redis.py @@ -4,18 +4,72 @@ from typing import TYPE_CHECKING import pytest -from redis import Redis -from redis.exceptions import ConnectionError as RedisConnectionError +from docker.errors import ContainerError 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 +REDIS_PROBE_IMAGE = "redis:latest" + + +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_redis_cli(container: Container, *args: str, db: int = 0) -> tuple[int, bytes]: + result = container.exec_run([ + "redis-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) + + +def _probe_redis_endpoint( + docker_service: DockerService, + service: ServiceContainer, + *args: str, + db: int = 0, + probe_image: str = REDIS_PROBE_IMAGE, +) -> tuple[int, bytes]: + try: + output = docker_service._client.containers.run( + image=probe_image, + command=[ + "redis-cli", + "-h", + service.host, + "-p", + str(service.port), + "-n", + str(db), + *args, + ], + network_mode="host", + remove=True, + ) + except ContainerError as exc: + return exc.exit_status, _output_to_bytes(exc.stderr) if exc.stderr else str(exc).encode() + return 0, _output_to_bytes(output) + + @dataclasses.dataclass class RedisService(ServiceContainer): db: int @@ -26,16 +80,6 @@ def xdist_redis_isolation_level() -> XdistIsolationLevel: return "database" -def redis_responsive(service_container: ServiceContainer) -> bool: - client = Redis(host=service_container.host, port=service_container.port) - try: - return client.ping() - except (ConnectionError, RedisConnectionError): - return False - finally: - client.close() - - @pytest.fixture(autouse=False, scope="session") def redis_port(redis_service: RedisService) -> int: return redis_service.port @@ -66,9 +110,13 @@ def redis_service( else: name += f"_{worker_num + 1}" + def _responsive(_service: ServiceContainer) -> bool: + exit_code, output = _probe_redis_endpoint(docker_service, _service, "PING") + return exit_code == 0 and output.strip().endswith(b"PONG") + with docker_service.run( redis_image, - check=redis_responsive, + check=_responsive, container_port=6379, name=name, transient=xdist_redis_isolation_level == "server", @@ -101,9 +149,13 @@ def dragonfly_service( else: name += f"_{worker_num + 1}" + def _responsive(_service: ServiceContainer) -> bool: + exit_code, output = _probe_redis_endpoint(docker_service, _service, "PING") + return exit_code == 0 and output.strip().endswith(b"PONG") + with docker_service.run( dragonfly_image, - check=redis_responsive, + check=_responsive, container_port=6379, name=name, transient=xdist_redis_isolation_level == "server", @@ -146,9 +198,13 @@ def keydb_service( else: name += f"_{worker_num + 1}" + def _responsive(_service: ServiceContainer) -> bool: + exit_code, output = _probe_redis_endpoint(docker_service, _service, "PING") + return exit_code == 0 and output.strip().endswith(b"PONG") + with docker_service.run( keydb_image, - check=redis_responsive, + check=_responsive, container_port=6379, name=name, transient=xdist_redis_isolation_level == "server", diff --git a/tests/test_redis.py b/tests/test_redis.py index 49cc5396..dff60b98 100644 --- a/tests/test_redis.py +++ b/tests/test_redis.py @@ -3,6 +3,47 @@ import pytest +def test_plugin_imports_without_redis(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 == "redis" or name.startswith("redis."): + raise ModuleNotFoundError(name) + return original_import(name, globals, locals, fromlist, level) + + builtins.__import__ = blocked_import + try: + import pytest_databases.docker.redis + finally: + builtins.__import__ = original_import + """) + + result = pytester.runpytest_subprocess("-p", "pytest_databases", "-vv") + result.assert_outcomes(passed=1) + + +REDIS_TEST_HELPERS = """ +def run_redis_cli(docker_service, service, *args, db=None): + if db is None: + db = service.db + output = docker_service._client.containers.run( + image="redis:latest", + command=["redis-cli", "-h", service.host, "-p", str(service.port), "-n", str(db), *args], + network_mode="host", + remove=True, + ) + 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("redis:latest", id="redis"), @@ -29,20 +70,19 @@ def redis_compatible_service(request: pytest.FixtureRequest) -> str: def test_redis_image(pytester: pytest.Pytester, redis_image_name: str) -> None: pytester.makepyfile(f""" import pytest -import redis from pytest_databases.docker.redis import RedisService -from pytest_databases.helpers import get_xdist_worker_num -pytest_plugins = [ - "pytest_databases.docker.redis", -] +pytest_plugins = ["pytest_databases.docker.redis"] + @pytest.fixture(scope="session") def redis_image(): return "{redis_image_name}" -def test_redis_service(redis_service: RedisService) -> None: - assert redis.Redis(host=redis_service.host, port=redis_service.port).ping() +{REDIS_TEST_HELPERS} + +def test_redis_service(docker_service, redis_service: RedisService) -> None: + assert run_redis_cli(docker_service, redis_service, "PING") == "PONG" """) result = pytester.runpytest_subprocess("-p", "pytest_databases") result.assert_outcomes(passed=1) @@ -50,17 +90,14 @@ def test_redis_service(redis_service: RedisService) -> None: def test_default_no_xdist(pytester: pytest.Pytester, redis_compatible_service: str) -> None: pytester.makepyfile(f""" -import pytest -import redis from pytest_databases.docker.redis import RedisService -from pytest_databases.helpers import get_xdist_worker_num -pytest_plugins = [ - "pytest_databases.docker.redis", -] +pytest_plugins = ["pytest_databases.docker.redis"] + +{REDIS_TEST_HELPERS} -def test_redis_service({redis_compatible_service}: RedisService) -> None: - assert redis.Redis(host={redis_compatible_service}.host, port={redis_compatible_service}.port).ping() +def test_redis_service(docker_service, {redis_compatible_service}: RedisService) -> None: + assert run_redis_cli(docker_service, {redis_compatible_service}, "PING") == "PONG" """) result = pytester.runpytest_subprocess("-p", "pytest_databases") result.assert_outcomes(passed=1) @@ -68,38 +105,32 @@ def test_redis_service({redis_compatible_service}: RedisService) -> None: def test_xdist_isolate_database(pytester: pytest.Pytester, redis_compatible_service: str) -> None: pytester.makepyfile(f""" -import pytest -import redis from pytest_databases.docker.redis import RedisService from pytest_databases.helpers import get_xdist_worker_num -pytest_plugins = [ - "pytest_databases.docker.redis", -] +pytest_plugins = ["pytest_databases.docker.redis"] + +{REDIS_TEST_HELPERS} -def test_one({redis_compatible_service}: RedisService) -> None: - client = redis.Redis(host={redis_compatible_service}.host, port={redis_compatible_service}.port, db={redis_compatible_service}.db) +def test_one(docker_service, {redis_compatible_service}: RedisService) -> None: assert {redis_compatible_service}.db == get_xdist_worker_num() - assert not client.get("one") - client.set("one", "0") - assert client.get("one") == b"0" + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "one") in ("", "(nil)") + assert run_redis_cli(docker_service, {redis_compatible_service}, "SET", "one", "0") == "OK" + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "one") == "0" -def test_two({redis_compatible_service}: RedisService) -> None: - client = redis.Redis(host={redis_compatible_service}.host, port={redis_compatible_service}.port, db={redis_compatible_service}.db) +def test_two(docker_service, {redis_compatible_service}: RedisService) -> None: assert {redis_compatible_service}.db == get_xdist_worker_num() - assert not client.get("one") - client.set("one", "1") - assert client.get("one") == b"1" - - -def test_use_same_db({redis_compatible_service}: RedisService) -> None: - client_0 = redis.Redis(host={redis_compatible_service}.host, port={redis_compatible_service}.port, db=0) - client_1 = redis.Redis(host={redis_compatible_service}.host, port={redis_compatible_service}.port, db=1) - client_0.set("foo", "0") - client_1.set("foo", "1") - assert client_0.get("foo") == b"0" - assert client_1.get("foo") == b"1" + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "one") in ("", "(nil)") + assert run_redis_cli(docker_service, {redis_compatible_service}, "SET", "one", "1") == "OK" + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "one") == "1" + + +def test_use_same_db(docker_service, {redis_compatible_service}: RedisService) -> None: + assert run_redis_cli(docker_service, {redis_compatible_service}, "SET", "foo", "0", db=0) == "OK" + assert run_redis_cli(docker_service, {redis_compatible_service}, "SET", "foo", "1", db=1) == "OK" + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "foo", db=0) == "0" + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "foo", db=1) == "1" """) result = pytester.runpytest_subprocess("-p", "pytest_databases", "-n", "2") result.assert_outcomes(passed=3) @@ -108,31 +139,27 @@ def test_use_same_db({redis_compatible_service}: RedisService) -> None: def test_xdist_isolate_server(pytester: pytest.Pytester, redis_compatible_service: str) -> None: pytester.makepyfile(f""" import pytest -import redis from pytest_databases.docker.redis import RedisService -from pytest_databases.helpers import get_xdist_worker_num -pytest_plugins = [ - "pytest_databases.docker.redis", -] +pytest_plugins = ["pytest_databases.docker.redis"] + @pytest.fixture(scope="session") def xdist_redis_isolation_level(): return "server" +{REDIS_TEST_HELPERS} -def test_one({redis_compatible_service}: RedisService) -> None: - client = redis.Redis(host={redis_compatible_service}.host, port={redis_compatible_service}.port, db={redis_compatible_service}.db) - assert not client.get("one") - client.set("one", "1") +def test_one(docker_service, {redis_compatible_service}: RedisService) -> None: assert {redis_compatible_service}.db == 0 + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "one") in ("", "(nil)") + assert run_redis_cli(docker_service, {redis_compatible_service}, "SET", "one", "1") == "OK" -def test_two({redis_compatible_service}: RedisService) -> None: - client = redis.Redis(host={redis_compatible_service}.host, port={redis_compatible_service}.port, db={redis_compatible_service}.db) - assert not client.get("one") - client.set("one", "1") +def test_two(docker_service, {redis_compatible_service}: RedisService) -> None: assert {redis_compatible_service}.db == 0 + assert run_redis_cli(docker_service, {redis_compatible_service}, "GET", "one") in ("", "(nil)") + assert run_redis_cli(docker_service, {redis_compatible_service}, "SET", "one", "1") == "OK" """) result = pytester.runpytest_subprocess("-p", "pytest_databases", "-n", "2") result.assert_outcomes(passed=2) diff --git a/uv.lock b/uv.lock index 3ee06df2..f78475c7 100644 --- a/uv.lock +++ b/uv.lock @@ -308,15 +308,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, ] -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, -] - [[package]] name = "auto-pytabs" version = "0.5.0" @@ -3772,29 +3763,16 @@ azure-storage = [ bigquery = [ { name = "google-cloud-bigquery" }, ] -dragonfly = [ - { name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "redis", version = "7.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] gizmosql = [ { name = "adbc-driver-flightsql", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "adbc-driver-flightsql", version = "1.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyarrow", version = "21.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pyarrow", version = "24.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -keydb = [ - { name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "redis", version = "7.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] postgres = [ { name = "psycopg", version = "3.2.13", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "psycopg", version = "3.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -redis = [ - { name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "redis", version = "7.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] - [package.dev-dependencies] build = [ { name = "bump-my-version" }, @@ -3858,7 +3836,6 @@ dev = [ { name = "types-pymysql", version = "1.1.0.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "types-pyyaml", version = "6.0.12.20250915", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "types-pyyaml", version = "6.0.12.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "types-redis" }, { name = "types-six", version = "1.17.0.20251009", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "types-six", version = "1.17.0.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] @@ -3912,7 +3889,6 @@ lint = [ { name = "types-pymysql", version = "1.1.0.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "types-pyyaml", version = "6.0.12.20250915", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "types-pyyaml", version = "6.0.12.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "types-redis" }, { name = "types-six", version = "1.17.0.20251009", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "types-six", version = "1.17.0.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] @@ -3941,9 +3917,6 @@ requires-dist = [ { name = "psycopg", marker = "extra == 'postgres'", specifier = ">=3" }, { name = "pyarrow", marker = "extra == 'gizmosql'" }, { name = "pytest" }, - { name = "redis", marker = "extra == 'dragonfly'" }, - { name = "redis", marker = "extra == 'keydb'" }, - { name = "redis", marker = "extra == 'redis'" }, ] provides-extras = ["azure-storage", "bigquery", "cockroachdb", "dragonfly", "elasticsearch7", "elasticsearch8", "gizmosql", "keydb", "mariadb", "mongodb", "mssql", "mysql", "oracle", "postgres", "redis", "spanner", "valkey", "yugabyte"] @@ -3986,7 +3959,6 @@ dev = [ { name = "types-docutils" }, { name = "types-pymysql" }, { name = "types-pyyaml" }, - { name = "types-redis" }, { name = "types-six" }, ] doc = [ @@ -4018,7 +3990,6 @@ lint = [ { name = "types-docutils" }, { name = "types-pymysql" }, { name = "types-pyyaml" }, - { name = "types-redis" }, { name = "types-six" }, ] test = [ @@ -4242,42 +4213,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, ] -[[package]] -name = "redis" -version = "7.0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "async-timeout", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/8f/f125feec0b958e8d22c8f0b492b30b1991d9499a4315dfde466cf4289edc/redis-7.0.1.tar.gz", hash = "sha256:c949df947dca995dc68fdf5a7863950bf6df24f8d6022394585acc98e81624f1", size = 4755322, upload-time = "2025-10-27T14:34:00.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/97/9f22a33c475cda519f20aba6babb340fb2f2254a02fb947816960d1e669a/redis-7.0.1-py3-none-any.whl", hash = "sha256:4977af3c7d67f8f0eb8b6fec0dafc9605db9343142f634041fb0235f67c0588a", size = 339938, upload-time = "2025-10-27T14:33:58.553Z" }, -] - -[[package]] -name = "redis" -version = "7.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.15'", - "python_full_version == '3.14.*'", - "python_full_version == '3.13.*'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] -dependencies = [ - { name = "async-timeout", marker = "python_full_version >= '3.10' and python_full_version < '3.11.3'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7b/7f/3759b1d0d72b7c92f0d70ffd9dc962b7b7b5ee74e135f9d7d8ab06b8a318/redis-7.4.0.tar.gz", hash = "sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad", size = 4943913, upload-time = "2026-03-24T09:14:37.53Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" }, -] - [[package]] name = "requests" version = "2.32.5" @@ -5440,42 +5375,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, ] -[[package]] -name = "types-cffi" -version = "1.17.0.20250915" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -dependencies = [ - { name = "types-setuptools", version = "81.0.0.20260209", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/98/ea454cea03e5f351323af6a482c65924f3c26c515efd9090dede58f2b4b6/types_cffi-1.17.0.20250915.tar.gz", hash = "sha256:4362e20368f78dabd5c56bca8004752cc890e07a71605d9e0d9e069dbaac8c06", size = 17229, upload-time = "2025-09-15T03:01:25.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/ec/092f2b74b49ec4855cdb53050deb9699f7105b8fda6fe034c0781b8687f3/types_cffi-1.17.0.20250915-py3-none-any.whl", hash = "sha256:cef4af1116c83359c11bb4269283c50f0688e9fc1d7f0eeb390f3661546da52c", size = 20112, upload-time = "2025-09-15T03:01:24.187Z" }, -] - -[[package]] -name = "types-cffi" -version = "2.0.0.20260518" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.15'", - "python_full_version == '3.14.*'", - "python_full_version == '3.13.*'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] -dependencies = [ - { name = "types-setuptools", version = "82.0.0.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/0b/b352742758a6054d1053783887bf8cfb739deda1102fda8722294bdc01f7/types_cffi-2.0.0.20260518.tar.gz", hash = "sha256:f9707e66c13454789a58f8843d1ded4a66f1e9c8b10bd24d5eb5e0f25c0c5472", size = 17790, upload-time = "2026-05-18T06:06:50.672Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/44/d3b4aafa20a3f76384ba19a513d39272add13746dcfe0409d8d4974fd464/types_cffi-2.0.0.20260518-py3-none-any.whl", hash = "sha256:5b68a215a95d0eac4203b58e766ff7fe40c2e091b1fa1a9e54111f04cc560084", size = 20198, upload-time = "2026-05-18T06:06:49.83Z" }, -] - [[package]] name = "types-click" version = "7.1.8" @@ -5575,21 +5474,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/5a/db02b5e6633fbe49eaf4e3194bc64ec031e6436a0cfcc610cbda4f1b6a24/types_pymysql-1.1.0.20260518-py3-none-any.whl", hash = "sha256:cf697ce4e44124fc859e8e8a7f047c1dc864745c3c628b85a51b3ee01502ef98", size = 23071, upload-time = "2026-05-18T06:02:30.36Z" }, ] -[[package]] -name = "types-pyopenssl" -version = "24.1.0.20240722" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography", version = "47.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9'" }, - { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version > '3.9'" }, - { name = "types-cffi", version = "1.17.0.20250915", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "types-cffi", version = "2.0.0.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/93/29/47a346550fd2020dac9a7a6d033ea03fccb92fa47c726056618cc889745e/types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39", size = 8458, upload-time = "2024-07-22T02:32:22.558Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/05/c868a850b6fbb79c26f5f299b768ee0adc1f9816d3461dcf4287916f655b/types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54", size = 7499, upload-time = "2024-07-22T02:32:21.232Z" }, -] - [[package]] name = "types-pyyaml" version = "6.0.12.20250915" @@ -5620,50 +5504,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/a2/c01db32be2ae7d6a1689972f3c492b149ee4e164b12fdfd9f64b50888215/types_pyyaml-6.0.12.20260518-py3-none-any.whl", hash = "sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd", size = 20312, upload-time = "2026-05-18T06:01:57.368Z" }, ] -[[package]] -name = "types-redis" -version = "4.6.0.20241004" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography", version = "47.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9'" }, - { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version > '3.9'" }, - { name = "types-pyopenssl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/95/c054d3ac940e8bac4ca216470c80c26688a0e79e09f520a942bb27da3386/types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e", size = 49679, upload-time = "2024-10-04T02:43:59.224Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/82/7d25dce10aad92d2226b269bce2f85cfd843b4477cd50245d7d40ecf8f89/types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed", size = 58737, upload-time = "2024-10-04T02:43:57.968Z" }, -] - -[[package]] -name = "types-setuptools" -version = "81.0.0.20260209" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/57/f1f7992d6d7bded78d1f14dc23d59e87601920852bf10ece2325e49bacae/types_setuptools-81.0.0.20260209.tar.gz", hash = "sha256:2c2eb64499b41b672c387f6f45678a28d20a143a81b45a5c77acbfd4da0df3e1", size = 43201, upload-time = "2026-02-09T04:14:15.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/87/90c9143af95850bdaf7eb0d47c59e5c3a8b55fc5a49aca0eb7f98cb964d5/types_setuptools-81.0.0.20260209-py3-none-any.whl", hash = "sha256:4facf71e3f953f8f5ac0020cd6c1b5e493aaff0183e85830bc34870b6abf8475", size = 64194, upload-time = "2026-02-09T04:14:14.278Z" }, -] - -[[package]] -name = "types-setuptools" -version = "82.0.0.20260518" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.15'", - "python_full_version == '3.14.*'", - "python_full_version == '3.13.*'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/38/bc/73c2c27e047e42f114ac50fb3bdef986c56cbdb68096f8690eeafb839a93/types_setuptools-82.0.0.20260518.tar.gz", hash = "sha256:3b743cfe63d0981ea4c15b90710fc1ed41e3464a537d51e705be514e891c1d07", size = 44999, upload-time = "2026-05-18T06:02:55.642Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/8f/d5e2d493f09a7a98c95619edda1cb37cee377626c0a869d53274c26f2858/types_setuptools-82.0.0.20260518-py3-none-any.whl", hash = "sha256:31c04a62b57a653a5021caf191be0f10f70df890f813b51f02bab3969d300f20", size = 68444, upload-time = "2026-05-18T06:02:54.582Z" }, -] - [[package]] name = "types-six" version = "1.17.0.20251009" From cc7e85a7111ad8f4b3350d4187188e271ecf8436 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Wed, 22 Jul 2026 03:51:30 +0000 Subject: [PATCH 2/2] ci: include Redis in compatibility lane --- .github/ci/provider-groups.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ci/provider-groups.json b/.github/ci/provider-groups.json index 63330e1a..64c635c0 100644 --- a/.github/ci/provider-groups.json +++ b/.github/ci/provider-groups.json @@ -27,6 +27,7 @@ "tests/test_elasticsearch.py::test_plugin_imports_without_elasticsearch_clients", "tests/test_mongodb.py::test_plugin_imports_without_pymongo", "tests/test_mssql.py::test_plugin_imports_without_pymssql", + "tests/test_redis.py::test_plugin_imports_without_redis", "tests/test_spanner.py::test_plugin_imports_without_google_cloud_spanner", "tests/test_valkey.py::test_plugin_imports_without_valkey" ],