Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2877c2f
feat(core): add support for temporalio module
bgener Feb 16, 2026
57f6dd8
corrected description
bgener Feb 16, 2026
41486f2
Merge branch 'main' into feature/implement-temporal-support
Tranquility2 Apr 3, 2026
baa5668
fix(azurite): make visible to type checkers (#927)
kveretennicov Apr 3, 2026
d48115d
feat(core): support SSH-based DOCKER_HOST (#993)
Tranquility2 Apr 3, 2026
407f798
fix(qdrant): migrate Qdrant from deprecated decorator. (#963)
ethanlee928 Apr 3, 2026
8034541
fix(clickhouse): add `HttpWaitStrategy` instead of deprecated `wait_c…
dzhalaevd Apr 3, 2026
fed65fe
fix(compose): return type in get_service_port docstring (#939)
PraneethJain Apr 3, 2026
58459a1
feat(compose): add structured container inspect information (#897)
VictorCavichioli Apr 3, 2026
c8a5bbd
fix(postgres): add py.typed marker to postgres module (#849)
oliverlambson Apr 4, 2026
e25713a
fix(redis): Use wait strategy instead of deprecated decorator (#914)
mschmitzer Apr 7, 2026
6ecf347
chore(main): release testcontainers 4.15.0-rc.1 (#986)
github-actions[bot] Apr 7, 2026
fc09dc1
feat(valkey): add Valkey module (#947)
daric93 Apr 9, 2026
9fe6b07
fix(azurite): use `HttpWaitStrategy` instead of deprecated `wait_cont…
edgarrmondragon Apr 9, 2026
2c1145c
chore(deps): bump authlib from 1.6.6 to 1.6.9 (#999)
dependabot[bot] Apr 10, 2026
aa12ee2
chore(deps): bump cryptography from 46.0.3 to 46.0.7 (#1006)
dependabot[bot] Apr 10, 2026
23aca0b
chore(deps): bump aiohttp from 3.13.3 to 3.13.4 (#1005)
dependabot[bot] Apr 10, 2026
59ec1ce
feat: support with_copy_to (#976)
guenhter Apr 29, 2026
73aeb43
feat(mongodb): Add Atlas Local for MongoDb (#873)
jeff-goddard Apr 29, 2026
be9a0a6
feat(core): support TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX (#961)
PadenZach Apr 29, 2026
8eff908
fix: fix pr #961 (#1011)
alexanderankin Apr 29, 2026
79d920e
chore(main): release testcontainers 4.15.0-rc2 (#1004)
github-actions[bot] Apr 30, 2026
4fb69bb
feat(core): add support for temporalio module
bgener Feb 16, 2026
e2b556b
corrected description
bgener Feb 16, 2026
ee61185
Merge branch 'feature/implement-temporal-support' of https://github.c…
bgener May 1, 2026
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-rc2"
}
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# Changelog

## [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)


### Features

* **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))
* **mongodb:** Add Atlas Local for MongoDb ([#873](https://github.com/testcontainers/testcontainers-python/issues/873)) ([73aeb43](https://github.com/testcontainers/testcontainers-python/commit/73aeb43c18d56993d7c2626fb598a01842a91c35))
* support with_copy_to ([#976](https://github.com/testcontainers/testcontainers-python/issues/976)) ([59ec1ce](https://github.com/testcontainers/testcontainers-python/commit/59ec1ce6dc7d54fa7f4b3c69f5bf674dfd19bfc0))
* **valkey:** add Valkey module ([#947](https://github.com/testcontainers/testcontainers-python/issues/947)) ([fc09dc1](https://github.com/testcontainers/testcontainers-python/commit/fc09dc17bccd45d57d92f12c0de26b99ab1ccecf))


### Bug Fixes

* **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)
* 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))

## [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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ ${TESTS}: %/tests:
quick-core-tests: ## Run core tests excluding long_running
uv run coverage run --parallel -m pytest -v -m "not long_running" core/tests

core-tests: ## Run tests for the core package
uv run coverage run --parallel -m pytest -v core/tests

coverage: ## Target to combine and report coverage.
uv run coverage combine
uv run coverage report
Expand Down
1 change: 1 addition & 0 deletions conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,5 @@
nitpick_ignore = [
("py:class", "typing_extensions.Self"),
("py:class", "docker.models.containers.ExecResult"),
("py:class", "testcontainers.core.docker_client.ContainerInspectInfo"),
]
67 changes: 48 additions & 19 deletions core/testcontainers/compose/compose.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import sys
from dataclasses import asdict, dataclass, field, fields, is_dataclass
from dataclasses import asdict, dataclass, field
from functools import cached_property
from json import loads
from logging import getLogger, warning
Expand All @@ -11,28 +11,16 @@
from types import TracebackType
from typing import Any, Callable, Literal, Optional, TypeVar, Union, cast

from testcontainers.core.docker_client import DockerClient, get_docker_host_hostname
from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed
from testcontainers.core.inspect import ContainerInspectInfo, _ignore_properties
from testcontainers.core.waiting_utils import WaitStrategy

_IPT = TypeVar("_IPT")
_WARNINGS = {"DOCKER_COMPOSE_GET_CONFIG": "get_config is experimental, see testcontainers/testcontainers-python#669"}

logger = getLogger(__name__)


def _ignore_properties(cls: type[_IPT], dict_: Any) -> _IPT:
"""omits extra fields like @JsonIgnoreProperties(ignoreUnknown = true)

https://gist.github.com/alexanderankin/2a4549ac03554a31bef6eaaf2eaf7fd5"""
if isinstance(dict_, cls):
return dict_
if not is_dataclass(cls):
raise TypeError(f"Expected a dataclass type, got {cls}")
class_fields = {f.name for f in fields(cls)}
filtered = {k: v for k, v in dict_.items() if k in class_fields}
return cls(**filtered)


@dataclass
class PublishedPortModel:
"""
Expand All @@ -45,10 +33,21 @@ class PublishedPortModel:
Protocol: Optional[str] = None

def normalize(self) -> "PublishedPortModel":
url_not_usable = system() == "Windows" and self.URL == "0.0.0.0"
if url_not_usable:
url = self.URL

# For SSH-based DOCKER_HOST, local addresses (0.0.0.0, 127.0.0.1, localhost, ::, ::1)
# refer to the remote machine, not the local one.
# Replace them with the actual remote hostname.
ssh_host = get_docker_host_hostname()
if ssh_host and url in ("0.0.0.0", "127.0.0.1", "localhost", "::", "::1"):
url = ssh_host
# On Windows, 0.0.0.0 is not usable — replace with 127.0.0.1
elif system() == "Windows" and url == "0.0.0.0":
url = "127.0.0.1"

if url != self.URL:
self_dict = asdict(self)
self_dict.update({"URL": "127.0.0.1"})
self_dict.update({"URL": url})
return PublishedPortModel(**self_dict)
return self

Expand Down Expand Up @@ -81,6 +80,7 @@ class ComposeContainer:
ExitCode: Optional[int] = None
Publishers: list[PublishedPortModel] = field(default_factory=list)
_docker_compose: Optional["DockerCompose"] = field(default=None, init=False, repr=False)
_cached_container_info: Optional[ContainerInspectInfo] = field(default=None, init=False, repr=False)

def __post_init__(self) -> None:
if self.Publishers:
Expand Down Expand Up @@ -147,6 +147,28 @@ def reload(self) -> None:
# each time through get_container(), but we need this method for compatibility
pass

def get_container_info(self) -> Optional[ContainerInspectInfo]:
"""Get container information via docker inspect (lazy loaded).

Returns:
Container inspect information or None if container is not started.
"""
if self._cached_container_info is not None:
return self._cached_container_info

if not self._docker_compose or not self.ID:
return None

try:
docker_client = self._docker_compose._get_docker_client()
self._cached_container_info = docker_client.get_container_inspect_info(self.ID)

except Exception as e:
logger.warning(f"Failed to get container info for {self.ID}: {e}")
self._cached_container_info = None

return self._cached_container_info

@property
def status(self) -> str:
"""Get container status for compatibility with wait strategies."""
Expand Down Expand Up @@ -221,6 +243,7 @@ class DockerCompose:
quiet_pull: bool = False
quiet_build: bool = False
_wait_strategies: Optional[dict[str, Any]] = field(default=None, init=False, repr=False)
_docker_client: Optional[DockerClient] = field(default=None, init=False, repr=False)

def __post_init__(self) -> None:
if isinstance(self.compose_file_name, str):
Expand Down Expand Up @@ -494,7 +517,7 @@ def get_service_port(

Returns
-------
str:
int:
The mapped port on the host
"""
normalize: PublishedPortModel = self.get_container(service_name).get_publisher(by_port=port).normalize()
Expand Down Expand Up @@ -585,3 +608,9 @@ def wait_for(self, url: str) -> "DockerCompose":
with urlopen(url) as response:
response.read()
return self

def _get_docker_client(self) -> DockerClient:
"""Get Docker client instance."""
if self._docker_client is None:
self._docker_client = DockerClient()
return self._docker_client
3 changes: 2 additions & 1 deletion core/testcontainers/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ def _render_bool(self, env_name: str, prop_name: str) -> bool:
_docker_auth_config: Optional[str] = field(default_factory=lambda: environ.get("DOCKER_AUTH_CONFIG"))
tc_host_override: Optional[str] = environ.get("TC_HOST", environ.get("TESTCONTAINERS_HOST_OVERRIDE"))
connection_mode_override: Optional[ConnectionMode] = field(default_factory=get_user_overwritten_connection_mode)

"""
https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644
if os env TC_HOST is set, use it
"""
hub_image_name_prefix: str = field(default_factory=lambda: environ.get("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", ""))
""" Prefix to use for hub image names, e.g. for private registries. """

@property
def docker_auth_config(self) -> Optional[str]:
Expand Down
44 changes: 37 additions & 7 deletions core/testcontainers/core/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from testcontainers.core.config import testcontainers_config as c
from testcontainers.core.docker_client import DockerClient
from testcontainers.core.exceptions import ContainerConnectException, ContainerStartException
from testcontainers.core.inspect import ContainerInspectInfo
from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID
from testcontainers.core.network import Network
from testcontainers.core.transferable import Transferable, TransferSpec, build_transfer_tar
Expand Down Expand Up @@ -88,8 +89,7 @@ def __init__(
self.with_volume_mapping(*vol)

self.tmpfs: dict[str, str] = {}

self.image = image
self.image = c.hub_image_name_prefix + image
self._docker = DockerClient(**(docker_client_kw or {}))
self._container: Optional[Container] = None
self._command: Optional[Union[str, list[str]]] = command
Expand All @@ -104,6 +104,7 @@ def __init__(

self._kwargs = kwargs
self._wait_strategy: Optional[WaitStrategy] = _wait_strategy
self._cached_container_info: Optional[ContainerInspectInfo] = None

self._transferable_specs: list[TransferSpec] = []
if transferables:
Expand Down Expand Up @@ -202,10 +203,9 @@ def start(self) -> Self:
else {}
)

self._container = docker_client.run(
self._container = docker_client.create(
self.image,
command=self._command,
detach=True,
environment=self.env,
ports=cast("dict[int, Optional[int]]", self.ports),
name=self._name,
Expand All @@ -214,14 +214,16 @@ def start(self) -> Self:
**{**network_kwargs, **self._kwargs},
)

for t in self._transferable_specs:
self._transfer_into_container(*t)

docker_client.start(self._container)

if self._wait_strategy is not None:
self._wait_strategy.wait_until_ready(self)

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

for t in self._transferable_specs:
self._transfer_into_container(*t)

return self

def stop(self, force: bool = True, delete_volume: bool = True) -> None:
Expand Down Expand Up @@ -328,6 +330,34 @@ def exec(self, command: Union[str, list[str]]) -> ExecResult:
raise ContainerStartException("Container should be started before executing a command")
return self._container.exec_run(command)

def wait(self) -> int:
"""Wait for the container to stop and return its exit code."""
if not self._container:
raise ContainerStartException("Container should be started before waiting")
result = self._container.wait()
return int(result["StatusCode"])

def get_container_info(self) -> Optional[ContainerInspectInfo]:
"""Get container information via docker inspect (lazy loaded).

Returns:
Container inspect information or None if container is not started.
"""
if self._cached_container_info is not None:
return self._cached_container_info

if not self._container:
return None

try:
self._cached_container_info = self.get_docker_client().get_container_inspect_info(self._container.id)

except Exception as e:
logger.warning(f"Failed to get container info for {self._container.id}: {e}")
self._cached_container_info = None

return self._cached_container_info

def _configure(self) -> None:
# placeholder if subclasses want to define this and use the default start method
pass
Expand Down
Loading