Skip to content

Commit 6f41206

Browse files
async namespaces, and more unit tests
1 parent 7635832 commit 6f41206

9 files changed

+1154
-2
lines changed

socketio/asyncio_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async def trigger_callback(self, sid, namespace, id, data):
3333
else:
3434
del self.callbacks[sid][namespace][id]
3535
if callback is not None:
36-
if asyncio.iscoroutinefunction(callback):
36+
if asyncio.iscoroutinefunction(callback) is True:
3737
try:
3838
await callback(*data)
3939
except asyncio.CancelledError: # pragma: no cover

socketio/asyncio_namespace.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import asyncio
2+
3+
from socketio import namespace
4+
5+
6+
class AsyncNamespace(namespace.Namespace):
7+
"""Base class for asyncio class-based namespaces.
8+
9+
A class-based namespace is a class that contains all the event handlers
10+
for a Socket.IO namespace. The event handlers are methods of the class
11+
with the prefix ``on_``, such as ``on_connect``, ``on_disconnect``,
12+
``on_message``, ``on_json``, and so on. These can be regular functions or
13+
coroutines.
14+
15+
:param namespace: The Socket.IO namespace to be used with all the event
16+
handlers defined in this class. If this argument is
17+
omitted, the default namespace is used.
18+
"""
19+
def is_asyncio_based(self):
20+
return True
21+
22+
async def trigger_event(self, event, *args):
23+
"""Dispatch an event to the proper handler method.
24+
25+
In the most common usage, this method is not overloaded by subclasses,
26+
as it performs the routing of events to methods. However, this
27+
method can be overriden if special dispatching rules are needed, or if
28+
having a single method that catches all events is desired.
29+
30+
Note: this method is a coroutine.
31+
"""
32+
handler_name = 'on_' + event
33+
if hasattr(self, handler_name):
34+
handler = getattr(self, handler_name)
35+
if asyncio.iscoroutinefunction(handler) is True:
36+
try:
37+
ret = await handler(*args)
38+
except asyncio.CancelledError: # pragma: no cover
39+
pass
40+
else:
41+
ret = handler(*args)
42+
return ret
43+
44+
async def emit(self, event, data=None, room=None, skip_sid=None,
45+
namespace=None, callback=None):
46+
"""Emit a custom event to one or more connected clients.
47+
48+
The only difference with the :func:`socketio.Server.emit` method is
49+
that when the ``namespace`` argument is not given the namespace
50+
associated with the class is used.
51+
52+
Note: this method is a coroutine.
53+
"""
54+
return await self.server.emit(event, data=data, room=room,
55+
skip_sid=skip_sid,
56+
namespace=namespace or self.namespace,
57+
callback=callback)
58+
59+
async def send(self, data, room=None, skip_sid=None, namespace=None,
60+
callback=None):
61+
"""Send a message to one or more connected clients.
62+
63+
The only difference with the :func:`socketio.Server.send` method is
64+
that when the ``namespace`` argument is not given the namespace
65+
associated with the class is used.
66+
67+
Note: this method is a coroutine.
68+
"""
69+
return await self.server.send(data, room=room, skip_sid=skip_sid,
70+
namespace=namespace or self.namespace,
71+
callback=callback)
72+
73+
async def disconnect(self, sid, namespace=None):
74+
"""Disconnect a client.
75+
76+
The only difference with the :func:`socketio.Server.disconnect` method
77+
is that when the ``namespace`` argument is not given the namespace
78+
associated with the class is used.
79+
80+
Note: this method is a coroutine.
81+
"""
82+
return await self.server.disconnect(
83+
sid, namespace=namespace or self.namespace)

socketio/asyncio_server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ async def _trigger_event(self, event, namespace, *args):
305305
"""Invoke an application event handler."""
306306
# first see if we have an explicit handler for the event
307307
if namespace in self.handlers and event in self.handlers[namespace]:
308-
if asyncio.iscoroutinefunction(self.handlers[namespace][event]):
308+
if asyncio.iscoroutinefunction(self.handlers[namespace][event]) \
309+
is True:
309310
try:
310311
ret = await self.handlers[namespace][event](*args)
311312
except asyncio.CancelledError: # pragma: no cover

socketio/namespace.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def __init__(self, namespace=None):
1717
def _set_server(self, server):
1818
self.server = server
1919

20+
def is_asyncio_based(self):
21+
return False
22+
2023
def trigger_event(self, event, *args):
2124
"""Dispatch an event to the proper handler method.
2225

socketio/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ def __init__(self, client_manager=None, logger=False, binary=False,
113113

114114
self.async_mode = self.eio.async_mode
115115

116+
def is_asyncio_based(self):
117+
return False
118+
116119
def on(self, event, handler=None, namespace=None):
117120
"""Register an event handler.
118121
@@ -171,6 +174,8 @@ def register_namespace(self, namespace_handler):
171174
"""
172175
if not isinstance(namespace_handler, namespace.Namespace):
173176
raise ValueError('Not a namespace instance')
177+
if self.is_asyncio_based() != namespace_handler.is_asyncio_based():
178+
raise ValueError('Not a valid namespace class for this server')
174179
namespace_handler._set_server(self)
175180
self.namespace_handlers[namespace_handler.namespace] = \
176181
namespace_handler

0 commit comments

Comments
 (0)