Skip to content

Commit 3d23676

Browse files
authored
Merge branch 'main' into influxdb3
2 parents 911e0b1 + 4c53b89 commit 3d23676

27 files changed

Lines changed: 784 additions & 206 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "4.14.2"
2+
".": "4.15.0-rc2"
33
}

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
11
# Changelog
22

3+
## [4.15.0-rc2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.15.0-rc.1...testcontainers-v4.15.0-rc2) (2026-04-30)
4+
5+
6+
### Features
7+
8+
* **core:** support TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX ([#961](https://github.com/testcontainers/testcontainers-python/issues/961)) ([be9a0a6](https://github.com/testcontainers/testcontainers-python/commit/be9a0a612d934c77bdde20defd4d9f7d5228fb0c))
9+
* **mongodb:** Add Atlas Local for MongoDb ([#873](https://github.com/testcontainers/testcontainers-python/issues/873)) ([73aeb43](https://github.com/testcontainers/testcontainers-python/commit/73aeb43c18d56993d7c2626fb598a01842a91c35))
10+
* support with_copy_to ([#976](https://github.com/testcontainers/testcontainers-python/issues/976)) ([59ec1ce](https://github.com/testcontainers/testcontainers-python/commit/59ec1ce6dc7d54fa7f4b3c69f5bf674dfd19bfc0))
11+
* **valkey:** add Valkey module ([#947](https://github.com/testcontainers/testcontainers-python/issues/947)) ([fc09dc1](https://github.com/testcontainers/testcontainers-python/commit/fc09dc17bccd45d57d92f12c0de26b99ab1ccecf))
12+
13+
14+
### Bug Fixes
15+
16+
* **azurite:** use `HttpWaitStrategy` instead of deprecated `wait_container_is_ready` ([#1003](https://github.com/testcontainers/testcontainers-python/issues/1003)) ([9fe6b07](https://github.com/testcontainers/testcontainers-python/commit/9fe6b074852e5d6f1df2942bda52ee0557e5cb32)), closes [#874](https://github.com/testcontainers/testcontainers-python/issues/874)
17+
* fix pr [#961](https://github.com/testcontainers/testcontainers-python/issues/961) ([#1011](https://github.com/testcontainers/testcontainers-python/issues/1011)) ([8eff908](https://github.com/testcontainers/testcontainers-python/commit/8eff90851eecaf5720021d63e852a927c47f978c))
18+
19+
## [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)
20+
21+
22+
### Features
23+
24+
* **compose:** add structured container inspect information ([#897](https://github.com/testcontainers/testcontainers-python/issues/897)) ([58459a1](https://github.com/testcontainers/testcontainers-python/commit/58459a13a1523c5dec8b21b0e16ae1afdce48156))
25+
* **core:** support SSH-based DOCKER_HOST ([#993](https://github.com/testcontainers/testcontainers-python/issues/993)) ([d48115d](https://github.com/testcontainers/testcontainers-python/commit/d48115def127644964d4d2b09a38e3f4492cc43c))
26+
* **generic:** Reintroducing the generic SQL module ([#892](https://github.com/testcontainers/testcontainers-python/issues/892)) ([2ca2321](https://github.com/testcontainers/testcontainers-python/commit/2ca2321ada12e09d491280c8ec855bf8511de7c2))
27+
* **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))
28+
* **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))
29+
30+
31+
### Bug Fixes
32+
33+
* **azurite:** make visible to type checkers ([#927](https://github.com/testcontainers/testcontainers-python/issues/927)) ([baa5668](https://github.com/testcontainers/testcontainers-python/commit/baa566814b22fa922094a625ff92037cbe8bd93f))
34+
* **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))
35+
* **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))
36+
* **core:** Refactor copy file ([#996](https://github.com/testcontainers/testcontainers-python/issues/996)) ([0e0bb24](https://github.com/testcontainers/testcontainers-python/commit/0e0bb24a2bddfd8a03bebdfc3b9ff8cf8c78092b))
37+
* **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))
38+
* **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))
39+
* **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))
40+
* **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))
41+
* **qdrant:** migrate Qdrant from deprecated decorator. ([#963](https://github.com/testcontainers/testcontainers-python/issues/963)) ([407f798](https://github.com/testcontainers/testcontainers-python/commit/407f79825be97865010dc0119cdfe3498a609a08))
42+
* **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))
43+
* **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))
44+
345
## [4.14.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.14.1...testcontainers-v4.14.2) (2026-03-18)
446

547

core/testcontainers/core/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,12 @@ def _render_bool(self, env_name: str, prop_name: str) -> bool:
110110
_docker_auth_config: Optional[str] = field(default_factory=lambda: environ.get("DOCKER_AUTH_CONFIG"))
111111
tc_host_override: Optional[str] = environ.get("TC_HOST", environ.get("TESTCONTAINERS_HOST_OVERRIDE"))
112112
connection_mode_override: Optional[ConnectionMode] = field(default_factory=get_user_overwritten_connection_mode)
113-
114113
"""
115114
https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644
116115
if os env TC_HOST is set, use it
117116
"""
117+
hub_image_name_prefix: str = field(default_factory=lambda: environ.get("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", ""))
118+
""" Prefix to use for hub image names, e.g. for private registries. """
118119

119120
@property
120121
def docker_auth_config(self) -> Optional[str]:

core/testcontainers/core/container.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import atexit
12
import contextlib
23
import io
34
import pathlib
@@ -89,8 +90,7 @@ def __init__(
8990
self.with_volume_mapping(*vol)
9091

9192
self.tmpfs: dict[str, str] = {}
92-
93-
self.image = image
93+
self.image = c.hub_image_name_prefix + image
9494
self._docker = DockerClient(**(docker_client_kw or {}))
9595
self._container: Optional[Container] = None
9696
self._command: Optional[Union[str, list[str]]] = command
@@ -204,10 +204,9 @@ def start(self) -> Self:
204204
else {}
205205
)
206206

207-
self._container = docker_client.run(
207+
self._container = docker_client.create(
208208
self.image,
209209
command=self._command,
210-
detach=True,
211210
environment=self.env,
212211
ports=cast("dict[int, Optional[int]]", self.ports),
213212
name=self._name,
@@ -216,14 +215,16 @@ def start(self) -> Self:
216215
**{**network_kwargs, **self._kwargs},
217216
)
218217

218+
for t in self._transferable_specs:
219+
self._transfer_into_container(*t)
220+
221+
docker_client.start(self._container)
222+
219223
if self._wait_strategy is not None:
220224
self._wait_strategy.wait_until_ready(self)
221225

222226
logger.info("Container started: %s", self._container.short_id)
223227

224-
for t in self._transferable_specs:
225-
self._transfer_into_container(*t)
226-
227228
return self
228229

229230
def stop(self, force: bool = True, delete_volume: bool = True) -> None:
@@ -330,6 +331,13 @@ def exec(self, command: Union[str, list[str]]) -> ExecResult:
330331
raise ContainerStartException("Container should be started before executing a command")
331332
return self._container.exec_run(command)
332333

334+
def wait(self) -> int:
335+
"""Wait for the container to stop and return its exit code."""
336+
if not self._container:
337+
raise ContainerStartException("Container should be started before waiting")
338+
result = self._container.wait()
339+
return int(result["StatusCode"])
340+
333341
def get_container_info(self) -> Optional[ContainerInspectInfo]:
334342
"""Get container information via docker inspect (lazy loaded).
335343
@@ -469,5 +477,6 @@ def _create_instance(cls) -> "Reaper":
469477
rs.send(f"label={LABEL_SESSION_ID}={SESSION_ID}\r\n".encode())
470478

471479
Reaper._instance = Reaper()
480+
atexit.register(Reaper.delete_instance)
472481

473482
return Reaper._instance

core/testcontainers/core/docker_client.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,43 @@ def run(
118118
)
119119
return container
120120

121+
@_wrapped_container_collection
122+
def create(
123+
self,
124+
image: str,
125+
command: Optional[Union[str, list[str]]] = None,
126+
environment: Optional[dict[str, str]] = None,
127+
ports: Optional[dict[int, Optional[int]]] = None,
128+
labels: Optional[dict[str, str]] = None,
129+
**kwargs: Any,
130+
) -> Container:
131+
"""Create a container without starting it, pulling the image first if not present locally."""
132+
if "network" not in kwargs and not get_docker_host():
133+
host_network = self.find_host_network()
134+
if host_network:
135+
kwargs["network"] = host_network
136+
137+
try:
138+
# This is more or less a replication of what the self.client.containers.start does internally
139+
self.client.images.get(image)
140+
except docker.errors.ImageNotFound:
141+
self.client.images.pull(image)
142+
143+
container = self.client.containers.create(
144+
image,
145+
command=command,
146+
environment=environment,
147+
ports=ports,
148+
labels=create_labels(image, labels),
149+
**kwargs,
150+
)
151+
return container
152+
153+
@_wrapped_container_collection
154+
def start(self, container: Container) -> None:
155+
"""Start a previously created container."""
156+
container.start()
157+
121158
@_wrapped_image_collection
122159
def build(
123160
self, path: str, tag: Optional[str], rm: bool = True, **kwargs: Any

core/tests/test_compose.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ def test_compose_normalize_rewrites_local_url_for_ssh_docker_host(
408408
result = model.normalize()
409409
assert result.URL == expected_url
410410
assert result.PublishedPort == 9999
411-
411+
412412

413413
def test_container_info():
414414
"""Test get_container_info functionality"""

core/tests/test_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ def test_read_tc_properties(monkeypatch: MonkeyPatch) -> None:
2828
assert config.tc_properties == {"tc.host": "some_value"}
2929

3030

31+
def test_hub_image_name_prefix(monkeypatch: MonkeyPatch) -> None:
32+
"""
33+
Ensure that the hub_image_name_prefix configuration variable can be read from the environment
34+
"""
35+
monkeypatch.setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "myregistry.local/")
36+
config = TCC()
37+
assert config.hub_image_name_prefix == "myregistry.local/"
38+
39+
3140
def test_set_tc_properties(monkeypatch: MonkeyPatch) -> None:
3241
"""
3342
Ensure the configuration file variables can be read if no environment variable is set

core/tests/test_container.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from testcontainers.core.container import DockerContainer
66
from testcontainers.core.docker_client import DockerClient
77
from testcontainers.core.config import ConnectionMode
8+
from testcontainers.core.config import testcontainers_config
89

910
FAKE_ID = "ABC123"
1011

@@ -103,6 +104,28 @@ def test_attribute(init_attr, init_value, class_attr, stored_value):
103104
assert getattr(container, class_attr) == stored_value
104105

105106

107+
def test_image_prefix_applied(monkeypatch: pytest.MonkeyPatch) -> None:
108+
"""Test that the hub_image_name_prefix is properly applied to the image name."""
109+
110+
# Set a prefix
111+
test_prefix = "myregistry.example.com/"
112+
monkeypatch.setattr(testcontainers_config, "hub_image_name_prefix", test_prefix)
113+
114+
# Create a container and verify the prefix is applied
115+
container = DockerContainer("nginx:latest")
116+
assert container.image == "myregistry.example.com/nginx:latest"
117+
118+
119+
def test_image_no_prefix_applied_when_empty(monkeypatch: pytest.MonkeyPatch) -> None:
120+
"""Test that when hub_image_name_prefix is empty, no prefix is applied."""
121+
# Set an empty prefix
122+
monkeypatch.setattr(testcontainers_config, "hub_image_name_prefix", "")
123+
124+
# Create a container and verify no prefix is applied
125+
container = DockerContainer("nginx:latest")
126+
assert container.image == "nginx:latest"
127+
128+
106129
def test_container_info():
107130
"""Test get_container_info functionality with a real container."""
108131
with DockerContainer("alpine:latest").with_command("sleep 30") as container:

core/tests/test_transferable.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,19 @@ def test_copy_into_container_at_startup(transferable: Transferable):
104104
assert result.output == b"hello world"
105105

106106

107+
def test_copy_into_startup_file(transferable: Transferable):
108+
destination_in_container = "/tmp/my_file"
109+
110+
container = DockerContainer("bash", command=f"cat {destination_in_container}")
111+
container.with_copy_into_container(transferable, destination_in_container)
112+
113+
with container:
114+
exit_code = container.wait()
115+
stdout, _ = container.get_logs()
116+
assert exit_code == 0
117+
assert stdout.decode() == "hello world"
118+
119+
107120
def test_copy_into_container_via_initializer(transferable: Transferable):
108121
destination_in_container = "/tmp/my_file"
109122
transferables: list[TransferSpec] = [(transferable, destination_in_container, 0o644)]

docs/modules/valkey.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Valkey
2+
3+
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>
4+
5+
## Introduction
6+
7+
The Testcontainers module for Valkey.
8+
9+
## Adding this module to your project dependencies
10+
11+
Please run the following command to add the Valkey module to your python dependencies:
12+
13+
```bash
14+
pip install testcontainers[valkey]
15+
```
16+
17+
## Usage example
18+
19+
<!--codeinclude-->
20+
21+
[Creating a Valkey container](../../modules/valkey/example_basic.py)
22+
23+
<!--/codeinclude-->

0 commit comments

Comments
 (0)