Browse Source

Async versions of enter_room and leave_room should be coroutines (breaking change)

pull/1255/head
Miguel Grinberg 2 years ago
parent
commit
8e3460c5fc
Failed to extract signature
  1. 4
      examples/server/aiohttp/app.py
  2. 4
      examples/server/asgi/app.py
  3. 4
      examples/server/sanic/app.py
  4. 4
      examples/server/tornado/app.py
  5. 14
      src/socketio/asyncio_manager.py
  6. 24
      src/socketio/asyncio_namespace.py
  7. 34
      src/socketio/asyncio_server.py
  8. 20
      src/socketio/base_manager.py
  9. 44
      tests/asyncio/test_asyncio_manager.py
  10. 24
      tests/asyncio/test_asyncio_namespace.py
  11. 20
      tests/asyncio/test_asyncio_server.py

4
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)

4
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)

4
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)

4
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)

14
src/socketio/asyncio_manager.py

@ -69,6 +69,20 @@ class AsyncManager(BaseManager):
"""
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.

24
src/socketio/asyncio_namespace.py

@ -86,6 +86,30 @@ class AsyncNamespace(namespace.Namespace):
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.

34
src/socketio/asyncio_server.py

@ -275,6 +275,40 @@ class AsyncServer(server.Server):
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.

20
src/socketio/base_manager.py

@ -54,11 +54,11 @@ class BaseManager(object):
"""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 @@ class BaseManager(object):
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 @@ class BaseManager(object):
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 @@ class BaseManager(object):
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 @@ class BaseManager(object):
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

44
tests/asyncio/test_asyncio_manager.py

@ -54,8 +54,8 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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 @@ class TestAsyncManager(unittest.TestCase):
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(

24
tests/asyncio/test_asyncio_namespace.py

@ -176,25 +176,29 @@ class TestAsyncNamespace(unittest.TestCase):
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'
)

20
tests/asyncio/test_asyncio_server.py

@ -29,6 +29,8 @@ class TestAsyncServer(unittest.TestCase):
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 @@ class TestAsyncServer(unittest.TestCase):
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()

Loading…
Cancel
Save