diff --git a/docs/index.rst b/docs/index.rst index 2f39b6b..8d0ce45 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -252,8 +252,14 @@ instance includes versions of several of the methods in the :class:`socketio.Server` class that default to the proper namespace when the ``namespace`` argument is not given. -Note: if an event has a handler in a class-based namespace, and also a -decorator-based function handler, the standalone function handler is invoked. +In the case that an event has a handler in a class-based namespace, and also a +decorator-based function handler, only the standalone function handler is +invoked. + +It is important to note that class-based namespaces are singletons. This means +that a single instance of a namespace class is used for all clients, and +consequently, a namespace instance cannot be used to store client specific +information. Event handler interceptors -------------------------- @@ -445,7 +451,7 @@ pub/sub functionality:: mgr = socketio.RedisManager('redis://') sio = socketio.Server(client_manager=mgr) -If multiple Sokcet.IO servers are connected to a message queue, they +If multiple Socket.IO servers are connected to a message queue, they automatically communicate with each other and manage a combined client list, without any need for additional configuration. To have a process other than a server connect to the queue to emit a message, the same ``KombuManager`` diff --git a/socketio/__init__.py b/socketio/__init__.py index 280e18a..462da6a 100644 --- a/socketio/__init__.py +++ b/socketio/__init__.py @@ -8,7 +8,7 @@ from .redis_manager import RedisManager from .server import Server from .util import apply_interceptor, ignore_interceptor -__version__ = '1.6.0' +__version__ = '1.6.1' __all__ = [__version__, Interceptor, Middleware, Namespace, Server, BaseManager, PubSubManager, KombuManager, RedisManager, diff --git a/socketio/base_manager.py b/socketio/base_manager.py index 5b98990..198fabc 100644 --- a/socketio/base_manager.py +++ b/socketio/base_manager.py @@ -113,7 +113,7 @@ class BaseManager(object): return r def emit(self, event, data, namespace, room=None, skip_sid=None, - callback=None): + callback=None, **kwargs): """Emit a message to a single client, a room, or all the clients connected to the namespace.""" if namespace not in self.rooms or room not in self.rooms[namespace]: diff --git a/socketio/kombu_manager.py b/socketio/kombu_manager.py index 3895e8a..7d97e08 100644 --- a/socketio/kombu_manager.py +++ b/socketio/kombu_manager.py @@ -47,6 +47,20 @@ class KombuManager(PubSubManager): # pragma: no cover self.url = url self.producer = self._producer() + def initialize(self, server): + super(KombuManager, self).initialize(server) + + monkey_patched = True + if server.async_mode == 'eventlet': + from eventlet.patcher import is_monkey_patched + monkey_patched = is_monkey_patched('socket') + elif 'gevent' in server.async_mode: + from gevent.monkey import is_module_patched + monkey_patched = is_module_patched('socket') + if not monkey_patched: + raise RuntimeError('Redis requires a monkey patched socket ' + 'library to work with ' + server.async_mode) + def _connection(self): return kombu.Connection(self.url) diff --git a/socketio/pubsub_manager.py b/socketio/pubsub_manager.py index 8f34786..5de89b6 100644 --- a/socketio/pubsub_manager.py +++ b/socketio/pubsub_manager.py @@ -37,7 +37,7 @@ class PubSubManager(BaseManager): self.server.logger.info(self.name + ' backend initialized.') def emit(self, event, data, namespace=None, room=None, skip_sid=None, - callback=None): + callback=None, **kwargs): """Emit a message to a single client, a room, or all the clients connected to the namespace. @@ -46,6 +46,10 @@ class PubSubManager(BaseManager): The parameters are the same as in :meth:`.Server.emit`. """ + if kwargs.get('ignore_queue'): + return super(PubSubManager, self).emit( + event, data, namespace=namespace, room=room, skip_sid=skip_sid, + callback=callback) namespace = namespace or '/' if callback is not None: if self.server is None: diff --git a/socketio/redis_manager.py b/socketio/redis_manager.py index e11ab97..3ed63e5 100644 --- a/socketio/redis_manager.py +++ b/socketio/redis_manager.py @@ -43,6 +43,20 @@ class RedisManager(PubSubManager): # pragma: no cover super(RedisManager, self).__init__(channel=channel, write_only=write_only) + def initialize(self, server): + super(RedisManager, self).initialize(server) + + monkey_patched = True + if server.async_mode == 'eventlet': + from eventlet.patcher import is_monkey_patched + monkey_patched = is_monkey_patched('socket') + elif 'gevent' in server.async_mode: + from gevent.monkey import is_module_patched + monkey_patched = is_module_patched('socket') + if not monkey_patched: + raise RuntimeError('Redis requires a monkey patched socket ' + 'library to work with ' + server.async_mode) + def _publish(self, data): return self.redis.publish(self.channel, pickle.dumps(data)) diff --git a/socketio/server.py b/socketio/server.py index 3e69e31..d1d1d17 100644 --- a/socketio/server.py +++ b/socketio/server.py @@ -8,6 +8,8 @@ from . import namespace as sio_namespace from . import packet from . import namespace +default_logger = logging.getLogger('socketio') + class Server(object): """A Socket.IO server. @@ -94,7 +96,7 @@ class Server(object): if not isinstance(logger, bool): self.logger = logger else: - self.logger = logging.getLogger('socketio') + self.logger = default_logger if not logging.root.handlers and \ self.logger.level == logging.NOTSET: if logger: @@ -181,7 +183,7 @@ class Server(object): namespace_handler def emit(self, event, data=None, room=None, skip_sid=None, namespace=None, - callback=None): + callback=None, **kwargs): """Emit a custom event to one or more connected clients. :param event: The event name. It can be any string. The event names @@ -206,14 +208,22 @@ class Server(object): that will be passed to the function are those provided by the client. Callback functions can only be used when addressing an individual client. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. """ namespace = namespace or '/' self.logger.info('emitting event "%s" to %s [%s]', event, room or 'all', namespace) - self.manager.emit(event, data, namespace, room, skip_sid, callback) + self.manager.emit(event, data, namespace, room, skip_sid, callback, + **kwargs) def send(self, data, room=None, skip_sid=None, namespace=None, - callback=None): + callback=None, **kwargs): """Send a message to one or more connected clients. This function emits an event with the name ``'message'``. Use @@ -238,8 +248,16 @@ class Server(object): that will be passed to the function are those provided by the client. Callback functions can only be used when addressing an individual client. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. """ - self.emit('message', data, room, skip_sid, namespace, callback) + self.emit('message', data, room, skip_sid, namespace, callback, + **kwargs) def enter_room(self, sid, room, namespace=None): """Enter a room. diff --git a/tests/test_pubsub_manager.py b/tests/test_pubsub_manager.py index d67dc5b..d4cca4f 100644 --- a/tests/test_pubsub_manager.py +++ b/tests/test_pubsub_manager.py @@ -85,6 +85,14 @@ class TestBaseManager(unittest.TestCase): self.assertRaises(ValueError, self.pm.emit, 'foo', 'bar', callback='cb') + def test_emit_with_ignore_queue(self): + self.pm.connect('123', '/') + self.pm.emit('foo', 'bar', room='123', namespace='/', + ignore_queue=True) + self.pm._publish.assert_not_called() + self.pm.server._emit_internal.assert_called_once_with('123', 'foo', + 'bar', '/', None) + def test_close_room(self): self.pm.close_room('foo') self.pm._publish.assert_called_once_with(