Skip to content

Commit 2538df8

Browse files
emit events to multiple rooms (Fixes #605)
1 parent a37ab00 commit 2538df8

File tree

6 files changed

+67
-19
lines changed

6 files changed

+67
-19
lines changed

socketio/asyncio_server.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,11 @@ async def emit(self, event, data=None, to=None, room=None, skip_sid=None,
122122
multiple arguments, use a tuple where each element is of
123123
one of the types indicated above.
124124
:param to: The recipient of the message. This can be set to the
125-
session ID of a client to address only that client, or to
126-
to any custom room created by the application to address all
127-
the clients in that room, If this argument is omitted the
128-
event is broadcasted to all connected clients.
125+
session ID of a client to address only that client, to any
126+
any custom room created by the application to address all
127+
the clients in that room, or to a list of custom room
128+
names. If this argument is omitted the event is broadcasted
129+
to all connected clients.
129130
:param room: Alias for the ``to`` parameter.
130131
:param skip_sid: The session ID of a client to skip when broadcasting
131132
to a room or to all clients. This can be used to
@@ -174,10 +175,11 @@ async def send(self, data, to=None, room=None, skip_sid=None,
174175
multiple arguments, use a tuple where each element is of
175176
one of the types indicated above.
176177
:param to: The recipient of the message. This can be set to the
177-
session ID of a client to address only that client, or to
178-
to any custom room created by the application to address all
179-
the clients in that room, If this argument is omitted the
180-
event is broadcasted to all connected clients.
178+
session ID of a client to address only that client, to any
179+
any custom room created by the application to address all
180+
the clients in that room, or to a list of custom room
181+
names. If this argument is omitted the event is broadcasted
182+
to all connected clients.
181183
:param room: Alias for the ``to`` parameter.
182184
:param skip_sid: The session ID of a client to skip when broadcasting
183185
to a room or to all clients. This can be used to

socketio/base_manager.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ def get_namespaces(self):
3838

3939
def get_participants(self, namespace, room):
4040
"""Return an iterable with the active participants in a room."""
41-
for sid, eio_sid in self.rooms[namespace][room]._fwdm.copy().items():
41+
ns = self.rooms[namespace]
42+
if room is None or isinstance(room, str):
43+
participants = ns[room]._fwdm.copy() if room in ns else {}
44+
else:
45+
participants = ns[room[0]]._fwdm.copy() if room[0] in ns else {}
46+
for r in room[1:]:
47+
participants.update(ns[r]._fwdm if r in ns else {})
48+
for sid, eio_sid in participants.items():
4249
yield sid, eio_sid
4350

4451
def connect(self, eio_sid, namespace):
@@ -147,7 +154,7 @@ def emit(self, event, data, namespace, room=None, skip_sid=None,
147154
callback=None, **kwargs):
148155
"""Emit a message to a single client, a room, or all the clients
149156
connected to the namespace."""
150-
if namespace not in self.rooms or room not in self.rooms[namespace]:
157+
if namespace not in self.rooms:
151158
return
152159
if not isinstance(skip_sid, list):
153160
skip_sid = [skip_sid]

socketio/server.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,11 @@ def emit(self, event, data=None, to=None, room=None, skip_sid=None,
255255
multiple arguments, use a tuple where each element is of
256256
one of the types indicated above.
257257
:param to: The recipient of the message. This can be set to the
258-
session ID of a client to address only that client, or to
259-
to any custom room created by the application to address all
260-
the clients in that room, If this argument is omitted the
261-
event is broadcasted to all connected clients.
258+
session ID of a client to address only that client, to any
259+
any custom room created by the application to address all
260+
the clients in that room, or to a list of custom room
261+
names. If this argument is omitted the event is broadcasted
262+
to all connected clients.
262263
:param room: Alias for the ``to`` parameter.
263264
:param skip_sid: The session ID of a client to skip when broadcasting
264265
to a room or to all clients. This can be used to
@@ -305,10 +306,11 @@ def send(self, data, to=None, room=None, skip_sid=None, namespace=None,
305306
multiple arguments, use a tuple where each element is of
306307
one of the types indicated above.
307308
:param to: The recipient of the message. This can be set to the
308-
session ID of a client to address only that client, or to
309-
to any custom room created by the application to address all
310-
the clients in that room, If this argument is omitted the
311-
event is broadcasted to all connected clients.
309+
session ID of a client to address only that client, to any
310+
any custom room created by the application to address all
311+
the clients in that room, or to a list of custom room
312+
names. If this argument is omitted the event is broadcasted
313+
to all connected clients.
312314
:param room: Alias for the ``to`` parameter.
313315
:param skip_sid: The session ID of a client to skip when broadcasting
314316
to a room or to all clients. This can be used to

tests/asyncio/test_asyncio_pubsub_manager.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@ def _run(coro):
2929
@unittest.skipIf(sys.version_info < (3, 5), 'only for Python 3.5+')
3030
class TestAsyncPubSubManager(unittest.TestCase):
3131
def setUp(self):
32+
id = 0
33+
34+
def generate_id():
35+
nonlocal id
36+
id += 1
37+
return str(id)
38+
3239
mock_server = mock.MagicMock()
40+
mock_server.eio.generate_id = generate_id
3341
mock_server._emit_internal = AsyncMock()
3442
mock_server.disconnect = AsyncMock()
3543
self.pm = asyncio_pubsub_manager.AsyncPubSubManager()

tests/common/test_base_manager.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,27 @@ def test_emit_to_room(self):
224224
'456', 'my event', {'foo': 'bar'}, '/foo', None
225225
)
226226

227+
def test_emit_to_rooms(self):
228+
sid1 = self.bm.connect('123', '/foo')
229+
self.bm.enter_room(sid1, '/foo', 'bar')
230+
sid2 = self.bm.connect('456', '/foo')
231+
self.bm.enter_room(sid2, '/foo', 'bar')
232+
self.bm.enter_room(sid2, '/foo', 'baz')
233+
sid3 = self.bm.connect('789', '/foo')
234+
self.bm.enter_room(sid3, '/foo', 'baz')
235+
self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo',
236+
room=['bar', 'baz'])
237+
assert self.bm.server._emit_internal.call_count == 3
238+
self.bm.server._emit_internal.assert_any_call(
239+
'123', 'my event', {'foo': 'bar'}, '/foo', None
240+
)
241+
self.bm.server._emit_internal.assert_any_call(
242+
'456', 'my event', {'foo': 'bar'}, '/foo', None
243+
)
244+
self.bm.server._emit_internal.assert_any_call(
245+
'789', 'my event', {'foo': 'bar'}, '/foo', None
246+
)
247+
227248
def test_emit_to_all(self):
228249
sid1 = self.bm.connect('123', '/foo')
229250
self.bm.enter_room(sid1, '/foo', 'bar')

tests/common/test_pubsub_manager.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@
99
from socketio import pubsub_manager
1010

1111

12-
class TestBaseManager(unittest.TestCase):
12+
class TestPubSubManager(unittest.TestCase):
1313
def setUp(self):
14+
id = 0
15+
16+
def generate_id():
17+
nonlocal id
18+
id += 1
19+
return str(id)
20+
1421
mock_server = mock.MagicMock()
22+
mock_server.eio.generate_id = generate_id
1523
self.pm = pubsub_manager.PubSubManager()
1624
self.pm._publish = mock.MagicMock()
1725
self.pm.set_server(mock_server)

0 commit comments

Comments
 (0)