1212- Structured message handling with Pydantic validation
1313- Automatic group management for pub/sub messaging
1414- Typed channel event system with discriminated unions
15+ - Generic type parameters for compile-time type safety
1516- Comprehensive error handling and reporting
1617- Configurable logging and message completion signals
1718- Support for object-level permissions and retrieval
1819
19- Developers should subclass AsyncJsonWebsocketConsumer and implement the
20- receive_message method to handle incoming messages. The consumer automatically
21- handles connection lifecycle, authentication, message validation, and group
22- messaging.
20+ Generic Type System:
21+ The consumer uses four generic type parameters for type safety:
22+ - IC (Incoming): Union of BaseMessage subclasses for incoming messages
23+ - Event (optional): Union of BaseChannelEvent subclasses for channel events
24+ - OG (optional): BaseGroupMessage subclass for outgoing group messages
25+ - M (optional): Model subclass for object-level permissions
26+
27+ Developers should subclass AsyncJsonWebsocketConsumer with appropriate generic parameters
28+ and implement the receive_message method to handle incoming messages. The consumer
29+ automatically handles connection lifecycle, authentication, message validation, and
30+ group messaging.
2331"""
2432
2533import asyncio
92100
93101
94102class EventPayload (TypedDict ):
103+ """
104+ Channel layer message containing event data.
105+
106+ Attributes:
107+ event_data: Serialized event data dictionary
108+ """
109+
95110 event_data : dict [str , Any ]
96111
97112
98113class AsyncJsonWebsocketConsumer (
99114 Generic [IC , Event , OG , M ], BaseAsyncJsonWebsocketConsumer , ABC
100115):
101116 """
102- Base class for asynchronous JSON WebSocket consumers with authentication and permissions.
117+ Base class for asynchronous JSON WebSocket consumers with authentication and permissions.
103118
104119 Provides DRF-style authentication/permissions, structured message handling with
105- Pydantic validation, logging, and error handling. Subclasses must implement
106- `receive_message` and set `INCOMING_MESSAGE_SCHEMA`.
120+ Pydantic validation, typed channel events, group messaging, logging, and error handling.
121+ Subclasses must implement `receive_message` and specify the incoming message type as
122+ a generic parameter.
123+
124+ For typed channel events, subclasses can define a union type of channel events
125+ and use the Event generic parameter to enable type-safe channel event handling.
107126
108- For group messaging functionality, subclasses should also define
109- `OUTGOING_GROUP_MESSAGE_SCHEMA` to enable proper validation and handling
127+ For group messaging functionality, subclasses should also define the outgoing group
128+ message type using the OG generic parameter to enable proper validation and handling
110129 of group message broadcasts.
111130
112- For typed channel events, subclasses can define a union type of channel events
113- and use the generic type parameter Event to enable type-safe channel event handling.
131+
132+ Generic Parameters:
133+ IC: Incoming message type (required) - Union of BaseMessage subclasses
134+ Event: Channel event type (optional) - Union of BaseChannelEvent subclasses or None
135+ OG: Outgoing group message type (optional) - BaseGroupMessage subclass or None
136+ M: Model type for object-level permissions (optional) - Model subclass or None
114137
115138 Attributes:
116139 authentication_classes: DRF authentication classes for connection verification
@@ -155,6 +178,24 @@ class AsyncJsonWebsocketConsumer(
155178 obj : M
156179
157180 def __init_subclass__ (cls , * args : Any , ** kwargs : Any ):
181+ """
182+ Validates and extracts generic type parameters during class definition.
183+
184+ This method automatically extracts the generic type parameters (IC, Event, OG, M)
185+ from the class definition and stores them for runtime use. It ensures that
186+ at least the incoming message type (IC) is specified.
187+
188+ Handles differences between Python versions:
189+ - Python 3.10: Only returns non-default type arguments
190+ - Python 3.11+: Returns all type arguments including defaults
191+
192+ Args:
193+ *args: Variable arguments passed to parent __init_subclass__
194+ **kwargs: Keyword arguments passed to parent __init_subclass__
195+
196+ Raises:
197+ ValueError: If no generic parameters are specified (must specify at least IC)
198+ """
158199 super ().__init_subclass__ (* args , ** kwargs )
159200
160201 # Extract the actual type from Generic parameters
@@ -437,9 +478,10 @@ async def receive_message(self, message: IC, **kwargs: Any) -> None:
437478 Process a validated received message.
438479
439480 Must be implemented by subclasses to handle messages after validation.
481+ The message parameter is automatically typed based on the IC generic parameter.
440482
441483 Args:
442- message: The validated message object
484+ message: The validated message object (typed as IC)
443485 **kwargs: Additional keyword arguments
444486 """
445487
@@ -539,18 +581,17 @@ async def send_group_message(
539581
540582 Important:
541583 When using kind="message" (the default), your consumer class must define
542- OUTGOING_GROUP_MESSAGE_SCHEMA to properly validate and wrap the message.
543- This schema ensures that group messages follow the expected structure
544- and contain the required metadata. If not defined, use kind="json" instead.
584+ the OG generic parameter (outgoing group message type) to properly validate
585+ and handle the message. If not defined, use kind="json" instead.
545586
546587 Args:
547588 message: Message object to send to the groups
548589 groups: Group names to send to (defaults to self.groups)
549590 kind: Format to send the message as:
550591
551- - "message": Validated and wrapped via OUTGOING_GROUP_MESSAGE_SCHEMA (default)
592+ - "message": Validated via OG generic parameter (default)
552593
553- - "json": Sent as raw JSON without validation or wrapping
594+ - "json": Sent as raw JSON without validation
554595 exclude_current: Whether to exclude the sending consumer from receiving
555596 the broadcast (prevents echo effects)
556597 """
@@ -618,28 +659,16 @@ async def asend_channel_event(
618659 event : Event ,
619660 ) -> None :
620661 """
621- Send a typed channel event to one or more channel groups .
662+ Send a typed channel event to a channel group .
622663
623664 This is a class method that provides a type-safe way to send events through
624665 the channel layer to consumers. It can be called from tasks, views, or other
625- places where you don't have a consumer instance.
666+ places where you don't have a consumer instance. The event type is constrained
667+ by the consumer's Event generic parameter.
626668
627669 Args:
628- event: The typed event to send (constrained by the consumer's Event type)
629- group_name: Group name to send to (required)
630-
631- Example:
632- ```python
633- # From a Django task or view:
634- await ChatDetailConsumer.send_channel_event(
635- MemberAddedEvent(
636- type="member_added",
637- payload={"member_id": 123, "email": "user@example.com"}
638- ),
639- groups=["chat_room_1"],
640- from_user_pk=request.user.pk
641- )
642- ```
670+ group_name: Group name to send the event to
671+ event: The typed event to send (must match the consumer's Event type)
643672 """
644673 channel_layer = get_channel_layer ()
645674 assert channel_layer is not None
@@ -660,40 +689,32 @@ def send_channel_event(
660689 event : Event ,
661690 ) -> None :
662691 """
663- Synchronous version of send_channel_event for use in Django tasks/views.
692+ Synchronous version of asend_channel_event for use in Django tasks/views.
664693
665- This method provides the same functionality as send_channel_event but
694+ This method provides the same functionality as asend_channel_event but
666695 can be called from synchronous code like Django tasks, views, or signals.
667696
668697 Args:
669- group_name: Group name to send to (required)
698+ group_name: Group name to send to
670699 event: The typed event to send (constrained by the consumer's Event type)
671-
672- Example:
673- ```python
674- # From a Django task:
675- ChatDetailConsumer.send_channel_event_sync(
676- "chat_room_1",
677- MemberAddedEvent(
678- type="member_added",
679- payload={"member_id": 123, "email": "user@example.com"}
680- ),
681- )
682- ```
683700 """
684701 async_to_sync (cls .asend_channel_event )(group_name , event )
685702
686703 async def handle_channel_event (self , event_payload : EventPayload ) -> None :
687704 """
688- Internal dispatcher for channel events with completion signal.
705+ Internal dispatcher for typed channel events with completion signal.
689706
690707 This method is called by the channel layer when an event is sent to a group
691- this consumer belongs to. It extracts the event data, checks exclusion rules,
692- finds the appropriate handler method, and calls it with proper error handling.
708+ this consumer belongs to. It extracts the event data, validates it against
709+ the Event generic parameter, finds the appropriate handler method, and calls
710+ it with proper error handling.
711+
712+ The handler method name is determined by the event's 'handler' field, and it
713+ must be an async method on the consumer class that accepts the event as a parameter.
693714
694715 Args:
695716 event_payload: The message from the channel layer containing event data
696- and metadata about the sender
717+
697718 """
698719 try :
699720 event_data_dict : dict [str , Any ] = event_payload .get ("event_data" , {})
0 commit comments