|
31 | 31 | import time |
32 | 32 | from datetime import timedelta |
33 | 33 | from pathlib import Path |
34 | | -from typing import Any, Callable, Optional, Union |
| 34 | +from typing import TYPE_CHECKING, Any, Callable, Optional, Union |
35 | 35 | from urllib.error import HTTPError, URLError |
36 | 36 | from urllib.request import Request, urlopen |
37 | 37 |
|
38 | 38 | from testcontainers.core.utils import setup_logger |
39 | | - |
| 39 | +from . import testcontainers_config |
40 | 40 | # Import base classes from waiting_utils to make them available for tests |
41 | | -from .waiting_utils import WaitStrategy, WaitStrategyTarget |
| 41 | +from testcontainers.core.waiting_utils import WaitStrategy, WaitStrategyTarget |
| 42 | +from testcontainers.compose import DockerCompose |
| 43 | + |
| 44 | +if TYPE_CHECKING: |
| 45 | + from testcontainers.core.container import DockerContainer |
42 | 46 |
|
43 | 47 | logger = setup_logger(__name__) |
44 | 48 |
|
@@ -718,6 +722,60 @@ def wait_until_ready(self, container: WaitStrategyTarget) -> None: |
718 | 722 | time.sleep(self._poll_interval) |
719 | 723 |
|
720 | 724 |
|
| 725 | +class ContainerStatusWaitStrategy(WaitStrategy): |
| 726 | + """ |
| 727 | + The possible values for the container status are: |
| 728 | + created |
| 729 | + running |
| 730 | + paused |
| 731 | + restarting |
| 732 | + exited |
| 733 | + removing |
| 734 | + dead |
| 735 | + https://docs.docker.com/reference/cli/docker/container/ls/#status |
| 736 | + """ |
| 737 | + CONTINUE_STATUSES = {"created", "restarting"} |
| 738 | + |
| 739 | + def __init__(self): |
| 740 | + super().__init__() |
| 741 | + |
| 742 | + def wait_until_ready(self, container: WaitStrategyTarget) -> None: |
| 743 | + result = self._poll(lambda: self.running(self.get_status(container))) |
| 744 | + if not result: |
| 745 | + raise TimeoutError("container did not become running") |
| 746 | + |
| 747 | + @staticmethod |
| 748 | + def running(status: str) -> bool: |
| 749 | + if status == "running": |
| 750 | + logger.debug("status is now running") |
| 751 | + return True |
| 752 | + if status in ContainerStatusWaitStrategy.CONTINUE_STATUSES: |
| 753 | + logger.debug("status is %s, which is valid for continuing (%s)", status, ContainerStatusWaitStrategy.CONTINUE_STATUSES) |
| 754 | + return False |
| 755 | + raise StopIteration(f"container status not valid for continuing: {status}") |
| 756 | + |
| 757 | + def get_status(self, container: Any) -> str: |
| 758 | + from testcontainers.core.container import DockerContainer |
| 759 | + |
| 760 | + if isinstance(container, DockerContainer): |
| 761 | + return self._get_status_tc_container(container) |
| 762 | + if isinstance(container, DockerCompose): |
| 763 | + return self._get_status_compose_container(container) |
| 764 | + raise TypeError(f"not supported operation: 'get_status' for type: {type(container)}") |
| 765 | + |
| 766 | + @staticmethod |
| 767 | + def _get_status_tc_container(container: "DockerContainer") -> str: |
| 768 | + logger.debug("fetching status of container %s", container) |
| 769 | + wrapped = container.get_wrapped_container() |
| 770 | + wrapped.reload() |
| 771 | + return wrapped.status |
| 772 | + |
| 773 | + @staticmethod |
| 774 | + def _get_status_compose_container(container: DockerCompose) -> str: |
| 775 | + logger.debug("fetching status of compose container %s", container) |
| 776 | + raise NotImplementedError |
| 777 | + |
| 778 | + |
721 | 779 | class CompositeWaitStrategy(WaitStrategy): |
722 | 780 | """ |
723 | 781 | Wait for multiple conditions to be satisfied in sequence. |
@@ -816,6 +874,7 @@ def wait_until_ready(self, container: WaitStrategyTarget) -> None: |
816 | 874 |
|
817 | 875 | __all__ = [ |
818 | 876 | "CompositeWaitStrategy", |
| 877 | + "ContainerStatusWaitStrategy", |
819 | 878 | "FileExistsWaitStrategy", |
820 | 879 | "HealthcheckWaitStrategy", |
821 | 880 | "HttpWaitStrategy", |
|
0 commit comments