Skip to content

Commit 28569d4

Browse files
Catch-all event handlers
1 parent dff3d3d commit 28569d4

File tree

10 files changed

+130
-31
lines changed

10 files changed

+130
-31
lines changed

docs/client.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ or can also be coroutines::
6565
async def message(data):
6666
print('I received a message!')
6767

68+
Catch-All Event Handlers
69+
------------------------
70+
71+
A "catch-all" event handler is invoked for any events that do not have an
72+
event handler. You can define a catch-all handler using ``'*'`` as event name::
73+
74+
@sio.on('*')
75+
def catch_all(event, sid, data):
76+
pass
77+
78+
Asyncio clients can also use a coroutine::
79+
80+
@sio.on('*')
81+
async def catch_all(event, sid, data):
82+
pass
83+
84+
A catch-all event handler receives the event name as a first argument. The
85+
remaining arguments are the same as for a regular event handler.
86+
87+
Connect, Connect Error and Disconnect Event Handlers
88+
----------------------------------------------------
89+
6890
The ``connect``, ``connect_error`` and ``disconnect`` events are special; they
6991
are invoked automatically when a client connects or disconnects from the
7092
server::

docs/server.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,28 @@ The ``sid`` argument is the Socket.IO session id, a unique identifier of each
178178
client connection. All the events sent by a given client will have the same
179179
``sid`` value.
180180

181+
Catch-All Event Handlers
182+
------------------------
183+
184+
A "catch-all" event handler is invoked for any events that do not have an
185+
event handler. You can define a catch-all handler using ``'*'`` as event name::
186+
187+
@sio.on('*')
188+
def catch_all(event, sid, data):
189+
pass
190+
191+
Asyncio servers can also use a coroutine::
192+
193+
@sio.on('*')
194+
async def catch_all(event, sid, data):
195+
pass
196+
197+
A catch-all event handler receives the event name as a first argument. The
198+
remaining arguments are the same as for a regular event handler.
199+
200+
Connect and Disconnect Event Handlers
201+
-------------------------------------
202+
181203
The ``connect`` and ``disconnect`` events are special; they are invoked
182204
automatically when a client connects or disconnects from the server::
183205

src/socketio/asyncio_client.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -418,15 +418,22 @@ async def _handle_error(self, namespace, data):
418418
async def _trigger_event(self, event, namespace, *args):
419419
"""Invoke an application event handler."""
420420
# first see if we have an explicit handler for the event
421-
if namespace in self.handlers and event in self.handlers[namespace]:
422-
if asyncio.iscoroutinefunction(self.handlers[namespace][event]):
423-
try:
424-
ret = await self.handlers[namespace][event](*args)
425-
except asyncio.CancelledError: # pragma: no cover
426-
ret = None
427-
else:
428-
ret = self.handlers[namespace][event](*args)
429-
return ret
421+
if namespace in self.handlers:
422+
handler = None
423+
if event in self.handlers[namespace]:
424+
handler = self.handlers[namespace][event]
425+
elif '*' in self.handlers[namespace]:
426+
handler = self.handlers[namespace]['*']
427+
args = (event, *args)
428+
if handler:
429+
if asyncio.iscoroutinefunction(handler):
430+
try:
431+
ret = await handler(*args)
432+
except asyncio.CancelledError: # pragma: no cover
433+
ret = None
434+
else:
435+
ret = handler(*args)
436+
return ret
430437

431438
# or else, forward the event to a namepsace handler if one exists
432439
elif namespace in self.namespace_handlers:

src/socketio/asyncio_server.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -524,16 +524,22 @@ async def _handle_ack(self, eio_sid, namespace, id, data):
524524
async def _trigger_event(self, event, namespace, *args):
525525
"""Invoke an application event handler."""
526526
# first see if we have an explicit handler for the event
527-
if namespace in self.handlers and event in self.handlers[namespace]:
528-
if asyncio.iscoroutinefunction(self.handlers[namespace][event]) \
529-
is True:
530-
try:
531-
ret = await self.handlers[namespace][event](*args)
532-
except asyncio.CancelledError: # pragma: no cover
533-
ret = None
534-
else:
535-
ret = self.handlers[namespace][event](*args)
536-
return ret
527+
if namespace in self.handlers:
528+
handler = None
529+
if event in self.handlers[namespace]:
530+
handler = self.handlers[namespace][event]
531+
elif '*' in self.handlers[namespace]:
532+
handler = self.handlers[namespace]['*']
533+
args = (event, *args)
534+
if handler:
535+
if asyncio.iscoroutinefunction(handler):
536+
try:
537+
ret = await handler(*args)
538+
except asyncio.CancelledError: # pragma: no cover
539+
ret = None
540+
else:
541+
ret = handler(*args)
542+
return ret
537543

538544
# or else, forward the event to a namepsace handler if one exists
539545
elif namespace in self.namespace_handlers:

src/socketio/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,11 @@ def _handle_error(self, namespace, data):
609609
def _trigger_event(self, event, namespace, *args):
610610
"""Invoke an application event handler."""
611611
# first see if we have an explicit handler for the event
612-
if namespace in self.handlers and event in self.handlers[namespace]:
613-
return self.handlers[namespace][event](*args)
612+
if namespace in self.handlers:
613+
if event in self.handlers[namespace]:
614+
return self.handlers[namespace][event](*args)
615+
elif '*' in self.handlers[namespace]:
616+
return self.handlers[namespace]['*'](event, *args)
614617

615618
# or else, forward the event to a namespace handler if one exists
616619
elif namespace in self.namespace_handlers:

src/socketio/server.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,11 @@ def _handle_ack(self, eio_sid, namespace, id, data):
732732
def _trigger_event(self, event, namespace, *args):
733733
"""Invoke an application event handler."""
734734
# first see if we have an explicit handler for the event
735-
if namespace in self.handlers and event in self.handlers[namespace]:
736-
return self.handlers[namespace][event](*args)
735+
if namespace in self.handlers:
736+
if event in self.handlers[namespace]:
737+
return self.handlers[namespace][event](*args)
738+
elif '*' in self.handlers[namespace]:
739+
return self.handlers[namespace]['*'](event, *args)
737740

738741
# or else, forward the event to a namespace handler if one exists
739742
elif namespace in self.namespace_handlers:

tests/asyncio/test_asyncio_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,16 +833,24 @@ def test_handle_error_unknown_namespace(self):
833833
def test_trigger_event(self):
834834
c = asyncio_client.AsyncClient()
835835
handler = mock.MagicMock()
836+
catchall_handler = mock.MagicMock()
836837
c.on('foo', handler)
838+
c.on('*', catchall_handler)
837839
_run(c._trigger_event('foo', '/', 1, '2'))
840+
_run(c._trigger_event('bar', '/', 1, '2', 3))
838841
handler.assert_called_once_with(1, '2')
842+
catchall_handler.assert_called_once_with('bar', 1, '2', 3)
839843

840844
def test_trigger_event_namespace(self):
841845
c = asyncio_client.AsyncClient()
842846
handler = AsyncMock()
847+
catchall_handler = AsyncMock()
843848
c.on('foo', handler, namespace='/bar')
849+
c.on('*', catchall_handler, namespace='/bar')
844850
_run(c._trigger_event('foo', '/bar', 1, '2'))
851+
_run(c._trigger_event('bar', '/bar', 1, '2', 3))
845852
handler.mock.assert_called_once_with(1, '2')
853+
catchall_handler.mock.assert_called_once_with('bar', 1, '2', 3)
846854

847855
def test_trigger_event_class_namespace(self):
848856
c = asyncio_client.AsyncClient()

tests/asyncio/test_asyncio_server.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -618,18 +618,28 @@ def test_handle_event(self, eio):
618618
s = asyncio_server.AsyncServer(async_handlers=False)
619619
sid = s.manager.connect('123', '/')
620620
handler = AsyncMock()
621-
s.on('my message', handler)
621+
catchall_handler = AsyncMock()
622+
s.on('msg', handler)
623+
s.on('*', catchall_handler)
624+
_run(s._handle_eio_message('123', '2["msg","a","b"]'))
622625
_run(s._handle_eio_message('123', '2["my message","a","b","c"]'))
623-
handler.mock.assert_called_once_with(sid, 'a', 'b', 'c')
626+
handler.mock.assert_called_once_with(sid, 'a', 'b')
627+
catchall_handler.mock.assert_called_once_with(
628+
'my message', sid, 'a', 'b', 'c')
624629

625630
def test_handle_event_with_namespace(self, eio):
626631
eio.return_value.send = AsyncMock()
627632
s = asyncio_server.AsyncServer(async_handlers=False)
628633
sid = s.manager.connect('123', '/foo')
629634
handler = mock.MagicMock()
630-
s.on('my message', handler, namespace='/foo')
635+
catchall_handler = mock.MagicMock()
636+
s.on('msg', handler, namespace='/foo')
637+
s.on('*', catchall_handler, namespace='/foo')
638+
_run(s._handle_eio_message('123', '2/foo,["msg","a","b"]'))
631639
_run(s._handle_eio_message('123', '2/foo,["my message","a","b","c"]'))
632-
handler.assert_called_once_with(sid, 'a', 'b', 'c')
640+
handler.assert_called_once_with(sid, 'a', 'b')
641+
catchall_handler.assert_called_once_with(
642+
'my message', sid, 'a', 'b', 'c')
633643

634644
def test_handle_event_with_disconnected_namespace(self, eio):
635645
eio.return_value.send = AsyncMock()

tests/common/test_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,16 +934,24 @@ def test_handle_error_unknown_namespace(self):
934934
def test_trigger_event(self):
935935
c = client.Client()
936936
handler = mock.MagicMock()
937+
catchall_handler = mock.MagicMock()
937938
c.on('foo', handler)
939+
c.on('*', catchall_handler)
938940
c._trigger_event('foo', '/', 1, '2')
941+
c._trigger_event('bar', '/', 1, '2', 3)
939942
handler.assert_called_once_with(1, '2')
943+
catchall_handler.assert_called_once_with('bar', 1, '2', 3)
940944

941945
def test_trigger_event_namespace(self):
942946
c = client.Client()
943947
handler = mock.MagicMock()
948+
catchall_handler = mock.MagicMock()
944949
c.on('foo', handler, namespace='/bar')
950+
c.on('*', catchall_handler, namespace='/bar')
945951
c._trigger_event('foo', '/bar', 1, '2')
952+
c._trigger_event('bar', '/bar', 1, '2', 3)
946953
handler.assert_called_once_with(1, '2')
954+
catchall_handler.assert_called_once_with('bar', 1, '2', 3)
947955

948956
def test_trigger_event_class_namespace(self):
949957
c = client.Client()

tests/common/test_server.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -546,17 +546,27 @@ def test_handle_event(self, eio):
546546
s = server.Server(async_handlers=False)
547547
s.manager.connect('123', '/')
548548
handler = mock.MagicMock()
549-
s.on('my message', handler)
549+
catchall_handler = mock.MagicMock()
550+
s.on('msg', handler)
551+
s.on('*', catchall_handler)
552+
s._handle_eio_message('123', '2["msg","a","b"]')
550553
s._handle_eio_message('123', '2["my message","a","b","c"]')
551-
handler.assert_called_once_with('1', 'a', 'b', 'c')
554+
handler.assert_called_once_with('1', 'a', 'b')
555+
catchall_handler.assert_called_once_with(
556+
'my message', '1', 'a', 'b', 'c')
552557

553558
def test_handle_event_with_namespace(self, eio):
554559
s = server.Server(async_handlers=False)
555560
s.manager.connect('123', '/foo')
556561
handler = mock.MagicMock()
557-
s.on('my message', handler, namespace='/foo')
562+
catchall_handler = mock.MagicMock()
563+
s.on('msg', handler, namespace='/foo')
564+
s.on('*', catchall_handler, namespace='/foo')
565+
s._handle_eio_message('123', '2/foo,["msg","a","b"]')
558566
s._handle_eio_message('123', '2/foo,["my message","a","b","c"]')
559-
handler.assert_called_once_with('1', 'a', 'b', 'c')
567+
handler.assert_called_once_with('1', 'a', 'b')
568+
catchall_handler.assert_called_once_with(
569+
'my message', '1', 'a', 'b', 'c')
560570

561571
def test_handle_event_with_disconnected_namespace(self, eio):
562572
s = server.Server(async_handlers=False)

0 commit comments

Comments
 (0)