diff --git a/examples/server/aiohttp/app.py b/examples/server/aiohttp/app.py index 14864728..cba51937 100644 --- a/examples/server/aiohttp/app.py +++ b/examples/server/aiohttp/app.py @@ -33,14 +33,14 @@ async def my_broadcast_event(sid, message): @sio.event async def join(sid, message): - sio.enter_room(sid, message['room']) + await sio.enter_room(sid, message['room']) await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, room=sid) @sio.event async def leave(sid, message): - sio.leave_room(sid, message['room']) + await sio.leave_room(sid, message['room']) await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, room=sid) diff --git a/examples/server/asgi/app.py b/examples/server/asgi/app.py index c6068237..22180bb0 100644 --- a/examples/server/asgi/app.py +++ b/examples/server/asgi/app.py @@ -31,14 +31,14 @@ async def test_broadcast_message(sid, message): @sio.on('join') async def join(sid, message): - sio.enter_room(sid, message['room']) + await sio.enter_room(sid, message['room']) await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, room=sid) @sio.on('leave') async def leave(sid, message): - sio.leave_room(sid, message['room']) + await sio.leave_room(sid, message['room']) await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, room=sid) diff --git a/examples/server/sanic/app.py b/examples/server/sanic/app.py index 8e9ab47a..e10d764d 100644 --- a/examples/server/sanic/app.py +++ b/examples/server/sanic/app.py @@ -40,14 +40,14 @@ async def my_broadcast_event(sid, message): @sio.event async def join(sid, message): - sio.enter_room(sid, message['room']) + await sio.enter_room(sid, message['room']) await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, room=sid) @sio.event async def leave(sid, message): - sio.leave_room(sid, message['room']) + await sio.leave_room(sid, message['room']) await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, room=sid) diff --git a/examples/server/tornado/app.py b/examples/server/tornado/app.py index 92fbfed6..16f7a191 100644 --- a/examples/server/tornado/app.py +++ b/examples/server/tornado/app.py @@ -38,14 +38,14 @@ async def my_broadcast_event(sid, message): @sio.event async def join(sid, message): - sio.enter_room(sid, message['room']) + await sio.enter_room(sid, message['room']) await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, room=sid) @sio.event async def leave(sid, message): - sio.leave_room(sid, message['room']) + await sio.leave_room(sid, message['room']) await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, room=sid) diff --git a/src/socketio/asyncio_manager.py b/src/socketio/asyncio_manager.py index 2bf90011..cc0ebfd5 100644 --- a/src/socketio/asyncio_manager.py +++ b/src/socketio/asyncio_manager.py @@ -69,6 +69,20 @@ async def disconnect(self, sid, namespace, **kwargs): """ return super().disconnect(sid, namespace, **kwargs) + async def enter_room(self, sid, namespace, room, eio_sid=None): + """Add a client to a room. + + Note: this method is a coroutine. + """ + return super().enter_room(sid, namespace, room, eio_sid=eio_sid) + + async def leave_room(self, sid, namespace, room): + """Remove a client from a room. + + Note: this method is a coroutine. + """ + return super().leave_room(sid, namespace, room) + async def close_room(self, room, namespace): """Remove all participants from a room. diff --git a/src/socketio/asyncio_namespace.py b/src/socketio/asyncio_namespace.py index 4baf28fc..96f8d27a 100644 --- a/src/socketio/asyncio_namespace.py +++ b/src/socketio/asyncio_namespace.py @@ -86,6 +86,30 @@ async def call(self, event, data=None, to=None, sid=None, namespace=None, timeout=timeout, ignore_queue=ignore_queue) + async def enter_room(self, sid, room, namespace=None): + """Enter a room. + + The only difference with the :func:`socketio.Server.enter_room` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.enter_room( + sid, room, namespace=namespace or self.namespace) + + async def leave_room(self, sid, room, namespace=None): + """Leave a room. + + The only difference with the :func:`socketio.Server.leave_room` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.leave_room( + sid, room, namespace=namespace or self.namespace) + async def close_room(self, room, namespace=None): """Close a room. diff --git a/src/socketio/asyncio_server.py b/src/socketio/asyncio_server.py index acdb265d..1e9cd9c4 100644 --- a/src/socketio/asyncio_server.py +++ b/src/socketio/asyncio_server.py @@ -275,6 +275,40 @@ def event_callback(*args): else callback_args[0][0] if len(callback_args[0]) == 1 \ else None + async def enter_room(self, sid, room, namespace=None): + """Enter a room. + + This function adds the client to a room. The :func:`emit` and + :func:`send` functions can optionally broadcast events to all the + clients in a room. + + :param sid: Session ID of the client. + :param room: Room name. If the room does not exist it is created. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the default namespace is used. + + Note: this method is a coroutine. + """ + namespace = namespace or '/' + self.logger.info('%s is entering room %s [%s]', sid, room, namespace) + await self.manager.enter_room(sid, namespace, room) + + async def leave_room(self, sid, room, namespace=None): + """Leave a room. + + This function removes the client from a room. + + :param sid: Session ID of the client. + :param room: Room name. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the default namespace is used. + + Note: this method is a coroutine. + """ + namespace = namespace or '/' + self.logger.info('%s is leaving room %s [%s]', sid, room, namespace) + await self.manager.leave_room(sid, namespace, room) + async def close_room(self, room, namespace=None): """Close a room. diff --git a/src/socketio/base_manager.py b/src/socketio/base_manager.py index 4330bac4..f0556131 100644 --- a/src/socketio/base_manager.py +++ b/src/socketio/base_manager.py @@ -54,11 +54,11 @@ def connect(self, eio_sid, namespace): """Register a client connection to a namespace.""" sid = self.server.eio.generate_id() try: - self.enter_room(sid, namespace, None, eio_sid=eio_sid) + self.basic_enter_room(sid, namespace, None, eio_sid=eio_sid) except ValueDuplicationError: # already connected return None - self.enter_room(sid, namespace, sid, eio_sid=eio_sid) + self.basic_enter_room(sid, namespace, sid, eio_sid=eio_sid) return sid def is_connected(self, sid, namespace): @@ -106,7 +106,7 @@ def disconnect(self, sid, namespace, **kwargs): if sid in room: rooms.append(room_name) for room in rooms: - self.leave_room(sid, namespace, room) + self.basic_leave_room(sid, namespace, room) if sid in self.callbacks: del self.callbacks[sid] if namespace in self.pending_disconnect and \ @@ -115,7 +115,7 @@ def disconnect(self, sid, namespace, **kwargs): if len(self.pending_disconnect[namespace]) == 0: del self.pending_disconnect[namespace] - def enter_room(self, sid, namespace, room, eio_sid=None): + def basic_enter_room(self, sid, namespace, room, eio_sid=None): """Add a client to a room.""" if eio_sid is None and namespace not in self.rooms: raise ValueError('sid is not connected to requested namespace') @@ -127,7 +127,7 @@ def enter_room(self, sid, namespace, room, eio_sid=None): eio_sid = self.rooms[namespace][None][sid] self.rooms[namespace][room][sid] = eio_sid - def leave_room(self, sid, namespace, room): + def basic_leave_room(self, sid, namespace, room): """Remove a client from a room.""" try: del self.rooms[namespace][room][sid] @@ -138,11 +138,19 @@ def leave_room(self, sid, namespace, room): except KeyError: pass + def enter_room(self, sid, namespace, room, eio_sid=None): + """Add a client to a room.""" + self.basic_enter_room(sid, namespace, room, eio_sid=eio_sid) + + def leave_room(self, sid, namespace, room): + """Remove a client from a room.""" + self.basic_leave_room(sid, namespace, room) + def close_room(self, room, namespace): """Remove all participants from a room.""" try: for sid, _ in self.get_participants(namespace, room): - self.leave_room(sid, namespace, room) + self.basic_leave_room(sid, namespace, room) except KeyError: pass diff --git a/tests/asyncio/test_asyncio_manager.py b/tests/asyncio/test_asyncio_manager.py index c4247234..a32b0fdb 100644 --- a/tests/asyncio/test_asyncio_manager.py +++ b/tests/asyncio/test_asyncio_manager.py @@ -54,8 +54,8 @@ def test_pre_disconnect(self): def test_disconnect(self): sid1 = self.bm.connect('123', '/foo') sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - self.bm.enter_room(sid2, '/foo', 'baz') + _run(self.bm.enter_room(sid1, '/foo', 'bar')) + _run(self.bm.enter_room(sid2, '/foo', 'baz')) _run(self.bm.disconnect(sid1, '/foo')) assert dict(self.bm.rooms['/foo'][None]) == {sid2: '456'} assert dict(self.bm.rooms['/foo'][sid2]) == {sid2: '456'} @@ -97,8 +97,8 @@ def test_disconnect_twice(self): def test_disconnect_all(self): sid1 = self.bm.connect('123', '/foo') sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - self.bm.enter_room(sid2, '/foo', 'baz') + _run(self.bm.enter_room(sid1, '/foo', 'bar')) + _run(self.bm.enter_room(sid2, '/foo', 'baz')) _run(self.bm.disconnect(sid1, '/foo')) _run(self.bm.disconnect(sid2, '/foo')) assert self.bm.rooms == {} @@ -173,8 +173,8 @@ def test_get_participants(self): def test_leave_invalid_room(self): sid = self.bm.connect('123', '/foo') - self.bm.leave_room(sid, '/foo', 'baz') - self.bm.leave_room(sid, '/bar', 'baz') + _run(self.bm.leave_room(sid, '/foo', 'baz')) + _run(self.bm.leave_room(sid, '/bar', 'baz')) def test_no_room(self): rooms = self.bm.get_rooms('123', '/foo') @@ -184,9 +184,11 @@ def test_close_room(self): sid = self.bm.connect('123', '/foo') self.bm.connect('456', '/foo') self.bm.connect('789', '/foo') - self.bm.enter_room(sid, '/foo', 'bar') - self.bm.enter_room(sid, '/foo', 'bar') + _run(self.bm.enter_room(sid, '/foo', 'bar')) + _run(self.bm.enter_room(sid, '/foo', 'bar')) _run(self.bm.close_room('bar', '/foo')) + from pprint import pprint + pprint(self.bm.rooms) assert 'bar' not in self.bm.rooms['/foo'] def test_close_invalid_room(self): @@ -194,7 +196,7 @@ def test_close_invalid_room(self): def test_rooms(self): sid = self.bm.connect('123', '/foo') - self.bm.enter_room(sid, '/foo', 'bar') + _run(self.bm.enter_room(sid, '/foo', 'bar')) r = self.bm.get_rooms(sid, '/foo') assert len(r) == 2 assert sid in r @@ -216,9 +218,9 @@ def test_emit_to_sid(self): def test_emit_to_room(self): sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') + _run(self.bm.enter_room(sid1, '/foo', 'bar')) sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') + _run(self.bm.enter_room(sid2, '/foo', 'bar')) self.bm.connect('789', '/foo') _run( self.bm.emit( @@ -237,12 +239,12 @@ def test_emit_to_room(self): def test_emit_to_rooms(self): sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') + _run(self.bm.enter_room(sid1, '/foo', 'bar')) sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') - self.bm.enter_room(sid2, '/foo', 'baz') + _run(self.bm.enter_room(sid2, '/foo', 'bar')) + _run(self.bm.enter_room(sid2, '/foo', 'baz')) sid3 = self.bm.connect('789', '/foo') - self.bm.enter_room(sid3, '/foo', 'baz') + _run(self.bm.enter_room(sid3, '/foo', 'baz')) _run( self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo', room=['bar', 'baz']) @@ -263,9 +265,9 @@ def test_emit_to_rooms(self): def test_emit_to_all(self): sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') + _run(self.bm.enter_room(sid1, '/foo', 'bar')) sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') + _run(self.bm.enter_room(sid2, '/foo', 'bar')) self.bm.connect('789', '/foo') self.bm.connect('abc', '/bar') _run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo')) @@ -285,9 +287,9 @@ def test_emit_to_all(self): def test_emit_to_all_skip_one(self): sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') + _run(self.bm.enter_room(sid1, '/foo', 'bar')) sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') + _run(self.bm.enter_room(sid2, '/foo', 'bar')) self.bm.connect('789', '/foo') self.bm.connect('abc', '/bar') _run( @@ -307,9 +309,9 @@ def test_emit_to_all_skip_one(self): def test_emit_to_all_skip_two(self): sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') + _run(self.bm.enter_room(sid1, '/foo', 'bar')) sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') + _run(self.bm.enter_room(sid2, '/foo', 'bar')) sid3 = self.bm.connect('789', '/foo') self.bm.connect('abc', '/bar') _run( diff --git a/tests/asyncio/test_asyncio_namespace.py b/tests/asyncio/test_asyncio_namespace.py index 0d9e6ce7..f0fd0213 100644 --- a/tests/asyncio/test_asyncio_namespace.py +++ b/tests/asyncio/test_asyncio_namespace.py @@ -176,25 +176,29 @@ def test_call(self): def test_enter_room(self): ns = asyncio_namespace.AsyncNamespace('/foo') - ns._set_server(mock.MagicMock()) - ns.enter_room('sid', 'room') - ns.server.enter_room.assert_called_with( + mock_server = mock.MagicMock() + mock_server.enter_room = AsyncMock() + ns._set_server(mock_server) + _run(ns.enter_room('sid', 'room')) + ns.server.enter_room.mock.assert_called_with( 'sid', 'room', namespace='/foo' ) - ns.enter_room('sid', 'room', namespace='/bar') - ns.server.enter_room.assert_called_with( + _run(ns.enter_room('sid', 'room', namespace='/bar')) + ns.server.enter_room.mock.assert_called_with( 'sid', 'room', namespace='/bar' ) def test_leave_room(self): ns = asyncio_namespace.AsyncNamespace('/foo') - ns._set_server(mock.MagicMock()) - ns.leave_room('sid', 'room') - ns.server.leave_room.assert_called_with( + mock_server = mock.MagicMock() + mock_server.leave_room = AsyncMock() + ns._set_server(mock_server) + _run(ns.leave_room('sid', 'room')) + ns.server.leave_room.mock.assert_called_with( 'sid', 'room', namespace='/foo' ) - ns.leave_room('sid', 'room', namespace='/bar') - ns.server.leave_room.assert_called_with( + _run(ns.leave_room('sid', 'room', namespace='/bar')) + ns.server.leave_room.mock.assert_called_with( 'sid', 'room', namespace='/bar' ) diff --git a/tests/asyncio/test_asyncio_server.py b/tests/asyncio/test_asyncio_server.py index ef9c2bb6..c39ed788 100644 --- a/tests/asyncio/test_asyncio_server.py +++ b/tests/asyncio/test_asyncio_server.py @@ -29,6 +29,8 @@ def _get_mock_manager(self): mgr = mock.MagicMock() mgr.can_disconnect = AsyncMock() mgr.emit = AsyncMock() + mgr.enter_room = AsyncMock() + mgr.leave_room = AsyncMock() mgr.close_room = AsyncMock() mgr.trigger_callback = AsyncMock() return mgr @@ -231,26 +233,28 @@ def test_call_without_async_handlers(self, eio): def test_enter_room(self, eio): mgr = self._get_mock_manager() s = asyncio_server.AsyncServer(client_manager=mgr) - s.enter_room('123', 'room', namespace='/foo') - s.manager.enter_room.assert_called_once_with('123', '/foo', 'room') + _run(s.enter_room('123', 'room', namespace='/foo')) + s.manager.enter_room.mock.assert_called_once_with('123', '/foo', + 'room') def test_enter_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = asyncio_server.AsyncServer(client_manager=mgr) - s.enter_room('123', 'room') - s.manager.enter_room.assert_called_once_with('123', '/', 'room') + _run(s.enter_room('123', 'room')) + s.manager.enter_room.mock.assert_called_once_with('123', '/', 'room') def test_leave_room(self, eio): mgr = self._get_mock_manager() s = asyncio_server.AsyncServer(client_manager=mgr) - s.leave_room('123', 'room', namespace='/foo') - s.manager.leave_room.assert_called_once_with('123', '/foo', 'room') + _run(s.leave_room('123', 'room', namespace='/foo')) + s.manager.leave_room.mock.assert_called_once_with('123', '/foo', + 'room') def test_leave_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = asyncio_server.AsyncServer(client_manager=mgr) - s.leave_room('123', 'room') - s.manager.leave_room.assert_called_once_with('123', '/', 'room') + _run(s.leave_room('123', 'room')) + s.manager.leave_room.mock.assert_called_once_with('123', '/', 'room') def test_close_room(self, eio): mgr = self._get_mock_manager()