Skip to content

Feature/nats/with get client #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions INDEX.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
modules/mongodb/README
modules/mssql/README
modules/mysql/README
modules/nats/README
modules/neo4j/README
modules/nginx/README
modules/opensearch/README
Expand Down
1 change: 1 addition & 0 deletions modules/nats/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.. autoclass:: testcontainers.nats.NatsContainer
75 changes: 75 additions & 0 deletions modules/nats/testcontainers/nats/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#
# 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.


from nats import connect as nats_connect
from nats.aio.client import Client as NATSClient
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs


class NatsContainer(DockerContainer):
"""
Nats container.

Example:

.. doctest::

>>> from testcontainers.nats import NatsContainer

>>> with NatsContainer() as nats_container:
... nc = nats_container.get_client()
"""

def __init__(
self,
image: str = "nats:latest",
client_port: int = 4222,
management_port: int = 8222,
expected_ready_log: str = "Server is ready",
ready_timeout_secs: int = 120,
**kwargs,
) -> None:
super().__init__(image, **kwargs)
self.client_port = client_port
self.management_port = management_port
self._expected_ready_log = expected_ready_log
self._ready_timeout_secs = max(ready_timeout_secs, 0)
self.with_exposed_ports(self.client_port, self.management_port)

@wait_container_is_ready()
def _healthcheck(self) -> None:
wait_for_logs(self, self._expected_ready_log, timeout=self._ready_timeout_secs)

def get_conn_string(self):
return f"nats://{self.get_container_host_ip()}:{self.get_exposed_port(self.client_port)}"

async def get_client(self, **kwargs) -> NATSClient:
"""
Get a nats client.

Args:
**kwargs: Keyword arguments passed to `redis.Redis`.

Returns:
client: Nats client to connect to the container.
"""
conn_string = self.get_conn_string()
client = await nats_connect(conn_string)
return client

def start(self) -> "NatsContainer":
super().start()
self._healthcheck()
return self
52 changes: 52 additions & 0 deletions modules/nats/tests/test_nats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from uuid import uuid4

import pytest
from nats.aio.client import Client as NATSClient

from testcontainers.nats import NatsContainer


@pytest.mark.asyncio
async def test_basic_publishing():
with NatsContainer() as container:
nc: NATSClient = await container.get_client()

topic = str(uuid4())

sub = await nc.subscribe(topic)
sent_message = b"Test-Containers"
await nc.publish(topic, b"Test-Containers")
received_msg = await sub.next_msg()
print("Received:", received_msg)
assert sent_message == received_msg.data
await nc.flush()
await nc.close()


@pytest.mark.asyncio
async def test_more_complex_example():
with NatsContainer() as container:
nc: NATSClient = await container.get_client()

await nc.publish("greet.joe", b"hello")

sub = await nc.subscribe("greet.*")

try:
await sub.next_msg(timeout=0.1)
except TimeoutError:
pass

await nc.publish("greet.joe", b"hello.joe")
await nc.publish("greet.pam", b"hello.pam")

first = await sub.next_msg(timeout=0.1)
assert b"hello.joe" == first.data

second = await sub.next_msg(timeout=0.1)
assert b"hello.pam" == second.data

await nc.publish("greet.bob", b"hello")

await sub.unsubscribe()
await nc.drain()
38 changes: 36 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ packages = [
{ include = "testcontainers", from = "modules/mongodb" },
{ include = "testcontainers", from = "modules/mssql" },
{ include = "testcontainers", from = "modules/mysql" },
{ include = "testcontainers", from = "modules/nats" },
{ include = "testcontainers", from = "modules/neo4j" },
{ include = "testcontainers", from = "modules/nginx" },
{ include = "testcontainers", from = "modules/opensearch" },
Expand Down Expand Up @@ -83,6 +84,7 @@ psycopg2-binary = { version = "*", optional = true }
pika = { version = "*", optional = true }
redis = { version = "*", optional = true }
selenium = { version = "*", optional = true }
nats-py = { version = "*", optional = true }

[tool.poetry.extras]
arangodb = ["python-arango"]
Expand All @@ -98,6 +100,7 @@ minio = ["minio"]
mongodb = ["pymongo"]
mssql = ["sqlalchemy", "pymssql"]
mysql = ["sqlalchemy", "pymysql"]
nats = ["nats-py"]
neo4j = ["neo4j"]
nginx = []
opensearch = ["opensearch-py"]
Expand All @@ -116,6 +119,7 @@ pytest-cov = "4.1.0"
sphinx = "^7.2.6"
twine = "^4.0.2"
anyio = "^4.3.0"
pytest-asyncio = "^0.23.5"

[[tool.poetry.source]]
name = "PyPI"
Expand Down Expand Up @@ -222,6 +226,7 @@ mypy_path = [
# "modules/mongodb",
# "modules/mssql",
# "modules/mysql",
# "modules/nats",
# "modules/neo4j",
# "modules/nginx",
# "modules/opensearch",
Expand Down