diff --git a/coolamqp/__init__.py b/coolamqp/__init__.py index 955cb63b74128c1201b505e06bf6372b4f688040..09e082214690cacdfe7243b72f6bb09bce711a7a 100644 --- a/coolamqp/__init__.py +++ b/coolamqp/__init__.py @@ -1 +1 @@ -__version__ = '1.4.4a1' +__version__ = '1.5.0a1' diff --git a/coolamqp/clustering/cluster.py b/coolamqp/clustering/cluster.py index 80e5864427bd68f5c2b5988631489b7cc8e639ea..a3ec6e8582d405a74b7e9ee11a4e76431ed1b76b 100644 --- a/coolamqp/clustering/cluster.py +++ b/coolamqp/clustering/cluster.py @@ -348,6 +348,13 @@ class Cluster(object): raise ConnectionDead( '[%s] Could not connect within %s seconds' % (self.name, timeout,)) + @property + def properties(self): + """ + Return a :class:`coolamqp.object.ServerProperties` if a connection was established + """ + return self.snr.properties + def shutdown(self, wait=True): # type: (bool) -> None """ Terminate all connections, release resources - finish the job. diff --git a/coolamqp/clustering/single.py b/coolamqp/clustering/single.py index cc86b45a28e7c7c924b162b084484d4f9879aaa7..1c856da672982b3ddd0f2604da30caa6ea5a7e32 100644 --- a/coolamqp/clustering/single.py +++ b/coolamqp/clustering/single.py @@ -18,6 +18,10 @@ class SingleNodeReconnector(object): Connection to one node. It will do it's best to remain alive. """ + @property + def properties(self): + return self.connection.properties + def __init__(self, node_def, # type: coolamqp.objects.NodeDefinition attache_group, # type: coolamqp.attaches.AttacheGroup listener_thread, # type: coolamqp.uplink.ListenerThread diff --git a/coolamqp/objects.py b/coolamqp/objects.py index 675f7416778545fa91a7f9a2900ca4e56b3c39cd..eb6731d1bc2d4658705a259d1299f58eef042c1c 100644 --- a/coolamqp/objects.py +++ b/coolamqp/objects.py @@ -10,7 +10,6 @@ import warnings import six -from coolamqp.framing.base import AMQPFrame from coolamqp.framing.definitions import \ BasicContentPropertyList as MessageProperties from coolamqp.framing.field_table import get_type_for @@ -232,6 +231,37 @@ class Exchange(object): Exchange.direct = Exchange() + +class ServerProperties(object): + """ + An object describing properties of the target server. + + :ivar version: tuple of (major version, minor version) + :ivar properties: dictionary of properties (key str, value any) + :ivar mechanisms: a list of strings, supported auth mechanisms + :ivar locales: locale in use + """ + + __slots__ = ('version', 'properties', 'mechanisms', 'locales') + + def __init__(self, data): + self.version = data.version_major, data.version_minor + self.properties = {} + for prop_name, prop_value in data.server_properties: + prop_name = toutf8(prop_name) + prop_value = prop_value[0] + if isinstance(prop_value, memoryview): + prop_value = prop_value.tobytes().decode('utf-8') + elif isinstance(prop_value, list): + prop_value = [toutf8(prop[0]) for prop in prop_value] + self.properties[prop_name] = prop_value + self.mechanisms = data.mechanisms.tobytes().decode('utf-8').split(' ') + self.locales = data.locales.tobytes().decode('utf-8') + + def __str__(self): + return '%s %s %s %s' % (self.version, repr(self.properties), self.mechanisms, self.locales) + + class Queue(object): """ This object represents a Queue that applications consume from or publish to. diff --git a/coolamqp/uplink/connection/connection.py b/coolamqp/uplink/connection/connection.py index bc157f117b765db7ad14049ca1d0ac9fadf9e225..0974f5cf876993f70ea46bdd7f81961ed42c5043 100644 --- a/coolamqp/uplink/connection/connection.py +++ b/coolamqp/uplink/connection/connection.py @@ -119,6 +119,7 @@ class Connection(object): self.frame_max = None self.heartbeat = None self.extensions = [] + self.properties = None # To be filled in later self.listener_socket = None diff --git a/coolamqp/uplink/handshake.py b/coolamqp/uplink/handshake.py index acfb3b7c230f690c43b6481c73ee9712dbdf9acb..99d99bbfc7e2bfc67fcadd5c15b38b4dd59b858d 100644 --- a/coolamqp/uplink/handshake.py +++ b/coolamqp/uplink/handshake.py @@ -13,6 +13,8 @@ from coolamqp.framing.definitions import ConnectionStart, ConnectionStartOk, \ from coolamqp.framing.frames import AMQPMethodFrame from coolamqp.uplink.connection.states import ST_ONLINE from coolamqp.uplink.heartbeat import Heartbeater +from coolamqp.objects import ServerProperties + from coolamqp import __version__ PUBLISHER_CONFIRMS = b'publisher_confirms' @@ -100,7 +102,7 @@ class Handshaker(object): if label in SUPPORTED_EXTENSIONS: if fv[0]: self.connection.extensions.append(label) - + self.connection.properties = ServerProperties(payload) self.connection.watchdog(WATCHDOG_TIMEOUT, self.on_watchdog) self.connection.watch_for_method(0, ConnectionTune, self.on_connection_tune) diff --git a/docs/basics.rst b/docs/basics.rst index 81c42ac2d899677835244a326d4c51088565b44a..1531b9beda10a3a89a4b7a9b36089d217a0843fb 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -34,3 +34,11 @@ Take care, as :class:`~coolamqp.objects.MessageProperties` will hash the keys found and store it within non-GCable memory. So each "variant" of message properties encountered will be compiled as a separate class. +Who am I talking to? +-------------------- + +:class:`coolamqp.clustering.Cluster` has a nice property, that will return None until the connection is established. +If it is, it will return something like this: + +.. autoclass:: coolamqp.objects.ServerProperties + :members: diff --git a/docs/conf.py b/docs/conf.py index 3c2a143d22cd9bbe5dcc63441041541f2a4e1ee2..29896f768b9948b827dc234c7c95576286d2306a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,7 +48,7 @@ master_doc = 'index' # General information about the project. project = u'CoolAMQP' -copyright = u'2016-2020, SMOK sp. z o. o.' +copyright = u'2016-2024, SMOK sp. z o. o.' author = u'SMOK sp. z o. o.' # The version info for the project you're documenting, acts as replacement for diff --git a/tests/test_clustering/test_a.py b/tests/test_clustering/test_a.py index 8383c0548cc42a4dce52247fb0a226b30aa9f20c..27f5d69e58d3fe620b7cc85764546fac9d67caf6 100644 --- a/tests/test_clustering/test_a.py +++ b/tests/test_clustering/test_a.py @@ -23,11 +23,14 @@ logging.basicConfig(level=logging.DEBUG) class TestA(unittest.TestCase): def setUp(self): self.c = Cluster([NODE]) - self.c.start() + self.c.start(timeout=20) def tearDown(self): self.c.shutdown() + def test_properties(self): + self.assertEqual(self.c.properties.properties['product'], 'RabbitMQ') + def test_queue_bind(self): queue = Queue('my-queue') exchange = Exchange('my-exchange', type='topic') diff --git a/tests/test_clustering/test_things.py b/tests/test_clustering/test_things.py index 98ee35938b1ea30c4a8c490586c97fbe94e362b4..e49fa12b7ddad7d9b0e3bcd81409dc58a1e47636 100644 --- a/tests/test_clustering/test_things.py +++ b/tests/test_clustering/test_things.py @@ -77,7 +77,7 @@ class TestConnecting(unittest.TestCase): def test_start_called_multiple_times(self): c = Cluster([NODE]) - c.start(wait=True) + c.start(wait=True, timeout=20) self.assertRaises(RuntimeError, lambda: c.start()) c.shutdown(wait=True)