From 4133a705f34e98bece2d584b9acd0c9279a39634 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <pmaslanka@smok.co>
Date: Tue, 15 Oct 2024 15:57:59 +0000
Subject: [PATCH] fixes #2

---
 coolamqp/__init__.py                     |  2 +-
 coolamqp/clustering/cluster.py           |  7 ++++++
 coolamqp/clustering/single.py            |  4 +++
 coolamqp/objects.py                      | 32 +++++++++++++++++++++++-
 coolamqp/uplink/connection/connection.py |  1 +
 coolamqp/uplink/handshake.py             |  4 ++-
 docs/basics.rst                          |  8 ++++++
 docs/conf.py                             |  2 +-
 tests/test_clustering/test_a.py          |  5 +++-
 tests/test_clustering/test_things.py     |  2 +-
 10 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/coolamqp/__init__.py b/coolamqp/__init__.py
index 955cb63..09e0822 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 80e5864..a3ec6e8 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 cc86b45..1c856da 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 675f741..eb6731d 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 bc157f1..0974f5c 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 acfb3b7..99d99bb 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 81c42ac..1531b9b 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 3c2a143..29896f7 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 8383c05..27f5d69 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 98ee359..e49fa12 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)
 
-- 
GitLab