From fe40b30e35f709158a36d2eaabb12445c7519286 Mon Sep 17 00:00:00 2001 From: Robert Schindler Date: Sun, 21 Aug 2016 14:41:30 +0200 Subject: [PATCH] Added class-based namespaces. --- socketio/__init__.py | 5 ++-- socketio/namespace.py | 56 +++++++++++++++++++++++++++++++++++++++++++ socketio/server.py | 14 +++++++++++ tests/test_server.py | 11 +++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 socketio/namespace.py diff --git a/socketio/__init__.py b/socketio/__init__.py index be5882f..ff68424 100644 --- a/socketio/__init__.py +++ b/socketio/__init__.py @@ -1,9 +1,10 @@ from .middleware import Middleware +from .namespace import Namespace from .base_manager import BaseManager from .pubsub_manager import PubSubManager from .kombu_manager import KombuManager from .redis_manager import RedisManager from .server import Server -__all__ = [Middleware, Server, BaseManager, PubSubManager, KombuManager, - RedisManager] +__all__ = [Middleware, Namespace, Server, BaseManager, PubSubManager, + KombuManager, RedisManager] diff --git a/socketio/namespace.py b/socketio/namespace.py new file mode 100644 index 0000000..5a2b49d --- /dev/null +++ b/socketio/namespace.py @@ -0,0 +1,56 @@ +import types + + +class Namespace(object): + """A container for a set of event handlers for a specific namespace. + + A method of this class named ``on_xxx`` is considered as the event handler + for the event ``'xxx'`` in the namespace this class is registered to. + + There are also the following methods available that insert the current + namespace automatically when none is given before they call their matching + method of the ``Server`` instance: + ``emit``, ``send``, ``enter_room``, ``leave_room``, ``close_room``, + ``rooms``, ``disconnect`` + + Example: + + from socketio import Namespace, Server + class ChatNamespace(Namespace): + def on_msg(self, sid, msg): + # self.server references to the socketio.Server object + data = "[%s]: %s" \ + % (self.server.environ[sid].get("REMOTE_ADDR"), msg) + # Note that we don't pass namespace="/chat" to the emit method. + # It is done automatically for us. + self.emit("msg", data, skip_sid=sid) + return "received your message: %s" % msg + sio = socketio.Server() + sio.register_namespace("/chat", ChatNamespace) + """ + + def __init__(self, name, server): + self.name = name + self.server = server + + # wrap methods of Server object + def get_wrapped_method(func_name): + def wrapped_func(self, *args, **kwargs): + """If namespace is None, it is automatically set to this + object's one before the original method is called. + """ + if kwargs.get('namespace') is None: + kwargs['namespace'] = self.name + return getattr(self.server, func_name)(*args, **kwargs) + return types.MethodType(wrapped_func, self) + for func_name in ('emit', 'send', 'enter_room', 'leave_room', + 'close_room', 'rooms', 'disconnect'): + setattr(self, func_name, get_wrapped_method(func_name)) + + def _get_handlers(self): + """Returns a dict of event names and handlers this namespace provides.""" + handlers = {} + for attr_name in dir(self): + if attr_name.startswith('on_'): + handlers[attr_name[3:]] = getattr(self, attr_name) + return handlers diff --git a/socketio/server.py b/socketio/server.py index 227ff80..e927a7f 100644 --- a/socketio/server.py +++ b/socketio/server.py @@ -150,6 +150,20 @@ class Server(object): return set_handler set_handler(handler) + def register_namespace(self, name, namespace_class): + """Register all handlers of the given ``namespace_class`` under the + namespace named by ``name``. + + :param name: The namespace's name. It can be any string. + :param namespace_class: The sub class of ``Namespace`` to register + handlers of. Don't pass an instance instead. + + See documentation of ``Namespace`` class for an example. + """ + namespace = namespace_class(name, self) + for event, handler in six.iteritems(namespace._get_handlers()): + self.on(event, handler=handler, namespace=name) + def emit(self, event, data=None, room=None, skip_sid=None, namespace=None, callback=None): """Emit a custom event to one or more connected clients. diff --git a/tests/test_server.py b/tests/test_server.py index 372f61a..8c10a06 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -8,6 +8,7 @@ if six.PY3: else: import mock +from socketio import namespace from socketio import packet from socketio import server @@ -45,6 +46,16 @@ class TestServer(unittest.TestCase): self.assertEqual(s.handlers['/']['disconnect'], bar) self.assertEqual(s.handlers['/foo']['disconnect'], bar) + def test_register_namespace(self, eio): + class NS(namespace.Namespace): + def on_foo(self): + self.emit("bar") + + s = server.Server() + s.register_namespace('/ns', NS) + + self.assertIsNotNone(s.handlers['/ns'].get('foo')) + def test_on_bad_event_name(self, eio): s = server.Server() self.assertRaises(ValueError, s.on, 'two-words')