|
13 | 13 | cast,
|
14 | 14 | overload,
|
15 | 15 | )
|
| 16 | +from uuid import uuid4 |
16 | 17 |
|
17 | 18 | import orjson as pickle
|
| 19 | +from channels import DEFAULT_CHANNEL_LAYER |
18 | 20 | from channels.db import database_sync_to_async
|
19 |
| -from reactpy import use_callback, use_effect, use_ref, use_state |
| 21 | +from channels.layers import InMemoryChannelLayer, get_channel_layer |
| 22 | +from reactpy import use_callback, use_effect, use_memo, use_ref, use_state |
20 | 23 | from reactpy import use_connection as _use_connection
|
21 | 24 | from reactpy import use_location as _use_location
|
22 | 25 | from reactpy import use_scope as _use_scope
|
23 | 26 | from reactpy.backend.types import Location
|
24 | 27 |
|
25 | 28 | from reactpy_django.exceptions import UserNotFoundError
|
26 | 29 | from reactpy_django.types import (
|
| 30 | + AsyncMessageReceiver, |
| 31 | + AsyncMessageSender, |
27 | 32 | ConnectionType,
|
28 | 33 | FuncParams,
|
29 | 34 | Inferred,
|
|
36 | 41 | from reactpy_django.utils import generate_obj_name, get_user_pk
|
37 | 42 |
|
38 | 43 | if TYPE_CHECKING:
|
| 44 | + from channels_redis.core import RedisChannelLayer |
39 | 45 | from django.contrib.auth.models import AbstractUser
|
40 | 46 |
|
41 | 47 |
|
@@ -361,6 +367,65 @@ async def _set_user_data(data: dict):
|
361 | 367 | return UserData(query, mutation)
|
362 | 368 |
|
363 | 369 |
|
| 370 | +def use_channel_layer( |
| 371 | + name: str, |
| 372 | + receiver: AsyncMessageReceiver | None = None, |
| 373 | + group: bool = False, |
| 374 | + layer: str = DEFAULT_CHANNEL_LAYER, |
| 375 | +) -> AsyncMessageSender: |
| 376 | + """ |
| 377 | + Subscribe to a Django Channels layer to send/receive messages. |
| 378 | +
|
| 379 | + Args: |
| 380 | + name: The name of the channel to subscribe to. |
| 381 | + receiver: An async function that receives a `message: dict` from the channel layer. \ |
| 382 | + If more than one receiver waits on the same channel, a random one \ |
| 383 | + will get the result (unless `group=True` is defined). |
| 384 | + group: If `True`, a "group channel" will be used. Messages sent within a \ |
| 385 | + group are broadcasted to all receivers on that channel. |
| 386 | + layer: The channel layer to use. These layers must be defined in \ |
| 387 | + `settings.py:CHANNEL_LAYERS`. |
| 388 | + """ |
| 389 | + channel_layer: InMemoryChannelLayer | RedisChannelLayer = get_channel_layer(layer) |
| 390 | + channel_name = use_memo(lambda: str(uuid4() if group else name)) |
| 391 | + group_name = name if group else "" |
| 392 | + |
| 393 | + if not channel_layer: |
| 394 | + raise ValueError( |
| 395 | + f"Channel layer '{layer}' is not available. Are you sure you" |
| 396 | + " configured settings.py:CHANNEL_LAYERS properly?" |
| 397 | + ) |
| 398 | + |
| 399 | + # Add/remove a group's channel during component mount/dismount respectively. |
| 400 | + @use_effect(dependencies=[]) |
| 401 | + async def group_manager(): |
| 402 | + if group: |
| 403 | + await channel_layer.group_add(group_name, channel_name) |
| 404 | + |
| 405 | + return lambda: asyncio.run( |
| 406 | + channel_layer.group_discard(group_name, channel_name) |
| 407 | + ) |
| 408 | + |
| 409 | + # Listen for messages on the channel using the provided `receiver` function. |
| 410 | + @use_effect |
| 411 | + async def message_receiver(): |
| 412 | + if not receiver or not channel_name: |
| 413 | + return |
| 414 | + |
| 415 | + while True: |
| 416 | + message = await channel_layer.receive(channel_name) |
| 417 | + await receiver(message) |
| 418 | + |
| 419 | + # User interface for sending messages to the channel |
| 420 | + async def message_sender(message: dict): |
| 421 | + if group: |
| 422 | + await channel_layer.group_send(group_name, message) |
| 423 | + else: |
| 424 | + await channel_layer.send(channel_name, message) |
| 425 | + |
| 426 | + return message_sender |
| 427 | + |
| 428 | + |
364 | 429 | def _use_query_args_1(options: QueryOptions, /, query: Query, *args, **kwargs):
|
365 | 430 | return options, query, args, kwargs
|
366 | 431 |
|
|
0 commit comments