134 lines
4.2 KiB
Python
134 lines
4.2 KiB
Python
import functools
|
|
|
|
from asgiref.sync import async_to_sync
|
|
|
|
from . import DEFAULT_CHANNEL_LAYER
|
|
from .db import database_sync_to_async
|
|
from .exceptions import StopConsumer
|
|
from .layers import get_channel_layer
|
|
from .utils import await_many_dispatch
|
|
|
|
|
|
def get_handler_name(message):
|
|
"""
|
|
Looks at a message, checks it has a sensible type, and returns the
|
|
handler name for that type.
|
|
"""
|
|
# Check message looks OK
|
|
if "type" not in message:
|
|
raise ValueError("Incoming message has no 'type' attribute")
|
|
# Extract type and replace . with _
|
|
handler_name = message["type"].replace(".", "_")
|
|
if handler_name.startswith("_"):
|
|
raise ValueError("Malformed type in message (leading underscore)")
|
|
return handler_name
|
|
|
|
|
|
class AsyncConsumer:
|
|
"""
|
|
Base consumer class. Implements the ASGI application spec, and adds on
|
|
channel layer management and routing of events to named methods based
|
|
on their type.
|
|
"""
|
|
|
|
_sync = False
|
|
channel_layer_alias = DEFAULT_CHANNEL_LAYER
|
|
|
|
async def __call__(self, scope, receive, send):
|
|
"""
|
|
Dispatches incoming messages to type-based handlers asynchronously.
|
|
"""
|
|
self.scope = scope
|
|
|
|
# Initialize channel layer
|
|
self.channel_layer = get_channel_layer(self.channel_layer_alias)
|
|
if self.channel_layer is not None:
|
|
self.channel_name = await self.channel_layer.new_channel()
|
|
self.channel_receive = functools.partial(
|
|
self.channel_layer.receive, self.channel_name
|
|
)
|
|
# Store send function
|
|
if self._sync:
|
|
self.base_send = async_to_sync(send)
|
|
else:
|
|
self.base_send = send
|
|
# Pass messages in from channel layer or client to dispatch method
|
|
try:
|
|
if self.channel_layer is not None:
|
|
await await_many_dispatch(
|
|
[receive, self.channel_receive], self.dispatch
|
|
)
|
|
else:
|
|
await await_many_dispatch([receive], self.dispatch)
|
|
except StopConsumer:
|
|
# Exit cleanly
|
|
pass
|
|
|
|
async def dispatch(self, message):
|
|
"""
|
|
Works out what to do with a message.
|
|
"""
|
|
handler = getattr(self, get_handler_name(message), None)
|
|
if handler:
|
|
await handler(message)
|
|
else:
|
|
raise ValueError("No handler for message type %s" % message["type"])
|
|
|
|
async def send(self, message):
|
|
"""
|
|
Overrideable/callable-by-subclasses send method.
|
|
"""
|
|
await self.base_send(message)
|
|
|
|
@classmethod
|
|
def as_asgi(cls, **initkwargs):
|
|
"""
|
|
Return an ASGI v3 single callable that instantiates a consumer instance
|
|
per scope. Similar in purpose to Django's as_view().
|
|
|
|
initkwargs will be used to instantiate the consumer instance.
|
|
"""
|
|
|
|
async def app(scope, receive, send):
|
|
consumer = cls(**initkwargs)
|
|
return await consumer(scope, receive, send)
|
|
|
|
app.consumer_class = cls
|
|
app.consumer_initkwargs = initkwargs
|
|
|
|
# take name and docstring from class
|
|
functools.update_wrapper(app, cls, updated=())
|
|
return app
|
|
|
|
|
|
class SyncConsumer(AsyncConsumer):
|
|
"""
|
|
Synchronous version of the consumer, which is what we write most of the
|
|
generic consumers against (for now). Calls handlers in a threadpool and
|
|
uses CallBouncer to get the send method out to the main event loop.
|
|
|
|
It would have been possible to have "mixed" consumers and auto-detect
|
|
if a handler was awaitable or not, but that would have made the API
|
|
for user-called methods very confusing as there'd be two types of each.
|
|
"""
|
|
|
|
_sync = True
|
|
|
|
@database_sync_to_async
|
|
def dispatch(self, message):
|
|
"""
|
|
Dispatches incoming messages to type-based handlers asynchronously.
|
|
"""
|
|
# Get and execute the handler
|
|
handler = getattr(self, get_handler_name(message), None)
|
|
if handler:
|
|
handler(message)
|
|
else:
|
|
raise ValueError("No handler for message type %s" % message["type"])
|
|
|
|
def send(self, message):
|
|
"""
|
|
Overrideable/callable-by-subclasses send method.
|
|
"""
|
|
self.base_send(message)
|