Skip to content

Commit ca71a4e

Browse files
replace more stuff
1 parent bc5a593 commit ca71a4e

File tree

5 files changed

+54
-10
lines changed

5 files changed

+54
-10
lines changed

core/testcontainers/core/generic.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from testcontainers.core.container import DockerContainer
1717
from testcontainers.core.exceptions import ContainerStartException
1818
from testcontainers.core.utils import raise_for_deprecated_parameter
19-
from testcontainers.core.waiting_utils import wait_container_is_ready
2019

2120
ADDITIONAL_TRANSIENT_ERRORS = []
2221
try:
@@ -34,8 +33,11 @@ class DbContainer(DockerContainer):
3433
Generic database container.
3534
"""
3635

37-
@wait_container_is_ready(*ADDITIONAL_TRANSIENT_ERRORS)
3836
def _connect(self) -> None:
37+
from testcontainers.core.wait_strategies import ContainerStatusWaitStrategy as C
38+
39+
C().with_transient_exceptions(*ADDITIONAL_TRANSIENT_ERRORS).wait_until_ready(self)
40+
3941
import sqlalchemy
4042

4143
engine = sqlalchemy.create_engine(self.get_connection_url())

core/testcontainers/core/wait_strategies.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
from urllib.error import HTTPError, URLError
3636
from urllib.request import Request, urlopen
3737

38+
from typing_extensions import Self
39+
3840
from testcontainers.compose import DockerCompose
3941
from testcontainers.core.utils import setup_logger
4042
# Import base classes from waiting_utils to make them available for tests
@@ -725,6 +727,24 @@ def __init__(self, *strategies: WaitStrategy) -> None:
725727
super().__init__()
726728
self._strategies = list(strategies)
727729

730+
def with_poll_interval(self, interval: Union[float, timedelta]) -> Self:
731+
super().with_poll_interval(interval)
732+
for _strategy in self._strategies:
733+
_strategy.with_poll_interval(interval)
734+
return self
735+
736+
def with_startup_timeout(self, timeout: Union[int, timedelta]) -> Self:
737+
super().with_startup_timeout(timeout)
738+
for _strategy in self._strategies:
739+
_strategy.with_startup_timeout(timeout)
740+
return self
741+
742+
def with_transient_exceptions(self, *transient_exceptions: type[Exception]) -> Self:
743+
super().with_transient_exceptions(*transient_exceptions)
744+
for _strategy in self._strategies:
745+
_strategy.with_transient_exceptions(*transient_exceptions)
746+
return self
747+
728748
def wait_until_ready(self, container: WaitStrategyTarget) -> None:
729749
"""
730750
Wait until all contained strategies are ready.

core/testcontainers/core/waiting_utils.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class WaitStrategy(ABC):
7575
def __init__(self) -> None:
7676
self._startup_timeout: float = testcontainers_config.timeout
7777
self._poll_interval: float = testcontainers_config.sleep_time
78+
self._transient_exceptions: list[type[Exception]] = [*TRANSIENT_EXCEPTIONS]
7879

7980
def with_startup_timeout(self, timeout: Union[int, timedelta]) -> "WaitStrategy":
8081
"""Set the maximum time to wait for the container to be ready."""
@@ -92,12 +93,21 @@ def with_poll_interval(self, interval: Union[float, timedelta]) -> "WaitStrategy
9293
self._poll_interval = interval
9394
return self
9495

96+
def with_transient_exceptions(self, *transient_exceptions: type[Exception]) -> "WaitStrategy":
97+
self._transient_exceptions.extend(transient_exceptions)
98+
return self
99+
95100
@abstractmethod
96101
def wait_until_ready(self, container: WaitStrategyTarget) -> None:
97102
"""Wait until the container is ready."""
98103
pass
99104

100-
def _poll(self, check: Callable[[], bool]) -> bool:
105+
def _poll(self, check: Callable[[], bool], transient_exceptions: list[type[Exception]] = None) -> bool:
106+
if not transient_exceptions:
107+
all_te_types = self._transient_exceptions
108+
else:
109+
all_te_types = [*self._transient_exceptions, *(transient_exceptions or [])]
110+
101111
start = time.time()
102112
while True:
103113
start_attempt = time.time()
@@ -112,8 +122,13 @@ def _poll(self, check: Callable[[], bool]) -> bool:
112122
return result
113123
except StopIteration:
114124
return False
115-
except: # noqa: E722, RUF100
116-
pass
125+
except Exception as e: # noqa: E722, RUF100
126+
is_transient = False
127+
for et in all_te_types:
128+
if isinstance(e, et):
129+
is_transient = True
130+
if not is_transient:
131+
raise RuntimeError(f"exception while checking for strategy {self}") from e
117132

118133
seconds_left_until_next = self._poll_interval - (time.time() - start_attempt)
119134
time.sleep(max(0.0, seconds_left_until_next))

core/tests/test_new_docker_api.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44

55

66
def test_docker_custom_image():
7-
container = DockerContainer("mysql:5.7.17")
8-
container.with_exposed_ports(3306)
9-
container.with_env("MYSQL_ROOT_PASSWORD", "root")
7+
container = DockerContainer("nginx:alpine-slim")
8+
container.with_exposed_ports(80)
109

1110
with container:
12-
port = container.get_exposed_port(3306)
11+
port = container.get_exposed_port(80)
1312
assert int(port) > 0
1413

1514

core/tests/test_waiting_utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from testcontainers.core.container import DockerContainer
4+
from testcontainers.core.wait_strategies import ContainerStatusWaitStrategy
45
from testcontainers.core.waiting_utils import wait_for_logs, wait_for, wait_container_is_ready
56

67

@@ -28,8 +29,15 @@ def simple_check() -> bool:
2829
def test_wait_container_is_ready_decorator_with_container() -> None:
2930
"""Test wait_container_is_ready decorator with a real container."""
3031

31-
@wait_container_is_ready()
3232
def check_container_logs(container: DockerContainer) -> bool:
33+
# wait until it becomes running.
34+
# if it is too late, it is actually fine in this case.
35+
# we are happy with an exited (even crashed) container that has logs.
36+
try:
37+
ContainerStatusWaitStrategy().wait_until_ready(container)
38+
except TimeoutError:
39+
pass
40+
3341
stdout, stderr = container.get_logs()
3442
return b"Hello from Docker!" in stdout or b"Hello from Docker!" in stderr
3543

0 commit comments

Comments
 (0)