Skip to content

Commit

Permalink
Merge branch 'master' into cassandra_scylla_support
Browse files Browse the repository at this point in the history
  • Loading branch information
fruch authored Jan 30, 2022
2 parents 33f3291 + cfd6b12 commit d3d1af6
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ docs/_build/
.idea/
.venv/
.testrepository/

# vscode:
.devcontainer/
.vscode/
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Currently available features:
* Microsoft SQL Server container
* Generic docker containers
* LocalStack
* RabbitMQ

Installation
------------
Expand Down Expand Up @@ -75,4 +76,4 @@ We recommend you use a `virtual environment <https://virtualenv.pypa.io/en/stabl
Adding requirements
^^^^^^^^^^^^^^^^^^^

We use :code:`pip-tools` to resolve and manage dependencies. If you need to add a dependency to testcontainers or one of the extras, run :code:`pip install pip-tools` followed by :code:`make requirements` to update the requirements files.
We use :code:`pip-tools` to resolve and manage dependencies. If you need to add a dependency to testcontainers or one of the extras, modify the :code:`setup.py` as well as the :code:`requirements.in` accordingly and then run :code:`pip install pip-tools` followed by :code:`make requirements` to update the requirements files.
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,cassandra,scylla]
-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,cassandra,scylla]
codecov>=2.1.0
flake8
pytest
Expand Down
2 changes: 2 additions & 0 deletions requirements/3.6.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ packaging==20.4
# sphinx
paramiko==2.7.1
# via docker
pika==1.2.0
# via testcontainers
pluggy==0.13.1
# via pytest
protobuf==3.13.0
Expand Down
2 changes: 2 additions & 0 deletions requirements/3.7.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ packaging==20.4
# sphinx
paramiko==2.7.1
# via docker
pika==1.2.0
# via testcontainers
pluggy==0.13.1
# via pytest
protobuf==3.13.0
Expand Down
2 changes: 2 additions & 0 deletions requirements/3.8.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ packaging==20.4
# sphinx
paramiko==2.7.1
# via docker
pika==1.2.0
# via testcontainers
pluggy==0.13.1
# via pytest
protobuf==3.13.0
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@
'mssqlserver': ['pyodbc'],
'neo4j': ['neo4j'],
'kafka': ['kafka-python'],
'rabbitmq': ['pika'],
'cassandra': ['cassandra-driver'],
'scylla': ['scylla-driver']
'scylla': ['scylla-driver'],
},
long_description_content_type="text/x-rst",
long_description=long_description,
Expand Down
3 changes: 2 additions & 1 deletion testcontainers/kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ def _connect(self):
raise KafkaError("Unable to connect with kafka container!")

def tc_start(self):
host = self.get_container_host_ip()
port = self.get_exposed_port(self.port_to_expose)
listeners = 'PLAINTEXT://localhost:{},BROKER://$(hostname -i):9092'.format(port)
listeners = 'PLAINTEXT://{}:{},BROKER://$(hostname -i):9092'.format(host, port)
data = (
dedent(
"""
Expand Down
88 changes: 88 additions & 0 deletions testcontainers/rabbitmq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
from typing import Optional

import pika
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_container_is_ready


class RabbitMqContainer(DockerContainer):
"""
Test container for RabbitMQ.
Example
-------
The example spins up a RabbitMQ broker and uses the `pika` client library
(https://pypi.org/project/pika/) establish a connection to the broker.
::
from testcontainer.rabbitmq import RabbitMqContainer
import pika
with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq:
connection = pika.BlockingConnection(rabbitmq.get_connection_params())
channel = connection.channel()
"""

RABBITMQ_NODE_PORT = os.environ.get("RABBITMQ_NODE_PORT", 5672)
RABBITMQ_DEFAULT_USER = os.environ.get("RABBITMQ_DEFAULT_USER", "guest")
RABBITMQ_DEFAULT_PASS = os.environ.get("RABBITMQ_DEFAULT_PASS", "guest")

def __init__(
self,
image: str = "rabbitmq:latest",
port: Optional[int] = None,
username: Optional[str] = None,
password: Optional[str] = None,
) -> None:
"""Initialize the RabbitMQ test container.
Args:
image (str, optional):
The docker image from docker hub. Defaults to "rabbitmq:latest".
port (int, optional):
The port to reach the AMQP API. Defaults to 5672.
username (str, optional):
Overwrite the default username which is "guest".
password (str, optional):
Overwrite the default username which is "guest".
"""
super(RabbitMqContainer, self).__init__(image=image)
self.RABBITMQ_NODE_PORT = port or int(self.RABBITMQ_NODE_PORT)
self.RABBITMQ_DEFAULT_USER = username or self.RABBITMQ_DEFAULT_USER
self.RABBITMQ_DEFAULT_PASS = password or self.RABBITMQ_DEFAULT_PASS

self.with_exposed_ports(self.RABBITMQ_NODE_PORT)
self.with_env("RABBITMQ_NODE_PORT", self.RABBITMQ_NODE_PORT)
self.with_env("RABBITMQ_DEFAULT_USER", self.RABBITMQ_DEFAULT_USER)
self.with_env("RABBITMQ_DEFAULT_PASS", self.RABBITMQ_DEFAULT_PASS)

@wait_container_is_ready()
def readiness_probe(self) -> bool:
"""Test if the RabbitMQ broker is ready."""
connection = pika.BlockingConnection(self.get_connection_params())
if connection.is_open:
connection.close()
return self
raise RuntimeError("Could not open connection to RabbitMQ broker.")

def get_connection_params(self) -> pika.ConnectionParameters:
"""
Get connection params as a pika.ConnectionParameters object.
For more details see:
https://pika.readthedocs.io/en/latest/modules/parameters.html
"""
credentials = pika.PlainCredentials(username=self.RABBITMQ_DEFAULT_USER,
password=self.RABBITMQ_DEFAULT_PASS)

return pika.ConnectionParameters(
host=self.get_container_host_ip(),
port=self.get_exposed_port(self.RABBITMQ_NODE_PORT),
credentials=credentials,
)

def start(self):
"""Start the test container."""
super().start()
self.readiness_probe()
return self
54 changes: 54 additions & 0 deletions tests/test_rabbitmq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import Optional
import json

import pika
import pytest
from testcontainers.rabbitmq import RabbitMqContainer

QUEUE = "test-q"
EXCHANGE = "test-exchange"
ROUTING_KEY = "test-route-key"
MESSAGE = {"hello": "world"}


@pytest.mark.parametrize(
"port,username,password",
[
(None, None, None), # use the defaults
(5673, None, None), # test with custom port
(None, "my_test_user", "my_secret_password"), # test with custom credentials
]
)
def test_docker_run_rabbitmq(
port: Optional[int],
username: Optional[str],
password: Optional[str]
):
"""Run rabbitmq test container and use it to deliver a simple message."""
kwargs = {}
if port is not None:
kwargs["port"] = port
if username is not None:
kwargs["username"] = username
if password is not None:
kwargs["password"] = password

rabbitmq_container = RabbitMqContainer("rabbitmq:latest", **kwargs)
with rabbitmq_container as rabbitmq:
# connect to rabbitmq:
connection_params = rabbitmq.get_connection_params()
connection = pika.BlockingConnection(connection_params)

# create exchange and queue:
channel = connection.channel()
channel.exchange_declare(exchange=EXCHANGE, exchange_type="topic")
channel.queue_declare(QUEUE, arguments={})
channel.queue_bind(QUEUE, EXCHANGE, ROUTING_KEY)

# pulish message:
encoded_message = json.dumps(MESSAGE)
channel.basic_publish(EXCHANGE, ROUTING_KEY, body=encoded_message)

_, _, body = channel.basic_get(queue=QUEUE)
received_message = json.loads(body.decode())
assert received_message == MESSAGE

0 comments on commit d3d1af6

Please sign in to comment.