def test_emit_update_indeterminate(self, add_handler): ws = WebSocket('pb') module_pb = ProgressBar(indeterminate=True) ws.bind(module_pb) ws.emit = Mock() # 1st try, without label module_pb.tick() call1 = call('module_progressbar_before_update', None) call2 = call('module_progressbar_update', {}) call3 = call('module_progressbar_after_update', None) ws.emit.assert_has_calls([call1, call2, call3]) ws.emit.reset_mock() # 2nd and last try, with label module_pb.tick('My label') call1 = call('module_progressbar_before_update', None) call2 = call('module_progressbar_update', {'label': 'My label'}) call3 = call('module_progressbar_after_update', None) ws.emit.assert_has_calls([call1, call2, call3])
class TestModuleProgressBarCommunication(WebSocketBaseTestCase): @patch('tornado_websockets.tornadowrapper.TornadoWrapper.add_handler') def get_app(self, add_handler): self.ws = WebSocket('pb') self.close_future = Future() return tornado.web.Application([ ('/ws/module/pb', WebSocketHandlerForTests, {'websocket': self.ws, 'close_future': self.close_future}), ]) @gen_test def test_open_event(self): @self.ws.on def open(): self.ws.emit('opened') module_pb = ProgressBar() module_pb.emit_init = Mock() self.ws.bind(module_pb) ws_connection = yield self.ws_connect('/ws/module/pb') # 1st: clasical websocket on open event response = yield ws_connection.read_message() response = json_decode(response) self.assertDictEqual(response, { 'event': 'opened', 'data': {} }) # 2nd: ProgressBar module on open event module_pb.emit_init.assert_called_with() self.close(ws_connection)
def get_app(self, add_handler): self.ws = WebSocket('pb') self.close_future = Future() return tornado.web.Application([ ('/ws/module/pb', WebSocketHandlerForTests, {'websocket': self.ws, 'close_future': self.close_future}), ])
def test_emit(self, add_handler): ws = WebSocket('foo') ws.emit = Mock() moduleBar = MyModule('bar') moduleBar.initialize = Mock() # Module is not binded to WebSocket instance self.assertEqual(ws.modules, []) self.assertEqual(ws.events, {}) self.assertIsNone(moduleBar._websocket) with self.assertRaisesRegexp( AttributeError, "'NoneType' object has no attribute 'emit'"): moduleBar.emit('my_event', {'my': 'data'}) ws.emit.assert_not_called() # Module is now binded to WebSocket instance ws.bind(moduleBar) moduleBar.initialize.assert_called_with() self.assertEqual(ws.modules, [moduleBar]) self.assertEqual(ws.events, {}) self.assertEqual(moduleBar._websocket, ws) moduleBar.emit('my_event', {'my': 'data'}) ws.emit.assert_called_with('module_mymodule_bar_my_event', {'my': 'data'})
def test_on(self, add_handler): ws = WebSocket('foo') moduleBar = MyModule('bar') moduleBar.initialize = Mock() # Module is not binded to WebSocket instance self.assertEqual(ws.modules, []) self.assertEqual(ws.events, {}) self.assertIsNone(moduleBar._websocket) with self.assertRaisesRegexp( AttributeError, "'NoneType' object has no attribute 'on'"): @moduleBar.on def func(): pass # Module is now binded to WebSocket instance ws.bind(moduleBar) moduleBar.initialize.assert_called_with() self.assertEqual(ws.modules, [moduleBar]) self.assertEqual(ws.events, {}) self.assertEqual(moduleBar._websocket, ws) @moduleBar.on def func(): pass self.assertDictEqual(ws.events, {'module_mymodule_bar_func': func})
def test_construct(self, add_handler): add_handler.assert_called_with(('/ws/path1', WebSocketHandler, {'websocket': WebSocket('path1')})) add_handler.assert_called_with(('/ws/path2', WebSocketHandler, {'websocket': WebSocket('/path2')})) add_handler.assert_called_with(('/ws/path3', WebSocketHandler, {'websocket': WebSocket(' path3 ')})) add_handler.assert_called_with(('/ws/path4', WebSocketHandler, {'websocket': WebSocket(' /path4 ')})) with self.assertRaisesRegexp(TypeError, '« Path » parameter should be a string.'): WebSocket(path=1234)
def test_emit_with_bad_parameter_data(self): ws = WebSocket('/abc') ws.handlers = ['handler'] time.sleep(SLEEPING_TIME) with self.assertRaises(TypeError) as e: ws.emit('my_event', 123) self.assertEqual(str(e.exception), 'Data should be a string or a dictionary.')
def test_emit_with_good_parameter_event(self): ws = WebSocket('/abc') ws.handlers = ['not_an_handler'] time.sleep(SLEEPING_TIME) # It raises an InvalidInstanceError because we override ws's handlers to dodge EmitHandlerError exception, # and we can't get a real WebSocketHandler to use with this ws. But it works with self.assertRaises(InvalidInstanceError) as e: ws.emit('my_event')
def test_emit_with_bad_parameter_event(self): ws = WebSocket('/abc') ws.handlers = ['not_an_handler'] time.sleep(SLEEPING_TIME) with self.assertRaises(TypeError) as e: ws.emit(12345) self.assertEqual(str(e.exception), 'Event should be a string.')
def test_initialize(self, add_handler): ws = WebSocket('pb') module_pb = ProgressBar() module_pb.initialize = Mock() self.assertEqual(ws.modules, []) ws.bind(module_pb) self.assertEqual(ws.modules, [module_pb]) module_pb.initialize.assert_called_with()
def test_context(self, add_handler): module = MyModule() ws = WebSocket('pb') self.assertIsNone(module._websocket) with self.assertRaisesRegexp(AttributeError, "'NoneType' object has no attribute 'context'"): print(module.context) ws.bind(module) module.context = 'foo' self.assertEqual(module._websocket.context, 'foo') self.assertEqual(module.context, 'foo')
def test_on(self, add_handler): ws = WebSocket('path') self.assertDictEqual(ws.events, {}) with self.assertRaises(NotCallableError): ws.on('string') @ws.on def func(): pass self.assertDictEqual(ws.events, {'func': func})
def test_emit_init_indeterminate(self, add_handler): ws = WebSocket('pb') module_pb = ProgressBar(indeterminate=True) ws.bind(module_pb) ws.emit = Mock() module_pb.emit_init() call1 = call('module_progressbar_before_init', None) call2 = call('module_progressbar_init', {'indeterminate': True}) call3 = call('module_progressbar_after_init', None) ws.emit.assert_has_calls([call1, call2, call3])
def test_bind_module(self, add_handler): ws = WebSocket('path') module = ProgressBar('progress') module.initialize = Mock() self.assertListEqual(ws.modules, []) self.assertIsNone(module._websocket) module.initialize.assert_not_called() ws.bind(module) self.assertListEqual(ws.modules, [module]) self.assertEqual(module._websocket, ws) module.initialize.assert_called_with()
def test_emit_outside_on_decorator(self): ws = WebSocket('/abc') time.sleep(SLEEPING_TIME) with self.assertRaises(EmitHandlerError) as e: ws.emit('my_event', 'my_message') self.assertEqual(e.exception.event, 'my_event') self.assertEqual(e.exception.path, '/abc') self.assertEqual( str(e.exception), 'Can not emit "%s" event in "%s" path, emit() should be used in a function or class method' ' decorated by @WebSocket.on decorator.' % ('my_event', '/abc') )
def test_emit_with_bad_handlers(self): ws = WebSocket('/abc') ws.handlers = ['not_an_handler'] time.sleep(SLEEPING_TIME) with self.assertRaises(InvalidInstanceError) as e: ws.emit('my_event') self.assertEqual(e.exception.actual_instance, 'not_an_handler') self.assertEqual(e.exception.expected_instance_name, 'tornado_websockets.websockethandler.WebSocketHandler') self.assertEqual( str(e.exception), 'Expected instance of "%s", got "%s" instead.' % ( 'tornado_websockets.websockethandler.WebSocketHandler', repr('not_an_handler') ) )
def test_emit_done(self, add_handler): ws = WebSocket('pb') module_pb = ProgressBar(min=0, max=2) ws.bind(module_pb) ws.emit = Mock() module_pb.emit_init = Mock() module_pb.emit_update = Mock() # 1st try module_pb.tick() self.assertEqual(module_pb.current, 1) ws.emit.assert_not_called() # 2nd and last try module_pb.tick() self.assertEqual(module_pb.current, 2) self.assertEqual(module_pb.current, module_pb.max) ws.emit.assert_called_with('module_progressbar_done', None)
def test_emit_init_determinate(self, add_handler): ws = WebSocket('pb') module_pb = ProgressBar() ws.bind(module_pb) ws.emit = Mock() module_pb.emit_init() call1 = call('module_progressbar_before_init', None) call2 = call('module_progressbar_init', { 'indeterminate': False, 'min': 0, 'max': 100, 'current': 0 }) call3 = call('module_progressbar_after_init', None) ws.emit.assert_has_calls([call1, call2, call3])
def get_ws(): ws = WebSocket('/test') @ws.on def hello(socket, data): ws.emit( 'hello', { 'socket': str(socket), 'data_sent': data, 'message': 'Hello from hello callback!' }) return ws
def __init__(self, path, min=0, max=100, add_to_handlers=True): if max < min: raise ValueError('`max` value (%d) can not be lower than `min` value (%d).' % (max, min)) self.min = min self.max = max self._value = min self.indeterminate = min is max self.path = path.strip() self.path = self.path if self.path.startswith('/') else '/' + self.path self.websocket = WebSocket('/module/progress_bar' + self.path, add_to_handlers) self.bind_default_events()
class TestModuleProgressBarCommunication(WebSocketBaseTestCase): @patch('tornado_websockets.tornadowrapper.TornadoWrapper.add_handler') def get_app(self, add_handler): self.ws = WebSocket('pb') self.close_future = Future() return tornado.web.Application([ ('/ws/module/pb', WebSocketHandlerForTests, { 'websocket': self.ws, 'close_future': self.close_future }), ]) @gen_test def test_open_event(self): module_pb = ProgressBar() module_pb.emit_init = Mock() self.ws.bind(module_pb) ws_connection = yield self.ws_connect('/ws/module/pb') module_pb.emit_init.assert_called_with() self.close(ws_connection)
# coding: utf-8 """ Example of module « Progress Bar » by using `tornado_websocket.modules.ProgressBar` to handle communications, and Django's TemplateView for rendering. """ from django.views.generic import TemplateView from tornado import gen from tornado_websockets.modules import ProgressBar from tornado_websockets.websocket import WebSocket ws = WebSocket('module_progressbar') progressbar = ProgressBar('foo', min=0, max=100) ws.bind(progressbar) @progressbar.on def reset(): progressbar.reset() @progressbar.on @gen.engine # Make this function asynchronous for Tornado's IOLoop def start(): for value in range(0, progressbar.max): yield gen.sleep(.1) # like time.sleep(), but asynchronous progressbar.tick(label="[%d/%d] Tâche %d terminée" % (progress_bar.current + 1, progress_bar.max, value))
# coding: utf-8 """ Example of a « echo » websocket server by using `tornado_websocket.WebSocket`. """ from tornado_websockets.websocket import WebSocket tws = WebSocket('/echo') # Listen the « message » event @tws.on def message(socket, data): socket.emit('new_message', { 'message': data.get('message') }) # Shorter version # socket.emit('new_message', data)
# coding: utf-8 """ Example of a « chat application » by using `tornado_websocket.WebSocket` to handle communications, and Django's TemplateView for rendering. """ from django.views.generic import TemplateView from tornado_websockets.websocket import WebSocket tws = WebSocket('/my_chat') class MyChat(TemplateView): """ Proof of concept about a really simple web chat using websockets and supporting messages history """ template_name = 'testapp/index.html' messages = [] def __init__(self, **kwargs): super(MyChat, self).__init__(**kwargs) # Otherwise, 'self' parameter for method decorated by @ws_chat.on will not be defined tws.context = self @tws.on def connection(self, socket, data): # Send an history of the chat [socket.emit('new_message', __) for __ in self.messages]
class ProgressBar(object): """ Initialize a new ProgressBar module instance. If ``min`` and ``max`` values are equal, this progress bar has its indeterminate state set to ``True``. :param path: WebSocket path, see ``tornado_websockets.websocket.WebSocket`` :param min: Minimum _value :param max: Maximum _value :type path: str :type min: int :type max: int """ def __init__(self, path, min=0, max=100, add_to_handlers=True): if max < min: raise ValueError('`max` value (%d) can not be lower than `min` value (%d).' % (max, min)) self.min = min self.max = max self._value = min self.indeterminate = min is max self.path = path.strip() self.path = self.path if self.path.startswith('/') else '/' + self.path self.websocket = WebSocket('/module/progress_bar' + self.path, add_to_handlers) self.bind_default_events() def reset(self): """ Reset progress bar's progression to its minimum value. """ self._value = self.min def tick(self, label=None): """ Increments progress bar's _value by ``1`` and emit ``update`` event. Can also emit ``done`` event if progression is done. Call :meth:`~tornado_websockets.modules.progress_bar.ProgressBar.emit_update` method each time this method is called. Call :meth:`~tornado_websockets.modules.progress_bar.ProgressBar.emit_done` method if progression is done. :param label: A label which can be displayed on the client screen :type label: str """ if not self.indeterminate and self._value < self.max: self._value += 1 self.emit_update(label) if self.is_done(): self.emit_done() def is_done(self): """ Return ``True`` if progress bar's progression is done, otherwise ``False``. Returns ``False`` if progress bar is indeterminate, returns ``True`` if progress bar is determinate and current value is equals to ``max`` value. Returns ``False`` by default. :rtype: bool """ if self.indeterminate: return False if self.value is self.max: return True return False def bind_default_events(self): """ Bind default events for WebSocket instance. Actually, it only binds ``open`` event. """ @self.websocket.on def open(): self.emit_init() def on(self, callback): """ Shortcut for :meth:`tornado_websockets.websocket.WebSocket.on` decorator. :param callback: Function or a class method. :type callback: Callable :return: ``callback`` parameter. """ return self.websocket.on(callback) def emit_init(self): """ Emit ``before_init``, ``init`` and ``after_init`` events to initialize a client-side progress bar. If progress bar is not indeterminate, ``min``, ``max`` and ``value`` values are sent with ``init`` event. """ data = {'indeterminate': self.indeterminate} if not self.indeterminate: data.update({ 'min': int(self.min), 'max': int(self.max), 'value': int(self._value), }) self.websocket.emit('before_init') self.websocket.emit('init', data) self.websocket.emit('after_init') def emit_update(self, label=None): """ Emit ``before_update``, ``update`` and ``after_update`` events to update a client-side progress bar. :param label: A label which can be displayed on the client screen :type label: str """ data = {} if not self.indeterminate: data.update({'value': int(self._value)}) if label: data.update({'label': label}) self.websocket.emit('before_update') self.websocket.emit('update', data) self.websocket.emit('after_update') def emit_done(self): """ Emit ``done`` event when progress bar's progression :meth:`~tornado_websockets.modules.progress_bar.ProgressBar.is_done`. """ self.websocket.emit('done') @property def value(self): return self._value @value.setter def value(self, value): if not self.indeterminate and not self.min <= value <= self.max: raise ValueError('Value is not in [%d; %d] range.' % (self.min, self.max)) self._value = value @property def context(self): return self.websocket.context @context.setter def context(self, value): self.websocket.context = value
def test_emit(self, add_handler): ws = WebSocket('path') handler = Mock() # Emulate WebSocketHandler class with Mock, because only Tornado can instantiate it properly def side_effect(websocket): ws.handlers.append(handler) handler.websocket = websocket handler.return_value = None handler.websocket = None handler.initialize.side_effect = side_effect handler.emit = Mock() self.assertListEqual(ws.handlers, []) self.assertIsNone(handler.websocket) handler.initialize(ws) self.assertListEqual(ws.handlers, [handler]) self.assertEqual(handler.websocket, ws) with self.assertRaisesRegexp(TypeError, 'Param « event » should be a string.'): ws.emit(123) handler.emit.assert_not_called() ws.emit('event') handler.emit.assert_called_with('event', {}) handler.emit.reset_mock() ws.emit('event', {}) handler.emit.assert_called_with('event', {}) handler.emit.reset_mock() ws.emit('event', 'my message') handler.emit.assert_called_with('event', {'message': 'my message'}) handler.emit.reset_mock() with self.assertRaisesRegexp(TypeError, 'Param « data » should be a string or a dictionary.'): ws.emit('event', 123) handler.emit.assert_not_called()