Messages System
Chanx provides a robust message system built on Pydantic that enables:
Type-safe message handling
Runtime validation of message structure
Discriminated unions for message type routing
Standardized message formats
Base Classes
The foundation of the message system consists of these base classes:
BaseMessage: Abstract base class for all message types
BaseGroupMessage: Extended messages with group metadata
BaseIncomingMessage: Container for all incoming messages
BaseOutgoingGroupMessage: Container for outgoing group messages
Message Structure
All messages in Chanx follow a standard format:
{
"action": "message_type",
"payload": {
// Message-specific data
}
}
The action field serves as a discriminator that identifies the message type.
Creating Custom Message Types
To create custom message types, define classes that inherit from BaseMessage with a unique action field:
from typing import Literal, Optional
from pydantic import Field
from chanx.messages.base import BaseMessage
class ChatMessage(BaseMessage):
"""Message for chat communication."""
action: Literal["chat"] = "chat"
payload: str
class NotificationMessage(BaseMessage):
"""System notification message."""
action: Literal["notification"] = "notification"
payload: dict[str, str] = Field(default_factory=dict)
Defining Message Schema
For a consumer to handle these message types, you need to create a message container:
from chanx.messages.base import BaseIncomingMessage
from chanx.messages.incoming import PingMessage
class MyIncomingMessage(BaseIncomingMessage):
"""Container for all incoming message types."""
message: PingMessage | ChatMessage | NotificationMessage
Then set this as your consumer’s schema:
class MyConsumer(AsyncJsonWebsocketConsumer):
INCOMING_MESSAGE_SCHEMA = MyIncomingMessage
Message Validation
When a message is received, Chanx automatically:
Validates the message against your schema
Deserializes it into the correct message type
Routes it to your consumer’s
receive_messagemethod
If validation fails, Chanx sends an error message to the client:
{
"action": "error",
"payload": [
{
"type": "missing",
"loc": ["message", "payload"],
"msg": "Field required"
}
]
}
Handling Messages
In your consumer, handle different message types based on the action field:
async def receive_message(self, message, **kwargs):
"""Process a validated received message."""
if message.action == "chat":
# Handle chat message
await self.handle_chat(message.payload)
elif message.action == "notification":
# Handle notification
await self.handle_notification(message.payload)
Sending Messages
To send a message to the connected client:
# Create a message instance
notification = NotificationMessage(payload={"type": "info", "text": "Update received"})
# Send it to the client
await self.send_message(notification)
Group Messages
For group broadcasting, use the group message methods:
# Send to all clients in the group(s)
await self.send_group_message(
ChatMessage(payload="Hello everyone!"),
exclude_current=True # Don't send to the sender
)
Group messages are automatically enhanced with metadata:
{
"action": "chat",
"payload": "Hello everyone!",
"is_mine": false,
"is_current": false
}
is_mine: True if the message originated from the current useris_current: True if the message came from this specific connection
Standard Message Types
Chanx provides several standard message types:
Incoming Messages
PingMessage: Simple ping message to check connection status
Outgoing Messages
PongMessage: Response to ping messagesErrorMessage: Error informationAuthenticationMessage: Authentication statusCompleteMessage: Signals message processing completionGroupCompleteMessage: Signals group message completion
Completion Messages
Chanx can automatically send completion messages after processing client messages:
{
"action": "complete"
}
Control this behavior with the send_completion setting:
class MyConsumer(AsyncJsonWebsocketConsumer):
send_completion = True # Send completion message
Advanced Usage
Custom Message Serialization
For advanced needs, you can customize how messages are serialized:
class MyMessage(BaseMessage):
action: Literal["custom"] = "custom"
payload: dict
# Custom serialization method
def model_dump(self, **kwargs):
data = super().model_dump(**kwargs)
# Modify data before sending
data["extra"] = "metadata"
return data
Group-Specific Message Types
For group messages, inherit from BaseGroupMessage:
from chanx.messages.base import BaseGroupMessage
class GroupChatMessage(BaseGroupMessage):
"""Group chat message with enhanced metadata."""
action: Literal["group_chat"] = "group_chat"
payload: str
# Automatically includes is_mine and is_current
Message Camelization
For frontend compatibility, Chanx supports automatic camelCase conversion of message keys:
# settings.py
CHANX = {
'CAMELIZE': True, # Enable camelCase conversion
}
With this setting enabled, a message like:
{"action": "notification", "payload": {"user_name": "Alice", "message_text": "Hello"}}
Will be automatically converted to:
{"action": "notification", "payload": {"userName": "Alice", "messageText": "Hello"}}
Note: This feature requires the ‘pyhumps’ package. Install it with:
pip install pyhumps
or via the extras:
pip install chanx[camel-case]
Best Practices
Define clear message contracts: Document the purpose and structure of each message type
Keep message types focused: Each message type should have a single purpose
Use strict typing: Take advantage of Pydantic’s validation to catch errors early
Validate payloads: Add validators for complex payloads
Handle validation errors: Provide meaningful error handling for malformed messages
Test message serialization: Write tests for serialization/deserialization
Next Steps
Consumers - Learn about consumer configuration
Chat Application Example - See the message system in a complete example