django_sockets
Django Sockets
Simplified Django WebSocket integrations designed for speed, flexibility, and cloud-cache scaling (Valkey/Redis). Works seamlessly on single, distributed, or serverless cache setups.
- ASGI Server Compatibility: Compatible with any standard ASGI server (such as Uvicorn, Daphne, or Hypercorn).
- Multi-Framework: Can also be used in non-Django applications (Flask, FastAPI, or raw Python) for lightweight Pub/Sub messaging.
Key Features
- Cache-Backed Pub/Sub: Async broadcasting using Redis or Valkey across standalone, sentinel, or cluster (
RedisCluster) setups. - Async & Sync Handlers: Implement
connect,receive, anddisconnecthooks as either synchronous methods (def) or asynchronous coroutines (async def). - Multi-Channel Broadcasting: Broadcast to single channels or batches of channels with Redis pipelining.
- Flexible Auth Middleware: Simple authentication wrappers for Django Sessions and DRF Tokens supporting query string (
?token=...) and subprotocol (Sec-WebSocket-Protocol: Token.<token>) token passing. - ASGI Native: Implements standard
ProtocolTypeRouterandURLRouter - Minimal Boilerplate: Define a class with
connect,receive, anddisconnecthooks and you're ready to go.
Installation & Setup
pip install django_sockets
Valkey/Redis Setup
To use broadcasting and pub/sub features, you need a Redis or Valkey cache server:
# Start a local Valkey cache via Docker
docker run -d -p 6379:6379 --name django_sockets_cache valkey/valkey:7
Quickstart (Django)
1. Define your Socket Server
Create a ws.py in your Django app:
from django.urls import path
from django_sockets.sockets import BaseSocketServer
from django_sockets.middleware import SessionAuthMiddleware
from django_sockets.utils import URLRouter
class MyCounterSocket(BaseSocketServer):
def configure(self):
# Configure cache hosts (optional, needed for pub/sub)
self.hosts = [{"address": "redis://localhost:6379"}]
def connect(self):
# Scope-aware user extraction (supports sync 'def' or async 'async def')
self.channel_id = f"user_{self.scope['user'].id}"
self.subscribe(self.channel_id)
def receive(self, data):
# Broadcast incoming JSON to all subscribers of this channel
# (Supports sync 'def receive' or async 'async def receive')
self.broadcast(self.channel_id, data)
# Wrap with authentication middleware and URL routing
websocket_application = SessionAuthMiddleware(
URLRouter(
[
path("ws/counter/", MyCounterSocket.as_asgi),
]
)
)
2. Configure ASGI Entrypoint
In your Django asgi.py (ensure imports are ordered correctly to allow proper Django initialization):
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings")
django_asgi_app = get_asgi_application()
# Import django_sockets after Django initialization
from django_sockets.utils import ProtocolTypeRouter
from .ws import websocket_application
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": websocket_application,
}
)
Running the ASGI Server
You can run your Django ASGI application using any ASGI-compliant web server:
Uvicorn
pip install uvicorn
uvicorn myapp.asgi:application --reload
Daphne
pip install daphne
daphne -p 8000 myapp.asgi:application
Hypercorn
pip install hypercorn
hypercorn myapp.asgi:application --bind 127.0.0.1:8000
Guides & Examples
We provide detailed step-by-step tutorials and code samples:
- Step-by-Step Django Tutorial (TUTORIAL.md): Build a fully-featured, user-scoped real-time counter using session or DRF token authentication from scratch.
- Examples Directory:
examples/django/myapp: Full project showing standard Django Session authentication.examples/django/myapp_drf: Full project showing DRF Token authentication.examples/without_django: Standalone python pub/sub without Django dependencies.
- Performance Benchmarks (benchmark.md): Detailed benchmark profiles covering pub/sub latency, burst throughput, payload scaling, fan-out scalability, and authentication middleware.
Non-Django Usage (Flask, FastAPI, Raw Python)
django_sockets can run without Django's registry:
1. Broadcaster (Sending from Flask/FastAPI)
Publish events from any HTTP route to WebSocket clients across single or multiple channels:
from flask import Flask, request
from django_sockets.broadcaster import Broadcaster
app = Flask(__name__)
broadcaster = Broadcaster(hosts=[{"address": "redis://localhost:6379"}])
@app.route("/alert", methods=["POST"])
def send_alert():
# Broadcast to a single channel:
broadcaster.broadcast("alerts_channel", request.json)
# Or broadcast to multiple channels simultaneously with Redis pipelining:
# broadcaster.broadcast(["alerts_channel", "audit_log"], request.json)
# broadcaster.broadcast_many(["chan1", "chan2"], request.json)
return {"status": "Alert sent"}
2. Running a Pure ASGI Server
Initialize BaseSocketServer manually in custom ASGI configurations or raw Python scripts:
import asyncio
from django_sockets.sockets import BaseSocketServer
async def my_send_handler(data):
print("Sent:", data)
receive_queue = asyncio.Queue()
socket_server = BaseSocketServer(
scope={},
receive=receive_queue.get,
send=my_send_handler,
hosts=[{"address": "redis://localhost:6379"}],
)
socket_server.start_listeners()
Development & Testing
Run the full pytest suite:
uv run pytest
Run the performance benchmark suite:
uv run python utils/benchmark.py
For manual testing, manage the local Docker Valkey instance using:
uv run python utils/redis_start.py
# Run your manual scripts (e.g. uv run test/06_django_integration.py)
uv run python utils/redis_stop.py
Attributions
Some of the code in this repository is formed similarly to or inspired by channels_redis and django_channels. Many thanks to their authors for the original work and inspiration.
1""" 2# Django Sockets 3 4[](https://badge.fury.io/py/django_sockets) 5[](https://opensource.org/licenses/MIT) 6 7Simplified Django WebSocket integrations designed for speed, flexibility, and cloud-cache scaling (Valkey/Redis). Works seamlessly on single, distributed, or serverless cache setups. 8 9- **ASGI Server Compatibility**: Compatible with **any standard ASGI server** (such as Uvicorn, Daphne, or Hypercorn). 10- **Multi-Framework**: Can also be used in **non-Django applications** (Flask, FastAPI, or raw Python) for lightweight Pub/Sub messaging. 11 12--- 13 14## Key Features 15 16- **Cache-Backed Pub/Sub**: Async broadcasting using Redis or Valkey across standalone, sentinel, or cluster (`RedisCluster`) setups. 17- **Async & Sync Handlers**: Implement `connect`, `receive`, and `disconnect` hooks as either synchronous methods (`def`) or asynchronous coroutines (`async def`). 18- **Multi-Channel Broadcasting**: Broadcast to single channels or batches of channels with Redis pipelining. 19- **Flexible Auth Middleware**: Simple authentication wrappers for Django Sessions and DRF Tokens supporting query string (`?token=...`) and subprotocol (`Sec-WebSocket-Protocol: Token.<token>`) token passing. 20- **ASGI Native**: Implements standard `ProtocolTypeRouter` and `URLRouter` 21- **Minimal Boilerplate**: Define a class with `connect`, `receive`, and `disconnect` hooks and you're ready to go. 22 23--- 24 25## Installation & Setup 26 27```bash 28pip install django_sockets 29``` 30 31### Valkey/Redis Setup 32To use broadcasting and pub/sub features, you need a Redis or Valkey cache server: 33```bash 34# Start a local Valkey cache via Docker 35docker run -d -p 6379:6379 --name django_sockets_cache valkey/valkey:7 36``` 37 38--- 39 40## Quickstart (Django) 41 42### 1. Define your Socket Server 43Create a `ws.py` in your Django app: 44 45```python 46from django.urls import path 47from django_sockets.sockets import BaseSocketServer 48from django_sockets.middleware import SessionAuthMiddleware 49from django_sockets.utils import URLRouter 50 51 52class MyCounterSocket(BaseSocketServer): 53 def configure(self): 54 # Configure cache hosts (optional, needed for pub/sub) 55 self.hosts = [{"address": "redis://localhost:6379"}] 56 57 def connect(self): 58 # Scope-aware user extraction (supports sync 'def' or async 'async def') 59 self.channel_id = f"user_{self.scope['user'].id}" 60 self.subscribe(self.channel_id) 61 62 def receive(self, data): 63 # Broadcast incoming JSON to all subscribers of this channel 64 # (Supports sync 'def receive' or async 'async def receive') 65 self.broadcast(self.channel_id, data) 66 67 68# Wrap with authentication middleware and URL routing 69websocket_application = SessionAuthMiddleware( 70 URLRouter( 71 [ 72 path("ws/counter/", MyCounterSocket.as_asgi), 73 ] 74 ) 75) 76``` 77 78### 2. Configure ASGI Entrypoint 79In your Django `asgi.py` (ensure imports are ordered correctly to allow proper Django initialization): 80 81```python 82import os 83from django.core.asgi import get_asgi_application 84 85os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings") 86django_asgi_app = get_asgi_application() 87 88# Import django_sockets after Django initialization 89from django_sockets.utils import ProtocolTypeRouter 90from .ws import websocket_application 91 92application = ProtocolTypeRouter( 93 { 94 "http": django_asgi_app, 95 "websocket": websocket_application, 96 } 97) 98``` 99 100--- 101 102## Running the ASGI Server 103 104You can run your Django ASGI application using any ASGI-compliant web server: 105 106### Uvicorn 107```bash 108pip install uvicorn 109uvicorn myapp.asgi:application --reload 110``` 111 112### Daphne 113```bash 114pip install daphne 115daphne -p 8000 myapp.asgi:application 116``` 117 118### Hypercorn 119```bash 120pip install hypercorn 121hypercorn myapp.asgi:application --bind 127.0.0.1:8000 122``` 123 124--- 125 126## Guides & Examples 127 128We provide detailed step-by-step tutorials and code samples: 129 130- **[Step-by-Step Django Tutorial (TUTORIAL.md)](TUTORIAL.md)**: Build a fully-featured, user-scoped real-time counter using session or DRF token authentication from scratch. 131- **[Examples Directory](examples/)**: 132 - `examples/django/myapp`: Full project showing standard Django Session authentication. 133 - `examples/django/myapp_drf`: Full project showing DRF Token authentication. 134 - `examples/without_django`: Standalone python pub/sub without Django dependencies. 135- **[Performance Benchmarks (benchmark.md)](benchmark.md)**: Detailed benchmark profiles covering pub/sub latency, burst throughput, payload scaling, fan-out scalability, and authentication middleware. 136 137--- 138 139## Non-Django Usage (Flask, FastAPI, Raw Python) 140 141`django_sockets` can run without Django's registry: 142 143### 1. Broadcaster (Sending from Flask/FastAPI) 144Publish events from any HTTP route to WebSocket clients across single or multiple channels: 145```python 146from flask import Flask, request 147from django_sockets.broadcaster import Broadcaster 148 149app = Flask(__name__) 150broadcaster = Broadcaster(hosts=[{"address": "redis://localhost:6379"}]) 151 152 153@app.route("/alert", methods=["POST"]) 154def send_alert(): 155 # Broadcast to a single channel: 156 broadcaster.broadcast("alerts_channel", request.json) 157 158 # Or broadcast to multiple channels simultaneously with Redis pipelining: 159 # broadcaster.broadcast(["alerts_channel", "audit_log"], request.json) 160 # broadcaster.broadcast_many(["chan1", "chan2"], request.json) 161 162 return {"status": "Alert sent"} 163``` 164 165### 2. Running a Pure ASGI Server 166Initialize `BaseSocketServer` manually in custom ASGI configurations or raw Python scripts: 167```python 168import asyncio 169from django_sockets.sockets import BaseSocketServer 170 171 172async def my_send_handler(data): 173 print("Sent:", data) 174 175 176receive_queue = asyncio.Queue() 177socket_server = BaseSocketServer( 178 scope={}, 179 receive=receive_queue.get, 180 send=my_send_handler, 181 hosts=[{"address": "redis://localhost:6379"}], 182) 183socket_server.start_listeners() 184``` 185 186--- 187 188## Development & Testing 189 190Run the full pytest suite: 191```bash 192uv run pytest 193``` 194 195Run the performance benchmark suite: 196```bash 197uv run python utils/benchmark.py 198``` 199 200For manual testing, manage the local Docker Valkey instance using: 201```bash 202uv run python utils/redis_start.py 203# Run your manual scripts (e.g. uv run test/06_django_integration.py) 204uv run python utils/redis_stop.py 205``` 206 207--- 208 209## Attributions 210 211Some of the code in this repository is formed similarly to or inspired by `channels_redis` and `django_channels`. Many thanks to their authors for the original work and inspiration. 212"""