From 8a4e5ffa5ceb03b63156b9520e79a4c7414ac214 Mon Sep 17 00:00:00 2001
From: Miguel Grinberg <miguel.grinberg@gmail.com>
Date: Sat, 29 Jun 2019 14:11:53 +0100
Subject: [PATCH] added "to" parameter as an alias to "room"

---
 socketio/asyncio_server.py           | 41 ++++++++++++++++------------
 socketio/server.py                   | 40 +++++++++++++++------------
 tests/asyncio/test_asyncio_server.py | 22 +++++++++++++--
 tests/common/test_server.py          | 25 ++++++++++++++---
 4 files changed, 85 insertions(+), 43 deletions(-)

diff --git a/socketio/asyncio_server.py b/socketio/asyncio_server.py
index 2bdfdcf..45f5d86 100644
--- a/socketio/asyncio_server.py
+++ b/socketio/asyncio_server.py
@@ -75,7 +75,7 @@ class AsyncServer(server.Server):
         """Attach the Socket.IO server to an application."""
         self.eio.attach(app, socketio_path)
 
-    async def emit(self, event, data=None, room=None, skip_sid=None,
+    async def emit(self, event, data=None, to=None, room=None, skip_sid=None,
                    namespace=None, callback=None, **kwargs):
         """Emit a custom event to one or more connected clients.
 
@@ -85,11 +85,12 @@ class AsyncServer(server.Server):
         :param data: The data to send to the client or clients. Data can be of
                      type ``str``, ``bytes``, ``list`` or ``dict``. If a
                      ``list`` or ``dict``, the data will be serialized as JSON.
-        :param room: The recipient of the message. This can be set to the
-                     session ID of a client to address that client's room, or
-                     to any custom room created by the application, If this
-                     argument is omitted the event is broadcasted to all
-                     connected clients.
+        :param to: The recipient of the message. This can be set to the
+                   session ID of a client to address only that client, or to
+                   to any custom room created by the application to address all
+                   the clients in that room, If this argument is omitted the
+                   event is broadcasted to all connected clients.
+        :param room: Alias for the ``to`` parameter.
         :param skip_sid: The session ID of a client to skip when broadcasting
                          to a room or to all clients. This can be used to
                          prevent a message from being sent to the sender.
@@ -112,14 +113,15 @@ class AsyncServer(server.Server):
         Note: this method is a coroutine.
         """
         namespace = namespace or '/'
+        room = to or room
         self.logger.info('emitting event "%s" to %s [%s]', event,
                          room or 'all', namespace)
         await self.manager.emit(event, data, namespace, room=room,
                                 skip_sid=skip_sid, callback=callback,
                                 **kwargs)
 
-    async def send(self, data, room=None, skip_sid=None, namespace=None,
-                   callback=None, **kwargs):
+    async def send(self, data, to=None, room=None, skip_sid=None,
+                   namespace=None, callback=None, **kwargs):
         """Send a message to one or more connected clients.
 
         This function emits an event with the name ``'message'``. Use
@@ -128,11 +130,12 @@ class AsyncServer(server.Server):
         :param data: The data to send to the client or clients. Data can be of
                      type ``str``, ``bytes``, ``list`` or ``dict``. If a
                      ``list`` or ``dict``, the data will be serialized as JSON.
-        :param room: The recipient of the message. This can be set to the
-                     session ID of a client to address that client's room, or
-                     to any custom room created by the application, If this
-                     argument is omitted the event is broadcasted to all
-                     connected clients.
+        :param to: The recipient of the message. This can be set to the
+                   session ID of a client to address only that client, or to
+                   to any custom room created by the application to address all
+                   the clients in that room, If this argument is omitted the
+                   event is broadcasted to all connected clients.
+        :param room: Alias for the ``to`` parameter.
         :param skip_sid: The session ID of a client to skip when broadcasting
                          to a room or to all clients. This can be used to
                          prevent a message from being sent to the sender.
@@ -154,10 +157,11 @@ class AsyncServer(server.Server):
 
         Note: this method is a coroutine.
         """
-        await self.emit('message', data=data, room=room, skip_sid=skip_sid,
-                        namespace=namespace, callback=callback, **kwargs)
+        await self.emit('message', data=data, to=to, room=room,
+                        skip_sid=skip_sid, namespace=namespace,
+                        callback=callback, **kwargs)
 
-    async def call(self, event, data=None, sid=None, namespace=None,
+    async def call(self, event, data=None, to=None, sid=None, namespace=None,
                    timeout=60, **kwargs):
         """Emit a custom event to a client and wait for the response.
 
@@ -167,7 +171,8 @@ class AsyncServer(server.Server):
         :param data: The data to send to the client or clients. Data can be of
                      type ``str``, ``bytes``, ``list`` or ``dict``. If a
                      ``list`` or ``dict``, the data will be serialized as JSON.
-        :param sid: The session ID of the recipient client.
+        :param to: The session ID of the recipient client.
+        :param sid: Alias for the ``to`` parameter.
         :param namespace: The Socket.IO namespace for the event. If this
                           argument is omitted the event is emitted to the
                           default namespace.
@@ -192,7 +197,7 @@ class AsyncServer(server.Server):
             callback_args.append(args)
             callback_event.set()
 
-        await self.emit(event, data=data, room=sid, namespace=namespace,
+        await self.emit(event, data=data, room=to or sid, namespace=namespace,
                         callback=event_callback, **kwargs)
         try:
             await asyncio.wait_for(callback_event.wait(), timeout)
diff --git a/socketio/server.py b/socketio/server.py
index 8bce1d9..f9a74dc 100644
--- a/socketio/server.py
+++ b/socketio/server.py
@@ -236,8 +236,8 @@ class Server(object):
         self.namespace_handlers[namespace_handler.namespace] = \
             namespace_handler
 
-    def emit(self, event, data=None, room=None, skip_sid=None, namespace=None,
-             callback=None, **kwargs):
+    def emit(self, event, data=None, to=None, room=None, skip_sid=None,
+             namespace=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
@@ -246,11 +246,12 @@ class Server(object):
         :param data: The data to send to the client or clients. Data can be of
                      type ``str``, ``bytes``, ``list`` or ``dict``. If a
                      ``list`` or ``dict``, the data will be serialized as JSON.
-        :param room: The recipient of the message. This can be set to the
-                     session ID of a client to address that client's room, or
-                     to any custom room created by the application, If this
-                     argument is omitted the event is broadcasted to all
-                     connected clients.
+        :param to: The recipient of the message. This can be set to the
+                   session ID of a client to address only that client, or to
+                   to any custom room created by the application to address all
+                   the clients in that room, If this argument is omitted the
+                   event is broadcasted to all connected clients.
+        :param room: Alias for the ``to`` parameter.
         :param skip_sid: The session ID of a client to skip when broadcasting
                          to a room or to all clients. This can be used to
                          prevent a message from being sent to the sender. To
@@ -272,12 +273,13 @@ class Server(object):
                              value of ``False``.
         """
         namespace = namespace or '/'
+        room = to or room
         self.logger.info('emitting event "%s" to %s [%s]', event,
                          room or 'all', namespace)
         self.manager.emit(event, data, namespace, room=room,
                           skip_sid=skip_sid, callback=callback, **kwargs)
 
-    def send(self, data, room=None, skip_sid=None, namespace=None,
+    def send(self, data, to=None, room=None, skip_sid=None, namespace=None,
              callback=None, **kwargs):
         """Send a message to one or more connected clients.
 
@@ -287,11 +289,12 @@ class Server(object):
         :param data: The data to send to the client or clients. Data can be of
                      type ``str``, ``bytes``, ``list`` or ``dict``. If a
                      ``list`` or ``dict``, the data will be serialized as JSON.
-        :param room: The recipient of the message. This can be set to the
-                     session ID of a client to address that client's room, or
-                     to any custom room created by the application, If this
-                     argument is omitted the event is broadcasted to all
-                     connected clients.
+        :param to: The recipient of the message. This can be set to the
+                   session ID of a client to address only that client, or to
+                   to any custom room created by the application to address all
+                   the clients in that room, If this argument is omitted the
+                   event is broadcasted to all connected clients.
+        :param room: Alias for the ``to`` parameter.
         :param skip_sid: The session ID of a client to skip when broadcasting
                          to a room or to all clients. This can be used to
                          prevent a message from being sent to the sender. To
@@ -312,11 +315,11 @@ class Server(object):
                              to always leave this parameter with its default
                              value of ``False``.
         """
-        self.emit('message', data=data, room=room, skip_sid=skip_sid,
+        self.emit('message', data=data, to=to, room=room, skip_sid=skip_sid,
                   namespace=namespace, callback=callback, **kwargs)
 
-    def call(self, event, data=None, sid=None, namespace=None, timeout=60,
-             **kwargs):
+    def call(self, event, data=None, to=None, sid=None, namespace=None,
+             timeout=60, **kwargs):
         """Emit a custom event to a client and wait for the response.
 
         :param event: The event name. It can be any string. The event names
@@ -325,7 +328,8 @@ class Server(object):
         :param data: The data to send to the client or clients. Data can be of
                      type ``str``, ``bytes``, ``list`` or ``dict``. If a
                      ``list`` or ``dict``, the data will be serialized as JSON.
-        :param sid: The session ID of the recipient client.
+        :param to: The session ID of the recipient client.
+        :param sid: Alias for the ``to`` parameter.
         :param namespace: The Socket.IO namespace for the event. If this
                           argument is omitted the event is emitted to the
                           default namespace.
@@ -350,7 +354,7 @@ class Server(object):
             callback_args.append(args)
             callback_event.set()
 
-        self.emit(event, data=data, room=sid, namespace=namespace,
+        self.emit(event, data=data, room=to or sid, namespace=namespace,
                   callback=event_callback, **kwargs)
         if not callback_event.wait(timeout=timeout):
             raise exceptions.TimeoutError()
diff --git a/tests/asyncio/test_asyncio_server.py b/tests/asyncio/test_asyncio_server.py
index 54c27b4..e9a2c66 100644
--- a/tests/asyncio/test_asyncio_server.py
+++ b/tests/asyncio/test_asyncio_server.py
@@ -84,28 +84,44 @@ class TestAsyncServer(unittest.TestCase):
     def test_emit(self, eio):
         mgr = self._get_mock_manager()
         s = asyncio_server.AsyncServer(client_manager=mgr)
-        _run(s.emit('my event', {'foo': 'bar'}, room='room',
+        _run(s.emit('my event', {'foo': 'bar'}, to='room',
                     skip_sid='123', namespace='/foo', callback='cb'))
         s.manager.emit.mock.assert_called_once_with(
             'my event', {'foo': 'bar'}, '/foo', room='room', skip_sid='123',
             callback='cb')
+        _run(s.emit('my event', {'foo': 'bar'}, room='room',
+                    skip_sid='123', namespace='/foo', callback='cb'))
+        s.manager.emit.mock.assert_called_with(
+            'my event', {'foo': 'bar'}, '/foo', room='room', skip_sid='123',
+            callback='cb')
 
     def test_emit_default_namespace(self, eio):
         mgr = self._get_mock_manager()
         s = asyncio_server.AsyncServer(client_manager=mgr)
-        _run(s.emit('my event', {'foo': 'bar'}, room='room',
+        _run(s.emit('my event', {'foo': 'bar'}, to='room',
                     skip_sid='123', callback='cb'))
         s.manager.emit.mock.assert_called_once_with(
             'my event', {'foo': 'bar'}, '/', room='room', skip_sid='123',
             callback='cb')
+        _run(s.emit('my event', {'foo': 'bar'}, room='room',
+                    skip_sid='123', callback='cb'))
+        s.manager.emit.mock.assert_called_with(
+            'my event', {'foo': 'bar'}, '/', room='room', skip_sid='123',
+            callback='cb')
 
     def test_send(self, eio):
         mgr = self._get_mock_manager()
         s = asyncio_server.AsyncServer(client_manager=mgr)
-        _run(s.send('foo', 'room', '123', namespace='/foo', callback='cb'))
+        _run(s.send('foo', to='room', skip_sid='123', namespace='/foo',
+                    callback='cb'))
         s.manager.emit.mock.assert_called_once_with(
             'message', 'foo', '/foo', room='room', skip_sid='123',
             callback='cb')
+        _run(s.send('foo', room='room', skip_sid='123', namespace='/foo',
+                    callback='cb'))
+        s.manager.emit.mock.assert_called_with(
+            'message', 'foo', '/foo', room='room', skip_sid='123',
+            callback='cb')
 
     def test_call(self, eio):
         mgr = self._get_mock_manager()
diff --git a/tests/common/test_server.py b/tests/common/test_server.py
index c8f9951..667a465 100644
--- a/tests/common/test_server.py
+++ b/tests/common/test_server.py
@@ -75,27 +75,44 @@ class TestServer(unittest.TestCase):
     def test_emit(self, eio):
         mgr = mock.MagicMock()
         s = server.Server(client_manager=mgr)
-        s.emit('my event', {'foo': 'bar'}, 'room', '123', namespace='/foo',
-               callback='cb')
+        s.emit('my event', {'foo': 'bar'}, to='room', skip_sid='123',
+               namespace='/foo', callback='cb')
         s.manager.emit.assert_called_once_with(
             'my event', {'foo': 'bar'}, '/foo', room='room', skip_sid='123',
             callback='cb')
+        s.emit('my event', {'foo': 'bar'}, room='room', skip_sid='123',
+               namespace='/foo', callback='cb')
+        s.manager.emit.assert_called_with(
+            'my event', {'foo': 'bar'}, '/foo', room='room', skip_sid='123',
+            callback='cb')
 
     def test_emit_default_namespace(self, eio):
         mgr = mock.MagicMock()
         s = server.Server(client_manager=mgr)
-        s.emit('my event', {'foo': 'bar'}, 'room', '123', callback='cb')
+        s.emit('my event', {'foo': 'bar'}, to='room', skip_sid='123',
+               callback='cb')
         s.manager.emit.assert_called_once_with(
             'my event', {'foo': 'bar'}, '/', room='room', skip_sid='123',
             callback='cb')
+        s.emit('my event', {'foo': 'bar'}, room='room', skip_sid='123',
+               callback='cb')
+        s.manager.emit.assert_called_with(
+            'my event', {'foo': 'bar'}, '/', room='room', skip_sid='123',
+            callback='cb')
 
     def test_send(self, eio):
         mgr = mock.MagicMock()
         s = server.Server(client_manager=mgr)
-        s.send('foo', 'room', '123', namespace='/foo', callback='cb')
+        s.send('foo', to='room', skip_sid='123', namespace='/foo',
+               callback='cb')
         s.manager.emit.assert_called_once_with(
             'message', 'foo', '/foo', room='room', skip_sid='123',
             callback='cb')
+        s.send('foo', room='room', skip_sid='123', namespace='/foo',
+               callback='cb')
+        s.manager.emit.assert_called_with(
+            'message', 'foo', '/foo', room='room', skip_sid='123',
+            callback='cb')
 
     def test_call(self, eio):
         mgr = mock.MagicMock()