diff --git a/core/testcontainers/compose/compose.py b/core/testcontainers/compose/compose.py index ffe6538ad..055dc9880 100644 --- a/core/testcontainers/compose/compose.py +++ b/core/testcontainers/compose/compose.py @@ -9,7 +9,7 @@ from subprocess import CalledProcessError, CompletedProcess from subprocess import run as subprocess_run from types import TracebackType -from typing import Any, Callable, Literal, Optional, TypeVar, Union, cast +from typing import Any, Callable, Literal, Optional, Self, TypeVar, Union, cast from testcontainers.core.docker_client import DockerClient, get_docker_host_hostname from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed @@ -32,7 +32,7 @@ class PublishedPortModel: PublishedPort: Optional[int] = None Protocol: Optional[str] = None - def normalize(self) -> "PublishedPortModel": + def normalize(self) -> Self: url = self.URL # For SSH-based DOCKER_HOST, local addresses (0.0.0.0, 127.0.0.1, localhost, ::, ::1) @@ -137,7 +137,7 @@ def get_logs(self) -> tuple[bytes, bytes]: stdout, stderr = self._docker_compose.get_logs(self.Service) return stdout.encode(), stderr.encode() - def get_wrapped_container(self) -> "ComposeContainer": + def get_wrapped_container(self) -> Self: """Get the underlying container object for compatibility.""" return self @@ -251,7 +251,7 @@ def __post_init__(self) -> None: if isinstance(self.env_file, str): self.env_file = [self.env_file] - def __enter__(self) -> "DockerCompose": + def __enter__(self) -> Self: try: self.start() return self @@ -288,7 +288,7 @@ def compose_command_property(self) -> list[str]: docker_compose_cmd += ["--env-file", env_file] return docker_compose_cmd - def waiting_for(self, strategies: dict[str, WaitStrategy]) -> "DockerCompose": + def waiting_for(self, strategies: dict[str, WaitStrategy]) -> Self: """ Set wait strategies for specific services. @@ -557,7 +557,7 @@ def get_service_host_and_port( publisher = self.get_container(service_name).get_publisher(by_port=port).normalize() return publisher.URL, publisher.PublishedPort - def wait_for(self, url: str) -> "DockerCompose": + def wait_for(self, url: str) -> Self: """ Waits for a response from a given URL. This is typically used to block until a service in the environment has started and is responding. Note that it does not assert any sort of @@ -605,10 +605,6 @@ def wait_for(self, url: str) -> "DockerCompose": time.sleep(1) - 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: diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py index 680e7ca20..33b25724b 100644 --- a/core/testcontainers/core/container.py +++ b/core/testcontainers/core/container.py @@ -6,14 +6,14 @@ from os import PathLike from socket import socket from types import TracebackType -from typing import TYPE_CHECKING, Any, Optional, TypedDict, Union, cast +from typing import TYPE_CHECKING, Any, Optional, Self, TypedDict, Union, cast import docker.errors from docker import version from docker.models.containers import ExecResult from docker.types import EndpointConfig from dotenv import dotenv_values -from typing_extensions import Self, assert_never +from typing_extensions import assert_never from testcontainers.core.config import ConnectionMode from testcontainers.core.config import testcontainers_config as c @@ -390,14 +390,14 @@ class Reaper: :meta private: """ - _instance: "Optional[Reaper]" = None + _instance: Optional[Self] = None _container: Optional[DockerContainer] = None _socket: Optional[socket] = None @classmethod - def get_instance(cls) -> "Reaper": + def get_instance(cls) -> Self: if not Reaper._instance: - Reaper._instance = Reaper._create_instance() + Reaper._create_instance() return Reaper._instance @@ -416,7 +416,7 @@ def delete_instance(cls) -> None: Reaper._instance = None @classmethod - def _create_instance(cls) -> "Reaper": + def _create_instance(cls) -> Self: logger.debug(f"Creating new Reaper for session: {SESSION_ID}") Reaper._container = ( diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py index ad08b1823..75656525a 100644 --- a/core/testcontainers/core/docker_client.py +++ b/core/testcontainers/core/docker_client.py @@ -276,7 +276,7 @@ def client_networks_create(self, name: str, param: dict[str, Any]) -> "DockerNet labels = create_labels("", param.get("labels")) return self.client.networks.create(name, **{**param, "labels": labels}) - def get_container_inspect_info(self, container_id: str) -> "ContainerInspectInfo": + def get_container_inspect_info(self, container_id: str) -> ContainerInspectInfo: """Get container inspect information with fresh data.""" container = self.client.containers.get(container_id) return ContainerInspectInfo.from_dict(container.attrs) diff --git a/core/testcontainers/core/generic.py b/core/testcontainers/core/generic.py index 1410321ee..1d5478f03 100644 --- a/core/testcontainers/core/generic.py +++ b/core/testcontainers/core/generic.py @@ -10,7 +10,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from typing import Any, Optional +from typing import Any, Optional, Self from urllib.parse import quote from testcontainers.core.container import DockerContainer @@ -74,7 +74,7 @@ def _create_connection_url( url = f"{url}/{dbname}" return url - def start(self) -> "DbContainer": + def start(self) -> Self: self._configure() super().start() self._transfer_seed() diff --git a/core/testcontainers/core/image.py b/core/testcontainers/core/image.py index eedb2ce40..0cc7894c9 100644 --- a/core/testcontainers/core/image.py +++ b/core/testcontainers/core/image.py @@ -1,8 +1,6 @@ from os import PathLike from types import TracebackType -from typing import TYPE_CHECKING, Any, Optional, Union - -from typing_extensions import Self +from typing import TYPE_CHECKING, Any, Optional, Self, Union from testcontainers.core.docker_client import DockerClient from testcontainers.core.utils import setup_logger diff --git a/core/testcontainers/core/inspect.py b/core/testcontainers/core/inspect.py index a139422dc..c0cb971c3 100644 --- a/core/testcontainers/core/inspect.py +++ b/core/testcontainers/core/inspect.py @@ -14,7 +14,7 @@ """Docker Engine API data structures for container inspect responses.""" from dataclasses import dataclass, fields, is_dataclass -from typing import Any, Optional, TypeVar +from typing import Any, Optional, Self, TypeVar _IPT = TypeVar("_IPT") @@ -523,7 +523,7 @@ class ContainerInspectInfo: NetworkSettings: Optional[ContainerNetworkSettings] = None @classmethod - def from_dict(cls, data: dict[str, Any]) -> "ContainerInspectInfo": + def from_dict(cls, data: dict[str, Any]) -> Self: """Create from docker inspect JSON.""" return cls( Id=data.get("Id"), diff --git a/core/testcontainers/core/network.py b/core/testcontainers/core/network.py index f602a6eaa..d4def7c46 100644 --- a/core/testcontainers/core/network.py +++ b/core/testcontainers/core/network.py @@ -12,7 +12,7 @@ # under the License. import uuid from types import TracebackType -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Self from testcontainers.core.docker_client import DockerClient @@ -52,11 +52,11 @@ def connect(self, container_id: str, network_aliases: Optional[list[str]] = None def remove(self) -> None: self._unwrap_network.remove() - def create(self) -> "Network": + def create(self) -> Self: self._network = self._docker.client_networks_create(self.name, self._docker_network_kw) return self - def __enter__(self) -> "Network": + def __enter__(self) -> Self: return self.create() def __exit__( diff --git a/core/testcontainers/core/wait_strategies.py b/core/testcontainers/core/wait_strategies.py index f2c2d4778..8ea2c33d6 100644 --- a/core/testcontainers/core/wait_strategies.py +++ b/core/testcontainers/core/wait_strategies.py @@ -35,12 +35,10 @@ import time from datetime import timedelta from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Callable, Optional, Self, Union, cast from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen -from typing_extensions import Self - from testcontainers.compose import DockerCompose from testcontainers.core.utils import setup_logger @@ -193,7 +191,7 @@ def __init__(self, port: int, path: Optional[str] = "/") -> None: self._insecure_tls = False @classmethod - def from_url(cls, url: str) -> "HttpWaitStrategy": + def from_url(cls, url: str) -> Self: """ Create an HttpWaitStrategy from a URL string. @@ -219,7 +217,7 @@ def from_url(cls, url: str) -> "HttpWaitStrategy": return strategy - def for_status_code(self, code: int) -> "HttpWaitStrategy": + def for_status_code(self, code: int) -> Self: """ Add an acceptable status code. @@ -232,7 +230,7 @@ def for_status_code(self, code: int) -> "HttpWaitStrategy": self._status_codes.add(code) return self - def for_status_code_matching(self, predicate: Callable[[int], bool]) -> "HttpWaitStrategy": + def for_status_code_matching(self, predicate: Callable[[int], bool]) -> Self: """ Set a predicate to match status codes against. @@ -245,7 +243,7 @@ def for_status_code_matching(self, predicate: Callable[[int], bool]) -> "HttpWai self._status_code_predicate = predicate return self - def for_response_predicate(self, predicate: Callable[[str], bool]) -> "HttpWaitStrategy": + def for_response_predicate(self, predicate: Callable[[str], bool]) -> Self: """ Set a predicate to match response body against. @@ -258,7 +256,7 @@ def for_response_predicate(self, predicate: Callable[[str], bool]) -> "HttpWaitS self._response_predicate = predicate return self - def using_tls(self, insecure: bool = False) -> "HttpWaitStrategy": + def using_tls(self, insecure: bool = False) -> Self: """ Use HTTPS instead of HTTP. @@ -272,7 +270,7 @@ def using_tls(self, insecure: bool = False) -> "HttpWaitStrategy": self._insecure_tls = insecure return self - def with_header(self, name: str, value: str) -> "HttpWaitStrategy": + def with_header(self, name: str, value: str) -> Self: """ Add a header to the request. @@ -286,7 +284,7 @@ def with_header(self, name: str, value: str) -> "HttpWaitStrategy": self._headers[name] = value return self - def with_basic_credentials(self, username: str, password: str) -> "HttpWaitStrategy": + def with_basic_credentials(self, username: str, password: str) -> Self: """ Add basic auth credentials. @@ -300,7 +298,7 @@ def with_basic_credentials(self, username: str, password: str) -> "HttpWaitStrat self._basic_auth = (username, password) return self - def with_method(self, method: str) -> "HttpWaitStrategy": + def with_method(self, method: str) -> Self: """ Set the HTTP method to use. @@ -313,7 +311,7 @@ def with_method(self, method: str) -> "HttpWaitStrategy": self._method = method.upper() return self - def with_body(self, body: str) -> "HttpWaitStrategy": + def with_body(self, body: str) -> Self: """ Set the request body. diff --git a/core/testcontainers/socat/socat.py b/core/testcontainers/socat/socat.py index bf6307e95..a48a6ea70 100644 --- a/core/testcontainers/socat/socat.py +++ b/core/testcontainers/socat/socat.py @@ -13,7 +13,7 @@ import random import socket import string -from typing import Any, Optional +from typing import Any, Optional, Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -46,7 +46,7 @@ def __init__( super().__init__(image=image, **kwargs) - def with_target(self, exposed_port: int, host: str, internal_port: Optional[int] = None) -> "SocatContainer": + def with_target(self, exposed_port: int, host: str, internal_port: Optional[int] = None) -> Self: """ Add a target to forward connections from the exposed port to the given host and port. @@ -77,7 +77,7 @@ def _configure(self) -> None: self.with_command(f'-c "{command}"') - def start(self) -> "SocatContainer": + def start(self) -> Self: super().start() self._connect() return self diff --git a/modules/azurite/testcontainers/azurite/__init__.py b/modules/azurite/testcontainers/azurite/__init__.py index 3cd755f34..f94d7b024 100644 --- a/modules/azurite/testcontainers/azurite/__init__.py +++ b/modules/azurite/testcontainers/azurite/__init__.py @@ -12,7 +12,7 @@ # under the License. import enum import os -from typing import Optional +from typing import Optional, Self from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -217,7 +217,7 @@ def __get_external_connection_string(self) -> str: return connection_string - def start(self) -> "AzuriteContainer": + def start(self) -> Self: super().start() self._connect() return self diff --git a/modules/chroma/testcontainers/chroma/__init__.py b/modules/chroma/testcontainers/chroma/__init__.py index 358351b82..275c4e085 100644 --- a/modules/chroma/testcontainers/chroma/__init__.py +++ b/modules/chroma/testcontainers/chroma/__init__.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Self from requests import ConnectionError, get @@ -73,7 +73,7 @@ def _healthcheck(self) -> None: response: Response = get(url) response.raise_for_status() - def start(self) -> "ChromaContainer": + def start(self) -> Self: """This method starts the Chroma container and runs the healthcheck to verify that the container is ready to use.""" super().start() diff --git a/modules/generic/testcontainers/generic/server.py b/modules/generic/testcontainers/generic/server.py index efbd343a9..4ee43dd74 100644 --- a/modules/generic/testcontainers/generic/server.py +++ b/modules/generic/testcontainers/generic/server.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Union, Self import httpx @@ -53,7 +53,7 @@ def _create_connection_url(self) -> str: url = f"http://{host}:{exposed_port}" return url - def start(self) -> "ServerContainer": + def start(self) -> Self: super().start() self._connect() return self diff --git a/modules/generic/testcontainers/generic/sql.py b/modules/generic/testcontainers/generic/sql.py index c7ed755ed..93652163f 100644 --- a/modules/generic/testcontainers/generic/sql.py +++ b/modules/generic/testcontainers/generic/sql.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Optional +from typing import Any, Optional, Self from urllib.parse import quote, urlencode from testcontainers.core.container import DockerContainer @@ -83,7 +83,7 @@ def _create_connection_url( return url - def start(self) -> "SqlContainer": + def start(self) -> Self: """ Start the database container and perform initialization. diff --git a/modules/influxdb/testcontainers/influxdb.py b/modules/influxdb/testcontainers/influxdb.py index d8956e992..86ada5efd 100644 --- a/modules/influxdb/testcontainers/influxdb.py +++ b/modules/influxdb/testcontainers/influxdb.py @@ -27,7 +27,7 @@ so you won't have to install dependencies that you do not need """ -from typing import Optional +from typing import Optional, Self from requests import get from requests.exceptions import ConnectionError, ReadTimeout @@ -90,7 +90,7 @@ def get_influxdb_version(self) -> str: return self._health_check().get("version") - def start(self) -> "InfluxDbContainer": + def start(self) -> Self: """ Spawns a container of the InfluxDB Docker image, ready to be used. """ diff --git a/modules/influxdb/testcontainers/influxdb1/__init__.py b/modules/influxdb/testcontainers/influxdb1/__init__.py index 81f21c163..de97a4643 100644 --- a/modules/influxdb/testcontainers/influxdb1/__init__.py +++ b/modules/influxdb/testcontainers/influxdb1/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -from typing import Optional +from typing import Optional, Self from influxdb import InfluxDBClient @@ -58,7 +58,7 @@ def get_client(self, **client_kwargs): return InfluxDBClient(self.get_container_host_ip(), self.get_exposed_port(self.container_port), **client_kwargs) - def start(self) -> "InfluxDb1Container": + def start(self) -> Self: """ Overridden for better typing reason """ diff --git a/modules/influxdb/testcontainers/influxdb2/__init__.py b/modules/influxdb/testcontainers/influxdb2/__init__.py index bd33d7fc4..93d6f2f72 100644 --- a/modules/influxdb/testcontainers/influxdb2/__init__.py +++ b/modules/influxdb/testcontainers/influxdb2/__init__.py @@ -12,7 +12,7 @@ # under the License. from os import getenv -from typing import Optional +from typing import Optional, Self from influxdb_client import InfluxDBClient, Organization @@ -70,7 +70,7 @@ def __init__( if env_value: self.with_env(env_key, env_value) - def start(self) -> "InfluxDb2Container": + def start(self) -> Self: """ Overridden for better typing reason """ diff --git a/modules/k3s/testcontainers/k3s/__init__.py b/modules/k3s/testcontainers/k3s/__init__.py index fbdeefee3..ff9a4d11d 100644 --- a/modules/k3s/testcontainers/k3s/__init__.py +++ b/modules/k3s/testcontainers/k3s/__init__.py @@ -12,6 +12,7 @@ # under the License. import logging +from typing import Self from testcontainers.core.config import testcontainers_config from testcontainers.core.container import DockerContainer @@ -53,7 +54,7 @@ def __init__(self, image="rancher/k3s:latest", enable_cgroup_mount=True, **kwarg def _connect(self) -> None: wait_for_logs(self, predicate="Node controller sync successful", timeout=testcontainers_config.timeout) - def start(self) -> "K3SContainer": + def start(self) -> Self: super().start() self._connect() return self diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/modules/kafka/testcontainers/kafka/__init__.py index 315c16ce5..e7d0833d1 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/modules/kafka/testcontainers/kafka/__init__.py @@ -5,8 +5,7 @@ from io import BytesIO from os import environ from textwrap import dedent - -from typing_extensions import Self +from typing import Self from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -183,7 +182,7 @@ def tc_start(self) -> None: ) self.create_file(data, KafkaContainer.TC_START_SCRIPT) - def start(self, timeout: int = 30) -> "KafkaContainer": + def start(self, timeout: int = 30) -> Self: script = KafkaContainer.TC_START_SCRIPT command = f'sh -c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' self.configure() diff --git a/modules/kafka/testcontainers/kafka/_redpanda.py b/modules/kafka/testcontainers/kafka/_redpanda.py index a8adc0e03..1fff903eb 100644 --- a/modules/kafka/testcontainers/kafka/_redpanda.py +++ b/modules/kafka/testcontainers/kafka/_redpanda.py @@ -1,9 +1,10 @@ +from io import BytesIO import os.path import re import tarfile -import time -from io import BytesIO from textwrap import dedent +import time +from typing import Self from testcontainers.core.container import DockerContainer from testcontainers.core.wait_strategies import LogMessageWaitStrategy @@ -66,7 +67,7 @@ def tc_start(self) -> None: self.create_file(data, RedpandaContainer.TC_START_SCRIPT) - def start(self, timeout=10) -> "RedpandaContainer": + def start(self, timeout=10) -> Self: script = RedpandaContainer.TC_START_SCRIPT command = f'-c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' self.with_command(command) diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/modules/keycloak/testcontainers/keycloak/__init__.py index 044ba0b2b..a6465c62e 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/modules/keycloak/testcontainers/keycloak/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. import os -from typing import Optional +from typing import Optional, Self import requests @@ -109,12 +109,12 @@ def _readiness_probe(self) -> None: wait_for_logs(self, "started in \\d+\\.\\d+s") wait_for_logs(self, "Created temporary admin user|Added user '") - def start(self) -> "KeycloakContainer": + def start(self) -> Self: super().start() self._readiness_probe() return self - def with_realm_import_file(self, realm_import_file: str) -> "KeycloakContainer": + def with_realm_import_file(self, realm_import_file: str) -> Self: file = os.path.abspath(realm_import_file) if not os.path.exists(file): raise FileNotFoundError(f"Realm file {file} does not exist") @@ -122,7 +122,7 @@ def with_realm_import_file(self, realm_import_file: str) -> "KeycloakContainer": self.has_realm_imports = True return self - def with_realm_import_folder(self, realm_import_folder: str) -> "KeycloakContainer": + def with_realm_import_folder(self, realm_import_folder: str) -> Self: folder = os.path.abspath(realm_import_folder) if not os.path.exists(folder): raise FileNotFoundError(f"Realm folder {folder} does not exist") diff --git a/modules/localstack/testcontainers/localstack/__init__.py b/modules/localstack/testcontainers/localstack/__init__.py index 15cabeab6..6293af194 100644 --- a/modules/localstack/testcontainers/localstack/__init__.py +++ b/modules/localstack/testcontainers/localstack/__init__.py @@ -12,7 +12,7 @@ # under the License. import functools as ft import os -from typing import Any, Optional +from typing import Any, Optional, Self import boto3 @@ -52,7 +52,7 @@ def __init__( self.with_env("AWS_ACCESS_KEY_ID", "testcontainers-localstack") self.with_env("AWS_SECRET_ACCESS_KEY", "testcontainers-localstack") - def with_services(self, *services) -> "LocalStackContainer": + def with_services(self, *services) -> Self: """ Restrict what services to run. By default all localstack services are launched. @@ -85,7 +85,7 @@ def get_client(self, name, **kwargs) -> Any: kwargs_.update(kwargs) return boto3.client(name, **kwargs_) - def start(self, timeout: float = 60) -> "LocalStackContainer": + def start(self, timeout: float = 60) -> Self: super().start() wait_for_logs(self, r"Ready\.\n", timeout=timeout) return self diff --git a/modules/memcached/testcontainers/memcached/__init__.py b/modules/memcached/testcontainers/memcached/__init__.py index 6da409e06..3d2a1bd66 100644 --- a/modules/memcached/testcontainers/memcached/__init__.py +++ b/modules/memcached/testcontainers/memcached/__init__.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. import socket +from typing import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -50,7 +51,7 @@ def _connect(self): if len(data) == 0: raise MemcachedNotReady("Memcached not ready yet") - def start(self): + def start(self) -> Self: super().start() self._connect() return self diff --git a/modules/milvus/testcontainers/milvus/__init__.py b/modules/milvus/testcontainers/milvus/__init__.py index 2a1534146..8277bccda 100644 --- a/modules/milvus/testcontainers/milvus/__init__.py +++ b/modules/milvus/testcontainers/milvus/__init__.py @@ -12,6 +12,7 @@ # under the License. import requests +from typing import Self from testcontainers.core.config import testcontainers_config as c from testcontainers.core.generic import DockerContainer @@ -75,7 +76,7 @@ def _healthcheck(self) -> None: response = requests.get(f"{healthcheck_url}/healthz", timeout=1) response.raise_for_status() - def start(self) -> "MilvusContainer": + def start(self) -> Self: """This method starts the Milvus container and runs the healthcheck to verify that the container is ready to use.""" self.with_command(self.cmd) diff --git a/modules/mqtt/testcontainers/mqtt/__init__.py b/modules/mqtt/testcontainers/mqtt/__init__.py index 8321d0d06..2c101c878 100644 --- a/modules/mqtt/testcontainers/mqtt/__init__.py +++ b/modules/mqtt/testcontainers/mqtt/__init__.py @@ -12,9 +12,7 @@ # under the License. from pathlib import Path -from typing import TYPE_CHECKING, Optional - -from typing_extensions import Self +from typing import TYPE_CHECKING, Optional, Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs diff --git a/modules/nats/testcontainers/nats/__init__.py b/modules/nats/testcontainers/nats/__init__.py index a900036dc..ef6a499d7 100644 --- a/modules/nats/testcontainers/nats/__init__.py +++ b/modules/nats/testcontainers/nats/__init__.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -72,7 +73,7 @@ def nats_host_and_port(self) -> tuple[str, int]: def nats_management_uri(self) -> str: return f"nats://{self.get_container_host_ip()}:{self.get_exposed_port(self.management_port)}" - def start(self) -> "NatsContainer": + def start(self) -> Self: super().start() self._healthcheck() return self diff --git a/modules/nginx/testcontainers/nginx/__init__.py b/modules/nginx/testcontainers/nginx/__init__.py index ecf4c072e..5418aba53 100644 --- a/modules/nginx/testcontainers/nginx/__init__.py +++ b/modules/nginx/testcontainers/nginx/__init__.py @@ -10,6 +10,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from typing import Self + import urllib.error import urllib.parse import urllib.request @@ -26,7 +28,7 @@ def __init__(self, image: str = "nginx:latest", port: int = 80, **kwargs) -> Non self.port = port self.with_exposed_ports(self.port) - def start(self) -> "NginxContainer": + def start(self) -> Self: super().start() host = self.get_container_host_ip() diff --git a/modules/ollama/testcontainers/ollama/__init__.py b/modules/ollama/testcontainers/ollama/__init__.py index 002b02d61..c3067dd51 100644 --- a/modules/ollama/testcontainers/ollama/__init__.py +++ b/modules/ollama/testcontainers/ollama/__init__.py @@ -12,7 +12,7 @@ # under the License. from os import PathLike -from typing import Any, Optional, TypedDict, Union +from typing import Any, Optional, Self, TypedDict, Union from docker.types.containers import DeviceRequest from requests import get @@ -103,7 +103,7 @@ def _check_and_add_gpu_capabilities(self): if "nvidia" in info["Runtimes"]: self._kwargs = {**self._kwargs, "device_requests": [DeviceRequest(count=-1, capabilities=[["gpu"]])]} - def start(self) -> "OllamaContainer": + def start(self) -> Self: """ Start the Ollama server """ diff --git a/modules/openfga/testcontainers/openfga/__init__.py b/modules/openfga/testcontainers/openfga/__init__.py index 56192aebb..d1aefc4dc 100644 --- a/modules/openfga/testcontainers/openfga/__init__.py +++ b/modules/openfga/testcontainers/openfga/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -from typing import Optional +from typing import Optional, Self import requests @@ -82,7 +82,7 @@ def get_api_url(self) -> str: def _readiness_probe(self) -> None: self.exec(["grpc_health_probe", "-addr=0.0.0.0:8081"]) # from chart - def start(self) -> "OpenFGAContainer": + def start(self) -> Self: super().start() self._readiness_probe() return self diff --git a/modules/opensearch/testcontainers/opensearch/__init__.py b/modules/opensearch/testcontainers/opensearch/__init__.py index 736bd98b9..627c2f23a 100644 --- a/modules/opensearch/testcontainers/opensearch/__init__.py +++ b/modules/opensearch/testcontainers/opensearch/__init__.py @@ -1,4 +1,5 @@ from contextlib import suppress +from typing import Self from opensearchpy import OpenSearch from opensearchpy.exceptions import ConnectionError, TransportError @@ -113,7 +114,7 @@ def _healthcheck(self) -> None: client: OpenSearchContainer = self.get_client() client.cluster.health(wait_for_status="green") - def start(self) -> "OpenSearchContainer": + def start(self) -> Self: """This method starts the OpenSearch container and runs the healthcheck to verify that the container is ready to use.""" super().start() diff --git a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py index 4a1911f3d..db07f5ce1 100644 --- a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/modules/rabbitmq/testcontainers/rabbitmq/__init__.py @@ -1,5 +1,5 @@ import os -from typing import Optional +from typing import Optional, Self import pika @@ -78,7 +78,7 @@ def get_connection_params(self) -> pika.ConnectionParameters: credentials=credentials, ) - def start(self) -> "RabbitMqContainer": + def start(self) -> Self: """Start the test container.""" super().start() self.readiness_probe() diff --git a/modules/registry/testcontainers/registry/__init__.py b/modules/registry/testcontainers/registry/__init__.py index 59a888904..8cd46dc72 100644 --- a/modules/registry/testcontainers/registry/__init__.py +++ b/modules/registry/testcontainers/registry/__init__.py @@ -1,7 +1,7 @@ import time from io import BytesIO from tarfile import TarFile, TarInfo -from typing import TYPE_CHECKING, Any, Optional +from typing import Any, Optional, Self, TYPE_CHECKING import bcrypt from requests import get @@ -62,7 +62,7 @@ def _readiness_probe(self) -> None: response: Response = get(url, timeout=1) response.raise_for_status() - def start(self) -> "DockerRegistryContainer": + def start(self) -> Self: if self.username and self.password: self.with_env("REGISTRY_AUTH_HTPASSWD_REALM", "local-registry") self.with_env("REGISTRY_AUTH_HTPASSWD_PATH", self.credentials_path) diff --git a/modules/scylla/testcontainers/scylla/__init__.py b/modules/scylla/testcontainers/scylla/__init__.py index 6d79ec165..2a2eb74e9 100644 --- a/modules/scylla/testcontainers/scylla/__init__.py +++ b/modules/scylla/testcontainers/scylla/__init__.py @@ -1,3 +1,5 @@ +from typing import Self + from testcontainers.core.generic import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -32,7 +34,7 @@ def _connect(self): cluster = self.get_cluster() cluster.connect() - def start(self): + def start(self) -> Self: super().start() self._connect() return self diff --git a/modules/selenium/testcontainers/selenium/__init__.py b/modules/selenium/testcontainers/selenium/__init__.py index 53305ef93..58229f04c 100644 --- a/modules/selenium/testcontainers/selenium/__init__.py +++ b/modules/selenium/testcontainers/selenium/__init__.py @@ -11,10 +11,9 @@ # License for the specific language governing permissions and limitations # under the License. from pathlib import Path -from typing import Any, Optional +from typing import Any, Optional, Self import urllib3 -from typing_extensions import Self from selenium import webdriver from selenium.webdriver.common.options import ArgOptions @@ -102,7 +101,7 @@ def with_video(self, image: Optional[str] = None, video_path: Optional[Path] = N return self - def start(self) -> "DockerContainer": + def start(self) -> Self: if not self.video: super().start() return self diff --git a/modules/selenium/testcontainers/selenium/video.py b/modules/selenium/testcontainers/selenium/video.py index debf098db..0251d5720 100644 --- a/modules/selenium/testcontainers/selenium/video.py +++ b/modules/selenium/testcontainers/selenium/video.py @@ -10,7 +10,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from typing import Optional +from typing import Optional, Self from testcontainers.core.container import DockerContainer @@ -26,14 +26,14 @@ def __init__(self, image: Optional[str] = None, **kwargs) -> None: self.image = image or VIDEO_DEFAULT_IMAGE super().__init__(image=self.image, **kwargs) - def set_video_name(self, video_name: str) -> "DockerContainer": + def set_video_name(self, video_name: str) -> Self: self.with_env("FILE_NAME", video_name) return self - def set_videos_host_path(self, host_path: str) -> "DockerContainer": + def set_videos_host_path(self, host_path: str) -> Self: self.with_volume_mapping(host_path, "/videos", "rw") return self - def set_selenium_container_host(self, host: str) -> "DockerContainer": + def set_selenium_container_host(self, host: str) -> Self: self.with_env("DISPLAY_CONTAINER_NAME", host) return self diff --git a/modules/valkey/testcontainers/valkey/__init__.py b/modules/valkey/testcontainers/valkey/__init__.py index 7237a64fe..50ac82589 100644 --- a/modules/valkey/testcontainers/valkey/__init__.py +++ b/modules/valkey/testcontainers/valkey/__init__.py @@ -11,6 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import Self + from testcontainers.core.container import DockerContainer from testcontainers.core.wait_strategies import ExecWaitStrategy @@ -31,7 +33,7 @@ def __init__(self, image: str = f"{_BASE_IMAGE}:latest", port: int = 6379, **kwa self.with_exposed_ports(self.port) self.waiting_for(ExecWaitStrategy(["valkey-cli", "ping"])) - def with_password(self, password: str) -> "ValkeyContainer": + def with_password(self, password: str) -> Self: """ Configure authentication for Valkey. @@ -46,7 +48,7 @@ def with_password(self, password: str) -> "ValkeyContainer": self.waiting_for(ExecWaitStrategy(["valkey-cli", "-a", password, "ping"])) return self - def with_image_tag(self, tag: str) -> "ValkeyContainer": + def with_image_tag(self, tag: str) -> Self: """ Specify Valkey version. @@ -60,7 +62,7 @@ def with_image_tag(self, tag: str) -> "ValkeyContainer": self.image = f"{base_image}:{tag}" return self - def with_bundle(self) -> "ValkeyContainer": + def with_bundle(self) -> Self: """ Enable all modules by switching to valkey-bundle image. diff --git a/modules/vault/testcontainers/vault/__init__.py b/modules/vault/testcontainers/vault/__init__.py index c89b27ea5..1c9934577 100644 --- a/modules/vault/testcontainers/vault/__init__.py +++ b/modules/vault/testcontainers/vault/__init__.py @@ -11,6 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import Self + from http.client import HTTPException from urllib.error import URLError from urllib.request import urlopen @@ -69,7 +71,7 @@ def _healthcheck(self) -> None: if res.status > 299: raise HTTPException() - def start(self) -> "VaultContainer": + def start(self) -> Self: super().start() self._healthcheck() return self