Skip to content

Commit d3d1af6

Browse files
authored
Merge branch 'master' into cassandra_scylla_support
2 parents 33f3291 + cfd6b12 commit d3d1af6

File tree

10 files changed

+159
-4
lines changed

10 files changed

+159
-4
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,7 @@ docs/_build/
6363
.idea/
6464
.venv/
6565
.testrepository/
66+
67+
# vscode:
68+
.devcontainer/
69+
.vscode/

README.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Currently available features:
2222
* Microsoft SQL Server container
2323
* Generic docker containers
2424
* LocalStack
25+
* RabbitMQ
2526

2627
Installation
2728
------------
@@ -75,4 +76,4 @@ We recommend you use a `virtual environment <https://virtualenv.pypa.io/en/stabl
7576
Adding requirements
7677
^^^^^^^^^^^^^^^^^^^
7778

78-
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.
79+
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.

requirements.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,cassandra,scylla]
1+
-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,cassandra,scylla]
22
codecov>=2.1.0
33
flake8
44
pytest

requirements/3.6.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ packaging==20.4
117117
# sphinx
118118
paramiko==2.7.1
119119
# via docker
120+
pika==1.2.0
121+
# via testcontainers
120122
pluggy==0.13.1
121123
# via pytest
122124
protobuf==3.13.0

requirements/3.7.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ packaging==20.4
117117
# sphinx
118118
paramiko==2.7.1
119119
# via docker
120+
pika==1.2.0
121+
# via testcontainers
120122
pluggy==0.13.1
121123
# via pytest
122124
protobuf==3.13.0

requirements/3.8.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ packaging==20.4
110110
# sphinx
111111
paramiko==2.7.1
112112
# via docker
113+
pika==1.2.0
114+
# via testcontainers
113115
pluggy==0.13.1
114116
# via pytest
115117
protobuf==3.13.0

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@
6464
'mssqlserver': ['pyodbc'],
6565
'neo4j': ['neo4j'],
6666
'kafka': ['kafka-python'],
67+
'rabbitmq': ['pika'],
6768
'cassandra': ['cassandra-driver'],
68-
'scylla': ['scylla-driver']
69+
'scylla': ['scylla-driver'],
6970
},
7071
long_description_content_type="text/x-rst",
7172
long_description=long_description,

testcontainers/kafka.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ def _connect(self):
4343
raise KafkaError("Unable to connect with kafka container!")
4444

4545
def tc_start(self):
46+
host = self.get_container_host_ip()
4647
port = self.get_exposed_port(self.port_to_expose)
47-
listeners = 'PLAINTEXT://localhost:{},BROKER://$(hostname -i):9092'.format(port)
48+
listeners = 'PLAINTEXT://{}:{},BROKER://$(hostname -i):9092'.format(host, port)
4849
data = (
4950
dedent(
5051
"""

testcontainers/rabbitmq.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import os
2+
from typing import Optional
3+
4+
import pika
5+
from testcontainers.core.container import DockerContainer
6+
from testcontainers.core.waiting_utils import wait_container_is_ready
7+
8+
9+
class RabbitMqContainer(DockerContainer):
10+
"""
11+
Test container for RabbitMQ.
12+
13+
Example
14+
-------
15+
The example spins up a RabbitMQ broker and uses the `pika` client library
16+
(https://pypi.org/project/pika/) establish a connection to the broker.
17+
::
18+
from testcontainer.rabbitmq import RabbitMqContainer
19+
import pika
20+
21+
with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq:
22+
23+
connection = pika.BlockingConnection(rabbitmq.get_connection_params())
24+
channel = connection.channel()
25+
"""
26+
27+
RABBITMQ_NODE_PORT = os.environ.get("RABBITMQ_NODE_PORT", 5672)
28+
RABBITMQ_DEFAULT_USER = os.environ.get("RABBITMQ_DEFAULT_USER", "guest")
29+
RABBITMQ_DEFAULT_PASS = os.environ.get("RABBITMQ_DEFAULT_PASS", "guest")
30+
31+
def __init__(
32+
self,
33+
image: str = "rabbitmq:latest",
34+
port: Optional[int] = None,
35+
username: Optional[str] = None,
36+
password: Optional[str] = None,
37+
) -> None:
38+
"""Initialize the RabbitMQ test container.
39+
40+
Args:
41+
image (str, optional):
42+
The docker image from docker hub. Defaults to "rabbitmq:latest".
43+
port (int, optional):
44+
The port to reach the AMQP API. Defaults to 5672.
45+
username (str, optional):
46+
Overwrite the default username which is "guest".
47+
password (str, optional):
48+
Overwrite the default username which is "guest".
49+
"""
50+
super(RabbitMqContainer, self).__init__(image=image)
51+
self.RABBITMQ_NODE_PORT = port or int(self.RABBITMQ_NODE_PORT)
52+
self.RABBITMQ_DEFAULT_USER = username or self.RABBITMQ_DEFAULT_USER
53+
self.RABBITMQ_DEFAULT_PASS = password or self.RABBITMQ_DEFAULT_PASS
54+
55+
self.with_exposed_ports(self.RABBITMQ_NODE_PORT)
56+
self.with_env("RABBITMQ_NODE_PORT", self.RABBITMQ_NODE_PORT)
57+
self.with_env("RABBITMQ_DEFAULT_USER", self.RABBITMQ_DEFAULT_USER)
58+
self.with_env("RABBITMQ_DEFAULT_PASS", self.RABBITMQ_DEFAULT_PASS)
59+
60+
@wait_container_is_ready()
61+
def readiness_probe(self) -> bool:
62+
"""Test if the RabbitMQ broker is ready."""
63+
connection = pika.BlockingConnection(self.get_connection_params())
64+
if connection.is_open:
65+
connection.close()
66+
return self
67+
raise RuntimeError("Could not open connection to RabbitMQ broker.")
68+
69+
def get_connection_params(self) -> pika.ConnectionParameters:
70+
"""
71+
Get connection params as a pika.ConnectionParameters object.
72+
For more details see:
73+
https://pika.readthedocs.io/en/latest/modules/parameters.html
74+
"""
75+
credentials = pika.PlainCredentials(username=self.RABBITMQ_DEFAULT_USER,
76+
password=self.RABBITMQ_DEFAULT_PASS)
77+
78+
return pika.ConnectionParameters(
79+
host=self.get_container_host_ip(),
80+
port=self.get_exposed_port(self.RABBITMQ_NODE_PORT),
81+
credentials=credentials,
82+
)
83+
84+
def start(self):
85+
"""Start the test container."""
86+
super().start()
87+
self.readiness_probe()
88+
return self

tests/test_rabbitmq.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from typing import Optional
2+
import json
3+
4+
import pika
5+
import pytest
6+
from testcontainers.rabbitmq import RabbitMqContainer
7+
8+
QUEUE = "test-q"
9+
EXCHANGE = "test-exchange"
10+
ROUTING_KEY = "test-route-key"
11+
MESSAGE = {"hello": "world"}
12+
13+
14+
@pytest.mark.parametrize(
15+
"port,username,password",
16+
[
17+
(None, None, None), # use the defaults
18+
(5673, None, None), # test with custom port
19+
(None, "my_test_user", "my_secret_password"), # test with custom credentials
20+
]
21+
)
22+
def test_docker_run_rabbitmq(
23+
port: Optional[int],
24+
username: Optional[str],
25+
password: Optional[str]
26+
):
27+
"""Run rabbitmq test container and use it to deliver a simple message."""
28+
kwargs = {}
29+
if port is not None:
30+
kwargs["port"] = port
31+
if username is not None:
32+
kwargs["username"] = username
33+
if password is not None:
34+
kwargs["password"] = password
35+
36+
rabbitmq_container = RabbitMqContainer("rabbitmq:latest", **kwargs)
37+
with rabbitmq_container as rabbitmq:
38+
# connect to rabbitmq:
39+
connection_params = rabbitmq.get_connection_params()
40+
connection = pika.BlockingConnection(connection_params)
41+
42+
# create exchange and queue:
43+
channel = connection.channel()
44+
channel.exchange_declare(exchange=EXCHANGE, exchange_type="topic")
45+
channel.queue_declare(QUEUE, arguments={})
46+
channel.queue_bind(QUEUE, EXCHANGE, ROUTING_KEY)
47+
48+
# pulish message:
49+
encoded_message = json.dumps(MESSAGE)
50+
channel.basic_publish(EXCHANGE, ROUTING_KEY, body=encoded_message)
51+
52+
_, _, body = channel.basic_get(queue=QUEUE)
53+
received_message = json.loads(body.decode())
54+
assert received_message == MESSAGE

0 commit comments

Comments
 (0)