Skip to content

Commit df61b51

Browse files
authored
Merge branch 'main' into typed_version
2 parents 2f5c127 + e9e40f9 commit df61b51

File tree

34 files changed

+831
-96
lines changed

34 files changed

+831
-96
lines changed

.github/.release-please-manifest.json

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

.github/CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ You need to have the following tools available to you:
3333
- Run `make install` to get `poetry` to install all dependencies and set up `pre-commit`
3434
- **Recommended**: Run `make` or `make help` to see other commands available to you.
3535
- After this, you should have a working virtual environment and proceed with writing code with your favourite IDE
36-
- **TIP**: You can run `make core/tests` or `make module/<my-module>/tests` to run the tests specifically for that to speed up feedback cycles
36+
- **TIP**: You can run `make core/tests` or `make modules/<my-module>/tests` to run the tests specifically for that to speed up feedback cycles
3737
- You can also run `make lint` to run the `pre-commit` for the entire codebase.
3838

3939

4040
## Adding new containers
4141

42-
We have an [issue template](.github/ISSUE_TEMPLATE/new-container.md) for adding new containers, please refer to that for more information.
42+
We have an [issue template](./ISSUE_TEMPLATE/new-container.md) for adding new containers, please refer to that for more information.
4343
Once you've talked to the maintainers (we do our best to reply!) then you can proceed with contributing the new container.
4444

4545
> [!WARNING]

.github/workflows/ci-community.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ jobs:
1919
- name: Checkout contents
2020
uses: actions/checkout@v4
2121
with:
22-
fetch-depth: 0 # recommended by tj-actions/changed-files
22+
fetch-depth: 0
2323
- name: Get changed files
2424
id: changed-files
25-
uses: tj-actions/changed-files@v42
25+
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
2626
with:
27-
path: "./modules"
28-
diff_relative: true
29-
dir_names: true
30-
dir_names_exclude_current_dir: true
31-
json: true
27+
base: ${{ github.ref }}
28+
list-files: 'json'
29+
filters: |
30+
modules:
31+
- 'modules/**'
3232
- name: Compute modules from files
3333
id: compute-changes
3434
run: |
35-
modules=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | jq '.[] | split("/") | first' | jq -s -c '. | unique')
35+
modules=$(echo "${{ toJson(steps.changed-files.outputs.modules_files) }}" | jq '.[] | split("/") | nth(1)' | jq -s -c '. | unique')
3636
echo "computed_modules=$modules"
3737
echo "computed_modules=$modules" >> $GITHUB_OUTPUT
3838
outputs:

.readthedocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ build:
1414
# https://github.com/readthedocs/readthedocs.org/issues/4912#issuecomment-1143587902s
1515
jobs:
1616
post_install:
17-
- pip install poetry==1.7.1 # match version from poetry.lock
17+
- pip install poetry==2.1.2 # match version from poetry.lock
1818
- poetry config virtualenvs.create false
1919
- poetry install --all-extras

CHANGELOG.md

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

3+
## [4.10.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.9.2...testcontainers-v4.10.0) (2025-04-02)
4+
5+
6+
### Features
7+
8+
* Add SocatContainer ([#795](https://github.com/testcontainers/testcontainers-python/issues/795)) ([2f9139c](https://github.com/testcontainers/testcontainers-python/commit/2f9139ca3ea9fba36325373b63635a5f539a3003))
9+
10+
11+
### Bug Fixes
12+
13+
* **ollama:** make device request a list ([#799](https://github.com/testcontainers/testcontainers-python/issues/799)) ([9497a45](https://github.com/testcontainers/testcontainers-python/commit/9497a45c39d13761aa3dd30dd5605676cbbe4b46))
14+
* **security:** Update track-modules job ([#787](https://github.com/testcontainers/testcontainers-python/issues/787)) ([f979525](https://github.com/testcontainers/testcontainers-python/commit/f97952505eba089f9cbbc979f8091dafbf520669))
15+
16+
## [4.9.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.9.1...testcontainers-v4.9.2) (2025-02-26)
17+
18+
19+
### Bug Fixes
20+
21+
* Change env var disabling OpenSearch security plugin ([#773](https://github.com/testcontainers/testcontainers-python/issues/773)) ([2620d7f](https://github.com/testcontainers/testcontainers-python/commit/2620d7fb1157caa18c3bef4bf2f9b3b79cd2f075))
22+
* **core:** create_label test ([#771](https://github.com/testcontainers/testcontainers-python/issues/771)) ([7517297](https://github.com/testcontainers/testcontainers-python/commit/751729722a013b46f67c09b4318b1b3d92b98008))
23+
* **core:** multiple container start invocations with custom labels ([#769](https://github.com/testcontainers/testcontainers-python/issues/769)) ([3e783a8](https://github.com/testcontainers/testcontainers-python/commit/3e783a80aa11b9c87201404a895d922624f0d451))
24+
* **keycloak:** Fixed Keycloak testcontainer for latest version v26.1.0 ([#766](https://github.com/testcontainers/testcontainers-python/issues/766)) ([b1642e9](https://github.com/testcontainers/testcontainers-python/commit/b1642e98c4d349564c4365782d1b58c9810b719a))
25+
* **scylla:** scylla get cluster method ([#778](https://github.com/testcontainers/testcontainers-python/issues/778)) ([46913c1](https://github.com/testcontainers/testcontainers-python/commit/46913c18a8b6f37bf8dc193828148926b6fc56a8))
26+
27+
28+
### Documentation
29+
30+
* Fixed typo in CONTRIBUTING.md ([#767](https://github.com/testcontainers/testcontainers-python/issues/767)) ([f0bb0f5](https://github.com/testcontainers/testcontainers-python/commit/f0bb0f54bea83885698bd137e24c397498709362))
31+
32+
## [4.9.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.9.0...testcontainers-v4.9.1) (2025-01-21)
33+
34+
35+
### Bug Fixes
36+
37+
* milvus healthcheck: use correct requests errors ([#759](https://github.com/testcontainers/testcontainers-python/issues/759)) ([78b137c](https://github.com/testcontainers/testcontainers-python/commit/78b137cfe53fc81eb8d5d858e98610fb6a8792ad))
38+
* **mysql:** add dialect parameter instead of hardcoded mysql dialect ([#739](https://github.com/testcontainers/testcontainers-python/issues/739)) ([8d77bd3](https://github.com/testcontainers/testcontainers-python/commit/8d77bd3541e1c5e73c7ed5d5bd3c0d7bb617f5c0))
39+
* **tests:** replace dind-test direct docker usage with sdk ([#750](https://github.com/testcontainers/testcontainers-python/issues/750)) ([ace2a7d](https://github.com/testcontainers/testcontainers-python/commit/ace2a7d143fb80576ddc0859a9106aa8652f2356))
40+
41+
## [4.9.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.8.2...testcontainers-v4.9.0) (2024-11-26)
42+
43+
44+
### Features
45+
46+
* **compose:** support for setting profiles ([#738](https://github.com/testcontainers/testcontainers-python/issues/738)) ([3e00e71](https://github.com/testcontainers/testcontainers-python/commit/3e00e71da4d2b5e7fd30315468d4e54c86ba6150))
47+
* **core:** Support working with env files ([#737](https://github.com/testcontainers/testcontainers-python/issues/737)) ([932ee30](https://github.com/testcontainers/testcontainers-python/commit/932ee307955e3591a63f194aee8e2f6d8e2f6bf9))
48+
49+
50+
### Bug Fixes
51+
52+
* allow running all tests ([#721](https://github.com/testcontainers/testcontainers-python/issues/721)) ([f958cf9](https://github.com/testcontainers/testcontainers-python/commit/f958cf9fe62a5f3ee2dc255713ec8b16de6a767d))
53+
* **core:** Avoid hanging upon bad docker host connection ([#742](https://github.com/testcontainers/testcontainers-python/issues/742)) ([4ced198](https://github.com/testcontainers/testcontainers-python/commit/4ced1983162914fe511a6e714f136b670e1dbdfb))
54+
* **core:** running testcontainer inside container ([#714](https://github.com/testcontainers/testcontainers-python/issues/714)) ([85a6666](https://github.com/testcontainers/testcontainers-python/commit/85a66667c23d76e87aecc6761bbb01429adb3dee))
55+
* **generic:** Also catch URLError waiting for ServerContainer ([#743](https://github.com/testcontainers/testcontainers-python/issues/743)) ([24e354f](https://github.com/testcontainers/testcontainers-python/commit/24e354f3bfa5912eaf7877da9442a885d7872f1a))
56+
* update wait_for_logs to not throw on 'created', and an optimization ([#719](https://github.com/testcontainers/testcontainers-python/issues/719)) ([271ca9a](https://github.com/testcontainers/testcontainers-python/commit/271ca9a0fef2e5f2b216457bfee44318e93990bf))
57+
* Vault health check ([#734](https://github.com/testcontainers/testcontainers-python/issues/734)) ([79434d6](https://github.com/testcontainers/testcontainers-python/commit/79434d6744b2918493884cf8fbf27aeadf78ecfd))
58+
59+
60+
### Documentation
61+
62+
* Documentation fix for ServerContainer ([#671](https://github.com/testcontainers/testcontainers-python/issues/671)) ([0303d47](https://github.com/testcontainers/testcontainers-python/commit/0303d47d7173e1c4ec1a4f565efee9b2fe694928))
63+
364
## [4.8.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.8.1...testcontainers-v4.8.2) (2024-09-27)
465

566

core/testcontainers/compose/compose.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ class DockerCompose:
139139
The list of services to use from this DockerCompose.
140140
client_args:
141141
arguments to pass to docker.from_env()
142+
docker_command_path:
143+
The docker compose command to run.
142144
143145
Example:
144146
@@ -171,6 +173,7 @@ class DockerCompose:
171173
env_file: Optional[str] = None
172174
services: Optional[list[str]] = None
173175
docker_command_path: Optional[str] = None
176+
profiles: Optional[list[str]] = None
174177

175178
def __post_init__(self):
176179
if isinstance(self.compose_file_name, str):
@@ -194,10 +197,12 @@ def docker_compose_command(self) -> list[str]:
194197

195198
@cached_property
196199
def compose_command_property(self) -> list[str]:
197-
docker_compose_cmd = [self.docker_command_path or "docker", "compose"]
200+
docker_compose_cmd = [self.docker_command_path] if self.docker_command_path else ["docker", "compose"]
198201
if self.compose_file_name:
199202
for file in self.compose_file_name:
200203
docker_compose_cmd += ["-f", file]
204+
if self.profiles:
205+
docker_compose_cmd += [item for profile in self.profiles for item in ["--profile", profile]]
201206
if self.env_file:
202207
docker_compose_cmd += ["--env-file", self.env_file]
203208
return docker_compose_cmd

core/testcontainers/core/config.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from pathlib import Path
77
from typing import Optional, Union
88

9+
import docker
10+
911

1012
class ConnectionMode(Enum):
1113
bridge_ip = "bridge_ip"
@@ -24,14 +26,32 @@ def use_mapped_port(self) -> bool:
2426
return True
2527

2628

29+
def get_docker_socket() -> str:
30+
"""
31+
Determine the docker socket, prefer value given by env variable
32+
33+
Using the docker api ensure we handle rootless docker properly
34+
"""
35+
if socket_path := environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE"):
36+
return socket_path
37+
38+
client = docker.from_env()
39+
try:
40+
socket_path = client.api.get_adapter(client.api.base_url).socket_path
41+
# return the normalized path as string
42+
return str(Path(socket_path).absolute())
43+
except AttributeError:
44+
return "/var/run/docker.sock"
45+
46+
2747
MAX_TRIES = int(environ.get("TC_MAX_TRIES", 120))
2848
SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1))
2949
TIMEOUT = MAX_TRIES * SLEEP_TIME
3050

3151
RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.8.1")
3252
RYUK_PRIVILEGED: bool = environ.get("TESTCONTAINERS_RYUK_PRIVILEGED", "false") == "true"
3353
RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true"
34-
RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock")
54+
RYUK_DOCKER_SOCKET: str = get_docker_socket()
3555
RYUK_RECONNECTION_TIMEOUT: str = environ.get("RYUK_RECONNECTION_TIMEOUT", "10s")
3656
TC_HOST_OVERRIDE: Optional[str] = environ.get("TC_HOST", environ.get("TESTCONTAINERS_HOST_OVERRIDE"))
3757

@@ -86,7 +106,7 @@ class TestcontainersConfiguration:
86106
tc_properties: dict[str, str] = field(default_factory=read_tc_properties)
87107
_docker_auth_config: Optional[str] = field(default_factory=lambda: environ.get("DOCKER_AUTH_CONFIG"))
88108
tc_host_override: Optional[str] = TC_HOST_OVERRIDE
89-
connection_mode_override: Optional[ConnectionMode] = None
109+
connection_mode_override: Optional[ConnectionMode] = field(default_factory=get_user_overwritten_connection_mode)
90110

91111
"""
92112
https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644

core/testcontainers/core/container.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import contextlib
2+
from os import PathLike
23
from socket import socket
34
from typing import TYPE_CHECKING, Optional, Union
45

56
import docker.errors
67
from docker import version
78
from docker.types import EndpointConfig
9+
from dotenv import dotenv_values
810
from typing_extensions import Self, assert_never
911

1012
from testcontainers.core.config import ConnectionMode
1113
from testcontainers.core.config import testcontainers_config as c
1214
from testcontainers.core.docker_client import DockerClient
13-
from testcontainers.core.exceptions import ContainerStartException
15+
from testcontainers.core.exceptions import ContainerConnectException, ContainerStartException
1416
from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID
1517
from testcontainers.core.network import Network
1618
from testcontainers.core.utils import is_arm, setup_logger
@@ -57,6 +59,12 @@ def with_env(self, key: str, value: str) -> Self:
5759
self.env[key] = value
5860
return self
5961

62+
def with_env_file(self, env_file: Union[str, PathLike]) -> Self:
63+
env_values = dotenv_values(env_file)
64+
for key, value in env_values.items():
65+
self.with_env(key, value)
66+
return self
67+
6068
def with_bind_ports(self, container: int, host: Optional[int] = None) -> Self:
6169
self.ports[container] = host
6270
return self
@@ -147,7 +155,7 @@ def get_exposed_port(self, port: int) -> int:
147155
return self.get_docker_client().port(self._container.id, port)
148156
return port
149157

150-
def with_command(self, command: str) -> Self:
158+
def with_command(self, command: Union[str, list[str]]) -> Self:
151159
self._command = command
152160
return self
153161

@@ -220,15 +228,21 @@ def _create_instance(cls) -> "Reaper":
220228
.with_env("RYUK_RECONNECTION_TIMEOUT", c.ryuk_reconnection_timeout)
221229
.start()
222230
)
223-
wait_for_logs(Reaper._container, r".* Started!")
231+
wait_for_logs(Reaper._container, r".* Started!", timeout=20, raise_on_exit=True)
224232

225233
container_host = Reaper._container.get_container_host_ip()
226234
container_port = int(Reaper._container.get_exposed_port(8080))
227235

236+
if not container_host or not container_port:
237+
raise ContainerConnectException(
238+
f"Could not obtain network details for {Reaper._container._container.id}. Host: {container_host} Port: {container_port}"
239+
)
240+
228241
last_connection_exception: Optional[Exception] = None
229242
for _ in range(50):
230243
try:
231244
Reaper._socket = socket()
245+
Reaper._socket.settimeout(1)
232246
Reaper._socket.connect((container_host, container_port))
233247
last_connection_exception = None
234248
break

core/testcontainers/core/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class ContainerStartException(RuntimeError):
1616
pass
1717

1818

19+
class ContainerConnectException(RuntimeError):
20+
pass
21+
22+
1923
class ContainerIsNotRunning(RuntimeError):
2024
pass
2125

core/testcontainers/core/image.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ class DockerImage:
2323
>>> with DockerImage(path="./core/tests/image_fixtures/sample/", tag="test-image") as image:
2424
... logs = image.get_logs()
2525
26-
:param tag: Tag for the image to be built (default: None)
2726
:param path: Path to the build context
27+
:param docker_client_kw: Keyword arguments to pass to the DockerClient
28+
:param tag: Tag for the image to be built (default: None)
29+
:param clean_up: Remove the image after exiting the context (default: True)
2830
:param dockerfile_path: Path to the Dockerfile within the build context path (default: Dockerfile)
2931
:param no_cache: Bypass build cache; CLI's --no-cache
32+
:param kwargs: Additional keyword arguments to pass to the underlying docker-py
3033
"""
3134

3235
def __init__(
@@ -49,11 +52,11 @@ def __init__(
4952
self._dockerfile_path = dockerfile_path
5053
self._no_cache = no_cache
5154

52-
def build(self, **kwargs) -> Self:
55+
def build(self) -> Self:
5356
logger.info(f"Building image from {self.path}")
5457
docker_client = self.get_docker_client()
5558
self._image, self._logs = docker_client.build(
56-
path=str(self.path), tag=self.tag, dockerfile=self._dockerfile_path, nocache=self._no_cache, **kwargs
59+
path=str(self.path), tag=self.tag, dockerfile=self._dockerfile_path, nocache=self._no_cache, **self._kwargs
5760
)
5861
logger.info(f"Built image {self.short_id} with tag {self.tag}")
5962
return self

core/testcontainers/core/labels.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str
2121
if k.startswith(TESTCONTAINERS_NAMESPACE):
2222
raise ValueError("The org.testcontainers namespace is reserved for internal use")
2323

24-
labels[LABEL_LANG] = "python"
25-
labels[LABEL_TESTCONTAINERS] = "true"
26-
labels[LABEL_VERSION] = importlib.metadata.version("testcontainers")
24+
tc_labels = {
25+
**labels,
26+
LABEL_LANG: "python",
27+
LABEL_TESTCONTAINERS: "true",
28+
LABEL_VERSION: importlib.metadata.version("testcontainers"),
29+
}
2730

2831
if image == c.ryuk_image:
29-
return labels
32+
return tc_labels
3033

31-
labels[LABEL_SESSION_ID] = SESSION_ID
32-
return labels
34+
tc_labels[LABEL_SESSION_ID] = SESSION_ID
35+
return tc_labels

core/testcontainers/socat/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# flake8: noqa
2+
from testcontainers.socat.socat import SocatContainer

0 commit comments

Comments
 (0)