Skip to content

Commit b4f1cc4

Browse files
committed
docs(docstring): update websocket and messages base docstring related to events handling and generic type
1 parent 299e8a2 commit b4f1cc4

File tree

3 files changed

+89
-56
lines changed

3 files changed

+89
-56
lines changed

chanx/generic/websocket.py

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,22 @@
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

2533
import asyncio
@@ -92,25 +100,40 @@
92100

93101

94102
class 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

98113
class 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", {})

chanx/messages/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,17 @@ class BaseGroupMessage(BaseMessage, abc.ABC):
9292

9393

9494
class BaseChannelEvent(BaseModel, abc.ABC):
95+
"""
96+
Base class for typed channel events.
97+
98+
Channel events provide a way to send typed messages through the channel layer
99+
to specific consumer methods. Each event type must define a unique 'handler'
100+
field that corresponds to a method name on the target consumer.
101+
102+
Attributes:
103+
handler: Method name on the consumer that will handle this event
104+
payload: Event-specific data payload
105+
"""
106+
95107
handler: Any
96108
payload: Any

docs/_static/interrogate_badge.svg

Lines changed: 3 additions & 3 deletions
Loading

0 commit comments

Comments
 (0)