Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/.release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "4.14.2"
".": "4.15.0-rc.1"
}
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## [4.15.0-rc.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.14.2...testcontainers-v4.15.0-rc.1) (2026-04-07)


### Features

* **compose:** add structured container inspect information ([#897](https://github.com/testcontainers/testcontainers-python/issues/897)) ([58459a1](https://github.com/testcontainers/testcontainers-python/commit/58459a13a1523c5dec8b21b0e16ae1afdce48156))
* **core:** support SSH-based DOCKER_HOST ([#993](https://github.com/testcontainers/testcontainers-python/issues/993)) ([d48115d](https://github.com/testcontainers/testcontainers-python/commit/d48115def127644964d4d2b09a38e3f4492cc43c))
* **generic:** Reintroducing the generic SQL module ([#892](https://github.com/testcontainers/testcontainers-python/issues/892)) ([2ca2321](https://github.com/testcontainers/testcontainers-python/commit/2ca2321ada12e09d491280c8ec855bf8511de7c2))
* **keycloak:** support for relative path and management relative path ([#982](https://github.com/testcontainers/testcontainers-python/issues/982)) ([898faf6](https://github.com/testcontainers/testcontainers-python/commit/898faf6a5955698958be6e8cfd32b87323d62a44))
* **mqtt:** MosquittoContainer: Add version 2.1.2 ([#978](https://github.com/testcontainers/testcontainers-python/issues/978)) ([af382f7](https://github.com/testcontainers/testcontainers-python/commit/af382f74e82bdcb14eac3f4e04a83432ae9beeba))


### Bug Fixes

* **azurite:** make visible to type checkers ([#927](https://github.com/testcontainers/testcontainers-python/issues/927)) ([baa5668](https://github.com/testcontainers/testcontainers-python/commit/baa566814b22fa922094a625ff92037cbe8bd93f))
* **clickhouse:** add `HttpWaitStrategy` instead of deprecated `wait_container_is_ready` ([#962](https://github.com/testcontainers/testcontainers-python/issues/962)) ([8034541](https://github.com/testcontainers/testcontainers-python/commit/803454147c03418b7b06601d251eb491a2cd79cf))
* **compose:** return type in get_service_port docstring ([#939](https://github.com/testcontainers/testcontainers-python/issues/939)) ([fed65fe](https://github.com/testcontainers/testcontainers-python/commit/fed65fe14507020007c115c535364c90d4bbdde9))
* **core:** Refactor copy file ([#996](https://github.com/testcontainers/testcontainers-python/issues/996)) ([0e0bb24](https://github.com/testcontainers/testcontainers-python/commit/0e0bb24a2bddfd8a03bebdfc3b9ff8cf8c78092b))
* **core:** wait for ryuk more reliably, improve tests: long_running, filter logs ([#984](https://github.com/testcontainers/testcontainers-python/issues/984)) ([b12ae13](https://github.com/testcontainers/testcontainers-python/commit/b12ae13e589a4ffe326c162a38df56eb30521d69))
* **generic:** Migrate ServerContainer from deprecated decorator to HttpWaitStrategy ([#971](https://github.com/testcontainers/testcontainers-python/issues/971)) ([460b0d8](https://github.com/testcontainers/testcontainers-python/commit/460b0d8a09635068815ea8c5c5a4e4cc1e3dfea7))
* **kafka:** Use wait strategy instead of deprecated wait_for_logs ([#903](https://github.com/testcontainers/testcontainers-python/issues/903)) ([87332c1](https://github.com/testcontainers/testcontainers-python/commit/87332c1332a30b673aac919b48e296e21f2c1baf))
* **postgres:** add py.typed marker to postgres module ([#849](https://github.com/testcontainers/testcontainers-python/issues/849)) ([c8a5bbd](https://github.com/testcontainers/testcontainers-python/commit/c8a5bbdbab137e6dc5af9a7224e65972665ec84d))
* **qdrant:** migrate Qdrant from deprecated decorator. ([#963](https://github.com/testcontainers/testcontainers-python/issues/963)) ([407f798](https://github.com/testcontainers/testcontainers-python/commit/407f79825be97865010dc0119cdfe3498a609a08))
* **redis:** Use wait strategy instead of deprecated decorator ([#914](https://github.com/testcontainers/testcontainers-python/issues/914)) ([e25713a](https://github.com/testcontainers/testcontainers-python/commit/e25713a300eda6a14973d2465590d2318dcc375d))
* **sftp:** Avoid using wait_for_logs in module. ([#995](https://github.com/testcontainers/testcontainers-python/issues/995)) ([83157eb](https://github.com/testcontainers/testcontainers-python/commit/83157eb4acd931949cfec3d2a84db0a61685e739))

## [4.14.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.14.1...testcontainers-v4.14.2) (2026-03-18)


Expand Down
23 changes: 23 additions & 0 deletions docs/modules/valkey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Valkey

Since testcontainers-python <a href="https://github.com/testcontainers/testcontainers-python/releases/tag/v4.14.3"><span class="tc-version">:material-tag: v4.14.3</span></a>

## Introduction

The Testcontainers module for Valkey.

## Adding this module to your project dependencies

Please run the following command to add the Valkey module to your python dependencies:

```bash
pip install testcontainers[valkey]
```

## Usage example

<!--codeinclude-->

[Creating a Valkey container](../../modules/valkey/example_basic.py)

<!--/codeinclude-->
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ nav:
- modules/redis.md
- modules/scylla.md
- modules/trino.md
- modules/valkey.md
- modules/weaviate.md
- modules/aws.md
- modules/azurite.md
Expand Down
2 changes: 2 additions & 0 deletions modules/valkey/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.. autoclass:: testcontainers.valkey.ValkeyContainer
.. title:: testcontainers.valkey.ValkeyContainer
82 changes: 82 additions & 0 deletions modules/valkey/example_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
Valkey container usage examples with valkey-glide sync client.

Requires: pip install valkey-glide-sync
"""

from glide_sync import GlideClient, GlideClientConfiguration, NodeAddress, ServerCredentials

from testcontainers.valkey import ValkeyContainer


def basic_example():
with ValkeyContainer() as valkey_container:
host = valkey_container.get_host()
port = valkey_container.get_exposed_port()
connection_url = valkey_container.get_connection_url()

print(f"Valkey connection URL: {connection_url}")
print(f"Host: {host}, Port: {port}")

config = GlideClientConfiguration([NodeAddress(host, port)])
client = GlideClient.create(config)

pong = client.ping()
print(f"PING response: {pong}")

client.set("key", "value")
print("SET response: OK")

value = client.get("key")
print(f"GET response: {value}")

client.close()


def password_example():
with ValkeyContainer().with_password("mypassword") as valkey_container:
host = valkey_container.get_host()
port = valkey_container.get_exposed_port()
connection_url = valkey_container.get_connection_url()

print(f"\nValkey with password connection URL: {connection_url}")

config = GlideClientConfiguration(
[NodeAddress(host, port)],
credentials=ServerCredentials(password="mypassword"),
)
client = GlideClient.create(config)

pong = client.ping()
print(f"PING response: {pong}")

client.close()


def version_example():
with ValkeyContainer().with_image_tag("8.0") as valkey_container:
print(f"\nUsing image: {valkey_container.image}")
connection_url = valkey_container.get_connection_url()
print(f"Connection URL: {connection_url}")


def bundle_example():
with ValkeyContainer().with_bundle() as valkey_container:
print(f"\nUsing bundle image: {valkey_container.image}")
host = valkey_container.get_host()
port = valkey_container.get_exposed_port()

config = GlideClientConfiguration([NodeAddress(host, port)])
client = GlideClient.create(config)

pong = client.ping()
print(f"PING response: {pong}")

client.close()


if __name__ == "__main__":
basic_example()
password_example()
version_example()
bundle_example()
103 changes: 103 additions & 0 deletions modules/valkey/testcontainers/valkey/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from testcontainers.core.container import DockerContainer
from testcontainers.core.wait_strategies import ExecWaitStrategy

_BASE_IMAGE = "valkey/valkey"
_BUNDLE_IMAGE = "valkey/valkey-bundle"


class ValkeyContainer(DockerContainer):
"""
Valkey container.

"""

def __init__(self, image: str = f"{_BASE_IMAGE}:latest", port: int = 6379, **kwargs) -> None:
super().__init__(image, **kwargs)
self.port = port
self.password: str | None = None
self.with_exposed_ports(self.port)
self.waiting_for(ExecWaitStrategy(["valkey-cli", "ping"]))

def with_password(self, password: str) -> "ValkeyContainer":
"""
Configure authentication for Valkey.

Args:
password: Password for Valkey authentication.

Returns:
self: Container instance for method chaining.
"""
self.password = password
self.with_command(["valkey-server", "--requirepass", password])
self.waiting_for(ExecWaitStrategy(["valkey-cli", "-a", password, "ping"]))
return self

def with_image_tag(self, tag: str) -> "ValkeyContainer":
"""
Specify Valkey version.

Args:
tag: Image tag (e.g., '8.0', 'latest').

Returns:
self: Container instance for method chaining.
"""
base_image = self.image.rsplit(":", 1)[0]
self.image = f"{base_image}:{tag}"
return self

def with_bundle(self) -> "ValkeyContainer":
"""
Enable all modules by switching to valkey-bundle image.

Returns:
self: Container instance for method chaining.
"""
tag = self.image.rsplit(":", 1)[-1]
self.image = f"{_BUNDLE_IMAGE}:{tag}"
return self

def get_connection_url(self) -> str:
"""
Get connection URL for Valkey.

Returns:
url: Connection URL in format valkey://[:password@]host:port
"""
host = self.get_host()
port = self.get_exposed_port()
if self.password:
return f"valkey://:{self.password}@{host}:{port}"
return f"valkey://{host}:{port}"

def get_host(self) -> str:
"""
Get container host.

Returns:
host: Container host IP.
"""
return self.get_container_host_ip()

def get_exposed_port(self) -> int:
"""
Get mapped port.

Returns:
port: Exposed port number.
"""
return int(super().get_exposed_port(self.port))
84 changes: 84 additions & 0 deletions modules/valkey/tests/test_valkey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import socket

from testcontainers.valkey import ValkeyContainer


def test_docker_run_valkey():
with ValkeyContainer() as valkey:
host = valkey.get_host()
port = valkey.get_exposed_port()

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall(b"*1\r\n$4\r\nPING\r\n")
response = s.recv(1024)
assert b"+PONG" in response


def test_docker_run_valkey_with_password():
with ValkeyContainer().with_password("mypass") as valkey:
host = valkey.get_host()
port = valkey.get_exposed_port()

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
# Authenticate
s.sendall(b"*2\r\n$4\r\nAUTH\r\n$6\r\nmypass\r\n")
auth_response = s.recv(1024)
assert b"+OK" in auth_response

# Test SET command
s.sendall(b"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n")
set_response = s.recv(1024)
assert b"+OK" in set_response

# Test GET command
s.sendall(b"*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n")
get_response = s.recv(1024)
assert b"world" in get_response


def test_get_connection_url():
with ValkeyContainer() as valkey:
url = valkey.get_connection_url()
assert url.startswith("valkey://")
assert str(valkey.get_exposed_port()) in url


def test_get_connection_url_with_password():
with ValkeyContainer().with_password("secret") as valkey:
url = valkey.get_connection_url()
assert url.startswith("valkey://:secret@")
assert str(valkey.get_exposed_port()) in url


def test_with_image_tag():
container = ValkeyContainer().with_image_tag("8.0")
assert container.image == "valkey/valkey:8.0"


def test_with_bundle():
container = ValkeyContainer().with_bundle()
assert container.image == "valkey/valkey-bundle:latest"


def test_with_bundle_and_tag():
container = ValkeyContainer().with_bundle().with_image_tag("9.0")
assert container.image == "valkey/valkey-bundle:9.0"


def test_with_tag_and_bundle():
container = ValkeyContainer().with_image_tag("8.0").with_bundle()
assert container.image == "valkey/valkey-bundle:8.0"


def test_bundle_starts():
with ValkeyContainer().with_bundle() as valkey:
host = valkey.get_host()
port = valkey.get_exposed_port()

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall(b"*1\r\n$4\r\nPING\r\n")
response = s.recv(1024)
assert b"+PONG" in response
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "testcontainers"
version = "4.14.2" # auto-incremented by release-please
version = "4.15.0-rc.1" # auto-incremented by release-please
description = "Python library for throwaway instances of anything that can run in a Docker container"
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -101,6 +101,7 @@ registry = ["bcrypt>=5"]
selenium = ["selenium>=4"]
scylla = ["cassandra-driver>=3; python_version < '3.14'"]
sftp = ["cryptography"]
valkey = []
vault = []
weaviate = ["weaviate-client>=4"]
chroma = ["chromadb-client>=1"]
Expand Down Expand Up @@ -218,6 +219,7 @@ packages = [
"modules/selenium/testcontainers",
"modules/scylla/testcontainers",
"modules/trino/testcontainers",
"modules/valkey/testcontainers",
"modules/vault/testcontainers",
"modules/weaviate/testcontainers",
]
Expand Down Expand Up @@ -267,6 +269,7 @@ dev-mode-dirs = [
"modules/selenium",
"modules/scylla",
"modules/trino",
"modules/valkey",
"modules/vault",
"modules/weaviate",
]
Expand Down
Loading