django_sockets

Django Sockets

PyPI version License: MIT

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, and disconnect hooks 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 ProtocolTypeRouter and URLRouter
  • Minimal Boilerplate: Define a class with connect, receive, and disconnect hooks 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[![PyPI version](https://badge.fury.io/py/django_sockets.svg)](https://badge.fury.io/py/django_sockets)
  5[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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"""