diff --git a/index.rst b/index.rst index 0f9cd5a3..3c7fcc14 100644 --- a/index.rst +++ b/index.rst @@ -27,6 +27,7 @@ testcontainers-python facilitates the use of Docker containers for functional an modules/kafka/README modules/keycloak/README modules/localstack/README + modules/memcached/README modules/minio/README modules/mongodb/README modules/mssql/README diff --git a/modules/memcached/README.rst b/modules/memcached/README.rst new file mode 100644 index 00000000..83cd9c82 --- /dev/null +++ b/modules/memcached/README.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.memcached.MemcachedContainer diff --git a/modules/memcached/setup.py b/modules/memcached/setup.py new file mode 100644 index 00000000..0f4dd323 --- /dev/null +++ b/modules/memcached/setup.py @@ -0,0 +1,17 @@ +from setuptools import find_namespace_packages, setup + +description = "Memcached component of testcontainers-python." + +setup( + name="testcontainers-memcached", + version="0.0.1rc1", + packages=find_namespace_packages(), + description=description, + long_description=description, + long_description_content_type="text/x-rst", + url="https://github.com/testcontainers/testcontainers-python", + install_requires=[ + "testcontainers-core", + ], + python_requires=">=3.7", +) diff --git a/modules/memcached/testcontainers/memcached/__init__.py b/modules/memcached/testcontainers/memcached/__init__.py new file mode 100644 index 00000000..6da409e0 --- /dev/null +++ b/modules/memcached/testcontainers/memcached/__init__.py @@ -0,0 +1,59 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import socket + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class MemcachedNotReady(Exception): + pass + + +class MemcachedContainer(DockerContainer): + """ + Test container for Memcached. The example below spins up a Memcached server + + Example: + + .. doctest:: + + >>> from testcontainers.memcached import MemcachedContainer + + >>> with MemcachedContainer() as memcached_container: + ... host, port = memcached_container.get_host_and_port() + """ + + def __init__(self, image="memcached:1", port_to_expose=11211, **kwargs): + super().__init__(image, **kwargs) + self.port_to_expose = port_to_expose + self.with_exposed_ports(port_to_expose) + + @wait_container_is_ready(MemcachedNotReady) + def _connect(self): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + host = self.get_container_host_ip() + port = int(self.get_exposed_port(self.port_to_expose)) + s.connect((host, port)) + s.sendall(b"stats\n\r") + data = s.recv(1024) + if len(data) == 0: + raise MemcachedNotReady("Memcached not ready yet") + + def start(self): + super().start() + self._connect() + return self + + def get_host_and_port(self): + return self.get_container_host_ip(), int(self.get_exposed_port(self.port_to_expose)) diff --git a/modules/memcached/tests/test_memcached.py b/modules/memcached/tests/test_memcached.py new file mode 100644 index 00000000..853ede40 --- /dev/null +++ b/modules/memcached/tests/test_memcached.py @@ -0,0 +1,28 @@ +import socket + +from testcontainers.memcached import MemcachedContainer + +import pytest + + +def test_memcached_host_and_exposed_port(): + with MemcachedContainer("memcached:1.6-alpine") as memcached: + host, port = memcached.get_host_and_port() + assert host == "localhost" + assert port != 11211 + + +@pytest.mark.parametrize("image", ["memcached:1.6-bookworm", "memcached:1.6-alpine"]) +def test_memcached_can_connect_and_retrieve_data(image): + with MemcachedContainer(image) as memcached: + host, port = memcached.get_host_and_port() + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((host, port)) + s.sendall(b"stats\n\r") + data = s.recv(1024) + assert len(data) > 0, "We should have received some data from memcached" + + pid_stat, uptime_stat, *_ = data.decode().split("\r\n") + + assert pid_stat.startswith("STAT pid") + assert uptime_stat.startswith("STAT uptime") diff --git a/poetry.lock b/poetry.lock index e4d5e1ee..272b0b23 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3412,13 +3412,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-oauthlib" -version = "1.4.0" +version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.4" files = [ - {file = "requests-oauthlib-1.4.0.tar.gz", hash = "sha256:acee623221e4a39abcbb919312c8ff04bd44e7e417087fb4bd5e2a2f53d5e79a"}, - {file = "requests_oauthlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:7a3130d94a17520169e38db6c8d75f2c974643788465ecc2e4b36d288bf13033"}, + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, ] [package.dependencies] @@ -4189,6 +4189,7 @@ k3s = ["kubernetes", "pyyaml"] kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] +memcached = [] minio = ["minio"] mongodb = ["pymongo"] mssql = ["pymssql", "sqlalchemy"] @@ -4211,4 +4212,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "54136d629f04e2b87cf7dc0905b0ce877b5db2eaf6e1d6616d7648d4b730a759" +content-hash = "95a2e0ef23d8dfb1cbc74d72f534028aeff5da8bc26cc194f464f6fe282ba38f" diff --git a/pyproject.toml b/pyproject.toml index 9570ce06..d658aab1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ packages = [ { include = "testcontainers", from = "modules/kafka" }, { include = "testcontainers", from = "modules/keycloak" }, { include = "testcontainers", from = "modules/localstack" }, + { include = "testcontainers", from = "modules/memcached" }, { include = "testcontainers", from = "modules/minio" }, { include = "testcontainers", from = "modules/mongodb" }, { include = "testcontainers", from = "modules/mssql" }, @@ -111,6 +112,7 @@ k3s = ["kubernetes", "pyyaml"] kafka = [] keycloak = ["python-keycloak"] localstack = ["boto3"] +memcached = [] minio = ["minio"] mongodb = ["pymongo"] mssql = ["sqlalchemy", "pymssql"]