From c51695f321d13fd7baac3439b36ebb5cc28eaefa Mon Sep 17 00:00:00 2001 From: Martin Uhrin Date: Mon, 17 Jan 2022 22:25:42 +0100 Subject: [PATCH] `RmqCommunicator`: add the `server_properties` property (#107) This exposes the server properties as returned by the RMQ server when connecting to clients of the async and threaded RMQ communicators. This is useful for, amongst other things, checking the server version to ensure compatibility. --- kiwipy/rmq/communicator.py | 29 +++++++++++++++++++++++- kiwipy/rmq/threadcomms.py | 26 ++++++++++++++++++++- test/rmq/test_coroutine_communicator.py | 10 ++++++++ test/rmq/test_rmq_thread_communicator.py | 11 +++++++-- 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/kiwipy/rmq/communicator.py b/kiwipy/rmq/communicator.py index 9f40f57..e90a05f 100644 --- a/kiwipy/rmq/communicator.py +++ b/kiwipy/rmq/communicator.py @@ -4,7 +4,7 @@ import copy import logging import typing -from typing import Union, Optional +from typing import Union, Optional, Dict import shortuuid import aio_pika @@ -345,6 +345,33 @@ async def __aexit__(self, exc_type, exc_val, exc_tb): def __str__(self): return f'RMQCommunicator({self._connection})' + @property + def server_properties(self) -> Dict: + """ + A dictionary containing server properties as returned by the RMQ server at connection time. + + The details are defined by the RMQ standard and can be found here: + + https://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.start.server-properties + + The protocol states that this dictionary SHOULD contain at least: + 'host' - specifying the server host name or address + 'product' - giving the name of the server product + 'version' - giving the name of the server version + 'platform' - giving the name of the operating system + 'copyright' - if appropriate, and, + 'information' - giving other general information + + .. note:: In testing it seems like 'host' is not always returned. Host information may be found in + 'cluster_name' but clients shouldn't rely on this. + + :return: the server properties dictionary + """ + if self._connection is None: + return {} + + return self._connection.connection.server_properties + @property def loop(self): """Get the event loop instance driving this communicator connection.""" diff --git a/kiwipy/rmq/threadcomms.py b/kiwipy/rmq/threadcomms.py index b93da39..cfe3e0f 100644 --- a/kiwipy/rmq/threadcomms.py +++ b/kiwipy/rmq/threadcomms.py @@ -5,7 +5,7 @@ from concurrent.futures import Future as ThreadFuture import functools import logging -from typing import Union +from typing import Union, Dict import aio_pika import deprecation @@ -148,6 +148,30 @@ def start(self): def stop(self): self.close() + @property + def server_properties(self) -> Dict: + """ + A dictionary containing server properties as returned by the RMQ server at connection time. + + The details are defined by the RMQ standard and can be found here: + + https://www.rabbitmq.com/amqp-0-9-1-reference.html#connection.start.server-properties + + The protocol states that this dictionary SHOULD contain at least: + 'host' - specifying the server host name or address + 'product' - giving the name of the server product + 'version' - giving the name of the server version + 'platform' - giving the name of the operating system + 'copyright' - if appropriate, and, + 'information' - giving other general information + + .. note:: In testing it seems like 'host' is not always returned. Host information may be found in + 'cluster_name' but clients shouldn't rely on this. + + :return: the server properties dictionary + """ + return self._communicator.server_properties + def __enter__(self): return self diff --git a/test/rmq/test_coroutine_communicator.py b/test/rmq/test_coroutine_communicator.py index b4900af..b347645 100644 --- a/test/rmq/test_coroutine_communicator.py +++ b/test/rmq/test_coroutine_communicator.py @@ -197,3 +197,13 @@ def broadcast_subscriber(_comm, _body, _sender=None, _subject=None, _correlation # endregion + + +@pytest.mark.asyncio +async def test_server_properties(communicator: kiwipy.rmq.RmqCommunicator): + props = communicator.server_properties + assert isinstance(props, dict) + + assert props['product'] == b'RabbitMQ' + assert 'version' in props + assert props['platform'].startswith(b'Erlang') diff --git a/test/rmq/test_rmq_thread_communicator.py b/test/rmq/test_rmq_thread_communicator.py index 4c8605b..4643e4a 100644 --- a/test/rmq/test_rmq_thread_communicator.py +++ b/test/rmq/test_rmq_thread_communicator.py @@ -262,8 +262,15 @@ def test_jupyter_notebook(): fixture.diff_color_words = False fixture.diff_ignore = ('/metadata/language_info/version',) - # Express the path in a way that will work no matter where the tests are being ran and convert - # to str as py35 doesn't support Paths being passed to open() my_dir = pathlib.Path(__file__).parent with open(my_dir / pathlib.Path('notebooks/communicator.ipynb')) as handle: fixture.check(handle) + + +def test_server_properties(thread_communicator: kiwipy.rmq.RmqThreadCommunicator): + props = thread_communicator.server_properties + assert isinstance(props, dict) + + assert props['product'] == b'RabbitMQ' + assert 'version' in props + assert props['platform'].startswith(b'Erlang')