Skip to content

Commit c3e31a1

Browse files
feat: new container valkey (#44)
1 parent 84648f4 commit c3e31a1

6 files changed

Lines changed: 843 additions & 644 deletions

File tree

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ PyDocks is a Python library that provides a set of pytest fixtures for running t
4545

4646
Key features include:
4747
- Easy integration with pytest
48-
- Support for PostgreSQL, Hashicorp Vault containers, Redis
48+
- Support for PostgreSQL, Hashicorp Vault, Redis, Valkey, and more
4949
- Automatic container cleanup
5050
- Configurable container settings
5151
- Reusable session-scoped containers for improved test performance
@@ -98,12 +98,22 @@ async def test_reuse_postgresql_container_2_2(postgresql_container_session):
9898
# postgresql_container_session uses the same instance of container created in test_reuse_postgresql_container_1_2
9999
```
100100

101+
### Available Containers
102+
103+
PyDocks provides fixtures for the following Docker containers:
104+
105+
- **PostgreSQL**: `postgresql_container`, `postgresql_container_session`, `postgresql_clean_all_containers`
106+
- **Redis**: `redis_container`, `redis_container_session`, `redis_clean_all_containers`
107+
- **Valkey**: `valkey_container`, `valkey_container_session`, `valkey_clean_all_containers`
108+
- **Hashicorp Vault**: `vault_container`, `vault_container_session`, `vault_clean_all_containers`
109+
- **Ubuntu**: `ubuntu_container`, `ubuntu_container_session`, `ubuntu_clean_all_containers`
110+
- **Alpine**: `alpine_container`, `alpine_container_session`, `alpine_clean_all_containers`
111+
- **OpenTofu**: `opentofu_container`, `opentofu_container_session`, `opentofu_clean_all_containers`
101112

102113
## License
103114

104115
PyDocks is released under the MIT License. See the [LICENSE](LICENSE) file for more details.
105116

106117
## Contact
107118

108-
For questions, suggestions, or issues related to PyDocks, please open an issue on the GitHub repository.
109-
119+
For questions, suggestions, or issues related to PyDocks, please open an issue on the GitHub repository.

pydocks/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
"alpine_clean_all_containers",
2121
"alpine_container",
2222
"alpine_container_session",
23+
"valkey_clean_all_containers",
24+
"valkey_container",
25+
"valkey_container_session",
2326
)
2427

2528
# pytest_plugins = ["pydocks.conftest"]
@@ -58,4 +61,10 @@
5861
alpine_clean_all_containers,
5962
alpine_container,
6063
alpine_container_session,
64+
)
65+
66+
from pydocks.valkey import (
67+
valkey_clean_all_containers,
68+
valkey_container,
69+
valkey_container_session,
6170
)

pydocks/valkey.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import pytest
2+
import os
3+
4+
5+
import pytest_asyncio
6+
from python_on_whales import docker as libdocker
7+
from reattempt import reattempt
8+
import logging
9+
import uuid
10+
11+
from pydocks.plugin import (
12+
clean_containers,
13+
socket_test_connection,
14+
wait_and_run_container,
15+
)
16+
17+
18+
logger = logging.getLogger("pydocks")
19+
logger.addHandler(logging.NullHandler())
20+
21+
22+
# https://hub.docker.com/r/valkey/valkey/tags
23+
TEST_VALKEY_DOCKER_IMAGE: str = "docker.io/valkey/valkey:8.1.1"
24+
25+
26+
@pytest_asyncio.fixture(scope="session", loop_scope="session")
27+
async def valkey_clean_all_containers(docker):
28+
container_name: str = "test-valkey"
29+
# clean before
30+
31+
await clean_containers(docker, container_name)
32+
yield
33+
# clean after
34+
await clean_containers(docker, container_name)
35+
36+
37+
@pytest.fixture(scope="function")
38+
async def valkey_container(docker: libdocker, mocker): # type: ignore
39+
mocker.patch(
40+
"logging.exception",
41+
lambda *args, **kwargs: logger.warning(f"Exception raised {args}"),
42+
)
43+
44+
container_name = f"test-valkey-{uuid.uuid4()}"
45+
# optional : await clean_containers(docker, container_name)
46+
47+
async for container in setup_valkey_container(docker, container_name):
48+
yield container
49+
50+
51+
@pytest_asyncio.fixture(scope="session", loop_scope="session")
52+
async def valkey_container_session(docker: libdocker, session_mocker): # type: ignore
53+
session_mocker.patch(
54+
"logging.exception",
55+
lambda *args, **kwargs: logger.warning(f"Exception raised {args}"),
56+
)
57+
58+
await clean_containers(docker, "test-valkey-session")
59+
60+
container_name = f"test-valkey-session-{uuid.uuid4()}"
61+
62+
async for container in setup_valkey_container(docker, container_name):
63+
yield container
64+
65+
66+
async def setup_valkey_container(docker: libdocker, container_name): # type: ignore
67+
valkey_image = (
68+
TEST_VALKEY_DOCKER_IMAGE
69+
if "TEST_VALKEY_DOCKER_IMAGE" not in os.environ
70+
else os.environ["TEST_VALKEY_DOCKER_IMAGE"]
71+
)
72+
logger.debug(f"pull docker image : {valkey_image}")
73+
74+
def run_container(container_name: str):
75+
return docker.run(
76+
image=valkey_image,
77+
name=container_name,
78+
detach=True,
79+
publish=[(6380, 6379)],
80+
expose=[6380],
81+
)
82+
83+
# Select the container with the given name if exists, else create a new one
84+
containers = docker.ps(all=True, filters={"name": f"^{container_name}$"})
85+
if containers and len(containers) > 0:
86+
container = containers[0] # type: ignore
87+
logger.debug(f"found existing container: {container_name}")
88+
else:
89+
logger.debug(f"no existing container found, creating new one: {container_name}")
90+
container = run_container(container_name)
91+
92+
await valkey_test_connection()
93+
94+
async for instance in wait_and_run_container(docker, container, container_name):
95+
yield instance
96+
97+
98+
@reattempt(max_retries=30, min_time=0.1, max_time=0.5)
99+
async def valkey_test_connection():
100+
await socket_test_connection("127.0.0.1", 6380)

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ keywords = [
1616
"opentofu",
1717
"postgresql",
1818
"pydocks",
19-
"pytest",
2019
"redis",
2120
"test",
2221
"ubuntu",
22+
"valkey",
2323
"vault"
2424
]
2525
classifiers = [
@@ -70,4 +70,4 @@ pydocks = { path = "pydocks" }
7070
# asyncpg = { git = "https://github.com/MagicStack/asyncpg"}
7171

7272
[project.entry-points.pytest11]
73-
pydocks = "pydocks"
73+
pydocks = "pydocks"

tests/test_valkey.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import pytest
2+
import os
3+
import redis.asyncio as redis
4+
from loguru import logger
5+
import pytest_asyncio
6+
7+
8+
@pytest_asyncio.fixture(scope="session", loop_scope="session", autouse=True)
9+
async def begin_clean_all_containers(valkey_clean_all_containers):
10+
logger.info("Begin - clean all containers")
11+
12+
13+
@pytest.mark.asyncio
14+
async def test_valkey_default_version(valkey_container):
15+
container_env_dict = dict(env.split("=") for env in valkey_container.config.env)
16+
17+
assert "VALKEY_VERSION" in container_env_dict, "VALKEY_VERSION environment variable not found"
18+
assert container_env_dict["VALKEY_VERSION"] == "8.1.1"
19+
20+
21+
@pytest.fixture
22+
def custom_valkey_version():
23+
os.environ["TEST_VALKEY_DOCKER_IMAGE"] = "docker.io/valkey/valkey:7.2.9"
24+
yield
25+
del os.environ["TEST_VALKEY_DOCKER_IMAGE"]
26+
27+
28+
@pytest.mark.asyncio
29+
async def test_valkey_custom_version(custom_valkey_version, valkey_container):
30+
container_env_dict = dict(env.split("=") for env in valkey_container.config.env)
31+
32+
assert "VALKEY_VERSION" in container_env_dict, "VALKEY_VERSION environment variable not found"
33+
assert container_env_dict["VALKEY_VERSION"] == "7.2.9"
34+
35+
36+
@pytest.mark.asyncio
37+
async def test_valkey_execute_command(valkey_container):
38+
# Execute Valkey CLI command
39+
result = valkey_container.execute(["valkey-cli", "PING"])
40+
assert result.strip() == "PONG"
41+
42+
# Set a key-value pair
43+
set_result = valkey_container.execute(["valkey-cli", "SET", "test_key", "test_value"])
44+
assert set_result.strip() == "OK"
45+
46+
# Get the value
47+
get_result = valkey_container.execute(["valkey-cli", "GET", "test_key"])
48+
assert get_result.strip() == "test_value"
49+
50+
# Delete the key
51+
del_result = valkey_container.execute(["valkey-cli", "DEL", "test_key"])
52+
assert del_result.strip() == "1"
53+
54+
# Verify key is deleted
55+
get_deleted = valkey_container.execute(["valkey-cli", "GET", "test_key"])
56+
assert get_deleted.strip() == ""
57+
58+
async with await redis.from_url(
59+
"redis://localhost:6380", encoding="utf8"
60+
) as vkey:
61+
# Flush all existing data
62+
await vkey.flushall()
63+
64+
# Set a key-value pair
65+
await vkey.set("test_key", "test_value")
66+
67+
# Get the value
68+
value = await vkey.get("test_key")
69+
assert value == b"test_value"
70+
71+
# Delete the key
72+
deleted = await vkey.delete("test_key")
73+
assert deleted == 1
74+
75+
# Verify key is deleted
76+
deleted_value = await vkey.get("test_key")
77+
assert deleted_value is None

0 commit comments

Comments
 (0)