Skip to content

Commit

Permalink
feat(containers): add redis (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
sylvainmouquet authored Oct 20, 2024
1 parent d5157f1 commit 885b4b2
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ PyDocks is a Python library that provides a set of pytest fixtures for running t

Key features include:
- Easy integration with pytest
- Support for PostgreSQL, Hashicorp Vault containers
- Support for PostgreSQL, Hashicorp Vault containers, Redis
- Automatic container cleanup
- Configurable container settings
- Reusable session-scoped containers for improved test performance
Expand Down
9 changes: 9 additions & 0 deletions pydocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"vault_clean_all_containers",
"vault_container",
"vault_container_session",
"redis_clean_all_containers",
"redis_container",
"redis_container_session",
)

# pytest_plugins = ["pydocks.conftest"]
Expand All @@ -23,3 +26,9 @@
vault_container,
vault_container_session,
)

from pydocks.redis import (
redis_clean_all_containers,
redis_container,
redis_container_session,
)
1 change: 0 additions & 1 deletion pydocks/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

@pytest.fixture(scope="session", autouse=True)
def docker():

if "DOCKER_SOCK" in os.environ:
yield DockerClient(host=os.environ["DOCKER_SOCK"])
elif "CI" in os.environ:
Expand Down
100 changes: 100 additions & 0 deletions pydocks/redis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import pytest
import os


import pytest_asyncio
from python_on_whales import docker as libdocker
from reattempt import reattempt
import logging
import uuid

from pydocks.plugin import (
clean_containers,
socket_test_connection,
wait_and_run_container,
)


logger = logging.getLogger("pydocks")
logger.addHandler(logging.NullHandler())


# https://hub.docker.com/_/redis/tags
TEST_REDIS_DOCKER_IMAGE: str = "docker.io/redis:7.4.1"


@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def redis_clean_all_containers(docker):
container_name: str = "test-redis"
# clean before

await clean_containers(docker, container_name)
yield
# clean after
await clean_containers(docker, container_name)


@pytest.fixture(scope="function")
async def redis_container(docker: libdocker, mocker): # type: ignore
mocker.patch(
"logging.exception",
lambda *args, **kwargs: logger.warning(f"Exception raised {args}"),
)

container_name = f"test-redis-{uuid.uuid4()}"
# optional : await clean_containers(docker, container_name)

async for container in setup_redis_container(docker, container_name):
yield container


@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def redis_container_session(docker: libdocker, session_mocker): # type: ignore
session_mocker.patch(
"logging.exception",
lambda *args, **kwargs: logger.warning(f"Exception raised {args}"),
)

await clean_containers(docker, "test-redis-session")

container_name = f"test-redis-session-{uuid.uuid4()}"

async for container in setup_redis_container(docker, container_name):
yield container


async def setup_redis_container(docker: libdocker, container_name): # type: ignore
redis_image = (
TEST_REDIS_DOCKER_IMAGE
if "TEST_REDIS_DOCKER_IMAGE" not in os.environ
else os.environ["TEST_REDIS_DOCKER_IMAGE"]
)
logger.debug(f"pull docker image : {redis_image}")

def run_container(container_name: str):
return docker.run(
image=redis_image,
name=container_name,
detach=True,
publish=[(6379, 6379)],
expose=[6379],
)

# Select the container with the given name if exists, else create a new one
containers = docker.ps(all=True, filters={"name": f"^{container_name}$"})
if containers and len(containers) > 0:
container = containers[0] # type: ignore
logger.debug(f"found existing container: {container_name}")
else:
logger.debug(f"no existing container found, creating new one: {container_name}")
container = run_container(container_name)

await redis_test_connection()

async for instance in wait_and_run_container(docker, container, container_name):
yield instance


@reattempt(max_retries=30, min_time=0.1, max_time=0.5)
async def redis_test_connection():
await socket_test_connection("127.0.0.1", 6379)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ authors = [{name = "Sylvain Mouquet", email = "[email protected]"}]
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"redis>=5.1.1",
]
license = { text = "MIT" }
url = "https://github.com/sylvainmouquet/pydocks"
Expand Down
75 changes: 75 additions & 0 deletions test/test_redis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import pytest
import os
import redis.asyncio as redis
from loguru import logger
import pytest_asyncio


@pytest_asyncio.fixture(scope="session", loop_scope="session", autouse=True)
async def begin_clean_all_containers(redis_clean_all_containers):
logger.info("Begin - clean all containers")


@pytest.mark.asyncio
async def test_redis_default_version(redis_container):
container_env_dict = dict(env.split("=") for env in redis_container.config.env)

assert container_env_dict["REDIS_VERSION"] == "7.4.1"


@pytest.fixture
def custom_redis_version():
os.environ["TEST_REDIS_DOCKER_IMAGE"] = "docker.io/redis:7.4.0"
yield
del os.environ["TEST_REDIS_DOCKER_IMAGE"]


@pytest.mark.asyncio
async def test_redis_custom_version(custom_redis_version, redis_container):
container_env_dict = dict(env.split("=") for env in redis_container.config.env)

assert container_env_dict["REDIS_VERSION"] == "7.4.0"


@pytest.mark.asyncio
async def test_redis_execute_command(redis_container):
# Execute Redis CLI command
result = redis_container.execute(["redis-cli", "PING"])
assert result.strip() == "PONG"

# Set a key-value pair
set_result = redis_container.execute(["redis-cli", "SET", "test_key", "test_value"])
assert set_result.strip() == "OK"

# Get the value
get_result = redis_container.execute(["redis-cli", "GET", "test_key"])
assert get_result.strip() == "test_value"

# Delete the key
del_result = redis_container.execute(["redis-cli", "DEL", "test_key"])
assert del_result.strip() == "1"

# Verify key is deleted
get_deleted = redis_container.execute(["redis-cli", "GET", "test_key"])
assert get_deleted.strip() == ""

async with await redis.from_url(
"redis://localhost:6379", encoding="utf8"
) as rredis:
# Flush all existing data
await rredis.flushall()

# Set a key-value pair
await rredis.set("test_key", "test_value")

# Get the value
value = await rredis.get("test_key")
assert value == b"test_value"

# Delete the key
deleted = await rredis.delete("test_key")
assert deleted == 1

# Verify key is deleted
deleted_value = await rredis.get("test_key")
assert deleted_value is None
16 changes: 16 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 885b4b2

Please sign in to comment.