[lxc-devel] [pylxd/master] Make ws4py an optional library.

rockstar on Github lxc-bot at linuxcontainers.org
Fri Sep 16 20:15:35 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 402 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160916/2c089fa6/attachment.bin>
-------------- next part --------------
From f58d24ed199f38b1e40ba0bc8c26b54b06ef9068 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Fri, 16 Sep 2016 14:12:23 -0600
Subject: [PATCH] Make ws4py an optional library.

Tests that require ws4py are skipped in environments that don't have
ws4py available.

Fixes #180
---
 pylxd/client.py                      | 10 ++++-
 pylxd/deprecated/connection.py       | 83 +++++++++++++++++++-----------------
 pylxd/models/container.py            | 12 +++++-
 pylxd/tests/models/test_container.py |  2 +
 pylxd/tests/test_client.py           |  7 +++
 pylxd/tests/testing.py               | 14 ++++++
 6 files changed, 87 insertions(+), 41 deletions(-)

diff --git a/pylxd/client.py b/pylxd/client.py
index e18028c..a4ee4f0 100644
--- a/pylxd/client.py
+++ b/pylxd/client.py
@@ -18,7 +18,12 @@
 import requests
 import requests_unixsocket
 from six.moves.urllib import parse
-from ws4py.client import WebSocketBaseClient
+try:
+    from ws4py.client import WebSocketBaseClient
+    _ws4py_installed = True
+except ImportError:
+    WebSocketBaseClient = object
+    _ws4py_installed = True
 
 from pylxd import exceptions, managers
 
@@ -238,6 +243,9 @@ def events(self, websocket_client=None):
         specified for implementation-specific handling
         of events as they occur.
         """
+        if not _ws4py_installed:
+            raise ValueError(
+                'This feature requires the optional ws4py library.')
         if websocket_client is None:
             websocket_client = _WebsocketClient
 
diff --git a/pylxd/deprecated/connection.py b/pylxd/deprecated/connection.py
index 65adbf0..b86ad94 100644
--- a/pylxd/deprecated/connection.py
+++ b/pylxd/deprecated/connection.py
@@ -24,7 +24,10 @@
 
 from six.moves import http_client
 from six.moves import queue
-from ws4py import client as websocket
+try:
+    from ws4py import client as websocket
+except ImportError:
+    websocket = None
 
 from pylxd.deprecated import exceptions
 from pylxd.deprecated import utils
@@ -88,43 +91,44 @@ def _get_ssl_certs():
 _LXDResponse = namedtuple('LXDResponse', ['status', 'body', 'json'])
 
 
-class WebSocketClient(websocket.WebSocketBaseClient):
-    def __init__(self, url, protocols=None, extensions=None, ssl_options=None,
-                 headers=None):
-        """WebSocket client that executes into a eventlet green thread."""
-        websocket.WebSocketBaseClient.__init__(self, url, protocols,
-                                               extensions,
-                                               ssl_options=ssl_options,
-                                               headers=headers)
-        self._th = threading.Thread(target=self.run, name='WebSocketClient')
-        self._th.daemon = True
-
-        self.messages = queue.Queue()
-
-    def handshake_ok(self):
-        """Starts the client's thread."""
-        self._th.start()
-
-    def received_message(self, message):
-        """Override the base class to store the incoming message."""
-        self.messages.put(copy.deepcopy(message))
-
-    def closed(self, code, reason=None):
-        # When the connection is closed, put a StopIteration
-        # on the message queue to signal there's nothing left
-        # to wait for
-        self.messages.put(StopIteration)
-
-    def receive(self):
-        # If the websocket was terminated and there are no messages
-        # left in the queue, return None immediately otherwise the client
-        # will block forever
-        if self.terminated and self.messages.empty():
-            return None
-        message = self.messages.get()
-        if message is StopIteration:
-            return None
-        return message
+if websocket is not None:
+    class WebSocketClient(websocket.WebSocketBaseClient):
+        def __init__(self, url, protocols=None, extensions=None, ssl_options=None,
+                     headers=None):
+            """WebSocket client that executes into a eventlet green thread."""
+            websocket.WebSocketBaseClient.__init__(self, url, protocols,
+                                                   extensions,
+                                                   ssl_options=ssl_options,
+                                                   headers=headers)
+            self._th = threading.Thread(target=self.run, name='WebSocketClient')
+            self._th.daemon = True
+
+            self.messages = queue.Queue()
+
+        def handshake_ok(self):
+            """Starts the client's thread."""
+            self._th.start()
+
+        def received_message(self, message):
+            """Override the base class to store the incoming message."""
+            self.messages.put(copy.deepcopy(message))
+
+        def closed(self, code, reason=None):
+            # When the connection is closed, put a StopIteration
+            # on the message queue to signal there's nothing left
+            # to wait for
+            self.messages.put(StopIteration)
+
+        def receive(self):
+            # If the websocket was terminated and there are no messages
+            # left in the queue, return None immediately otherwise the client
+            # will block forever
+            if self.terminated and self.messages.empty():
+                return None
+            message = self.messages.get()
+            if message is StopIteration:
+                return None
+            return message
 
 
 class LXDConnection(object):
@@ -202,6 +206,9 @@ def get_raw(self, *args, **kwargs):
             raise exceptions.PyLXDException('Failed to get raw response')
 
     def get_ws(self, path):
+        if websocket is None:
+            raise ValueError(
+                'This feature requires the optional ws4py library.')
         if self.unix_socket:
             connection_string = 'ws+unix://%s' % self.unix_socket
         else:
diff --git a/pylxd/models/container.py b/pylxd/models/container.py
index f7ef5d3..f7e3bd1 100644
--- a/pylxd/models/container.py
+++ b/pylxd/models/container.py
@@ -15,8 +15,13 @@
 
 import six
 from six.moves.urllib import parse
-from ws4py.client import WebSocketBaseClient
-from ws4py.manager import WebSocketManager
+try:
+    from ws4py.client import WebSocketBaseClient
+    from ws4py.manager import WebSocketManager
+    _ws4py_installed = True
+except ImportError:
+    WebSocketBaseClient = object
+    _ws4py_installed = False
 
 from pylxd import managers
 from pylxd.models import _model as model
@@ -183,6 +188,9 @@ def unfreeze(self, timeout=30, force=True, wait=False):
 
     def execute(self, commands, environment={}):
         """Execute a command on the container."""
+        if not _ws4py_installed:
+            raise ValueError(
+                'This feature requires the optional ws4py library.')
         if isinstance(commands, six.string_types):
             raise TypeError("First argument must be a list.")
         response = self.api['exec'].post(json={
diff --git a/pylxd/tests/models/test_container.py b/pylxd/tests/models/test_container.py
index e2044a5..e1ae3ca 100644
--- a/pylxd/tests/models/test_container.py
+++ b/pylxd/tests/models/test_container.py
@@ -155,6 +155,7 @@ def test_delete(self):
 
         an_container.delete(wait=True)
 
+    @testing.requires_ws4py
     @mock.patch('pylxd.models.container._StdinWebsocket')
     @mock.patch('pylxd.models.container._CommandWebsocketClient')
     def test_execute(self, _CommandWebsocketClient, _StdinWebsocket):
@@ -171,6 +172,7 @@ def test_execute(self, _CommandWebsocketClient, _StdinWebsocket):
 
         self.assertEqual('test\n', stdout)
 
+    @testing.requires_ws4py
     def test_execute_string(self):
         """A command passed as string raises a TypeError."""
         an_container = models.Container(
diff --git a/pylxd/tests/test_client.py b/pylxd/tests/test_client.py
index 2b1fbb2..cb68e5e 100644
--- a/pylxd/tests/test_client.py
+++ b/pylxd/tests/test_client.py
@@ -21,6 +21,7 @@
 import requests_unixsocket
 
 from pylxd import client, exceptions
+from pylxd.tests.testing import requires_ws4py
 
 
 class TestClient(unittest.TestCase):
@@ -164,6 +165,7 @@ def test_host_info(self):
         an_client = client.Client()
         self.assertEqual('zfs', an_client.host_info['environment']['storage'])
 
+    @requires_ws4py
     def test_events(self):
         """The default websocket client is returned."""
         an_client = client.Client()
@@ -172,6 +174,7 @@ def test_events(self):
 
         self.assertEqual('/1.0/events', ws_client.resource)
 
+    @requires_ws4py
     def test_events_unix_socket(self):
         """A unix socket compatible websocket client is returned."""
         websocket_client = mock.Mock(resource=None)
@@ -183,6 +186,7 @@ def test_events_unix_socket(self):
 
         WebsocketClient.assert_called_once_with('ws+unix:///lxd/unix.socket')
 
+    @requires_ws4py
     def test_events_htt(self):
         """An http compatible websocket client is returned."""
         websocket_client = mock.Mock(resource=None)
@@ -194,6 +198,7 @@ def test_events_htt(self):
 
         WebsocketClient.assert_called_once_with('ws://lxd.local')
 
+    @requires_ws4py
     def test_events_https(self):
         """An https compatible websocket client is returned."""
         websocket_client = mock.Mock(resource=None)
@@ -339,6 +344,7 @@ def test_delete(self, Session):
 class TestWebsocketClient(unittest.TestCase):
     """Tests for pylxd.client.WebsocketClient."""
 
+    @requires_ws4py
     def test_handshake_ok(self):
         """A `message` attribute of an empty list is created."""
         ws_client = client._WebsocketClient('ws://an/fake/path')
@@ -347,6 +353,7 @@ def test_handshake_ok(self):
 
         self.assertEqual([], ws_client.messages)
 
+    @requires_ws4py
     def test_received_message(self):
         """A json dict is added to the messages attribute."""
         message = mock.Mock(data=json.dumps({'test': 'data'}).encode('utf-8'))
diff --git a/pylxd/tests/testing.py b/pylxd/tests/testing.py
index 9adfb37..e60912c 100644
--- a/pylxd/tests/testing.py
+++ b/pylxd/tests/testing.py
@@ -6,6 +6,20 @@
 from pylxd.tests import mock_lxd
 
 
+def requires_ws4py(f):
+    """Marks a test as requiring ws4py.
+
+    As ws4py is an optional dependency, some tests should
+    be skipped, as they won't run properly if ws4py is not
+    installed.
+    """
+    try:
+        import ws4py  # NOQA
+        return f
+    except ImportError:
+        return unittest.skip('ws4py is not installed')(f)
+
+
 class PyLXDTestCase(unittest.TestCase):
     """A test case for handling mocking of LXD services."""
 


More information about the lxc-devel mailing list