Dead-simple Server-Sent Events for Django via a single decorator.
Project description
django-flosse ๐
Dead-simple Server-Sent Events for Django via a single decorator.
from django_flosse import sse_stream
@sse_stream
def live_feed(request):
for item in my_data_source():
yield ("update", {"value": item})
๐ฆ Installation
pip install django-flosse
No changes to INSTALLED_APPS are required.
โก Quick Start
1. Write a generator view
# myapp/views.py
import time
from django_flosse import sse_stream, SSEEvent
from django_flosse.permissions import IsAuthenticated
@sse_stream(permission_classes=[IsAuthenticated])
def progress(request):
for i in range(1, 11):
yield ("progress", {"step": i, "total": 10, "pct": i * 10})
time.sleep(1)
yield SSEEvent(data="All done!", event="complete")
2. Wire up the URL
# myapp/urls.py
from django.urls import path
from .views import progress
urlpatterns = [
path("sse/progress/", progress),
]
3. Listen in the browser
const source = new EventSource("/sse/progress/");
source.addEventListener("progress", (e) => {
const { step, total, pct } = JSON.parse(e.data);
console.log(`Step ${step}/${total} โ ${pct}%`);
});
source.addEventListener("complete", (e) => {
console.log(e.data);
source.close();
});
๐จ Yield Styles
You can yield in whichever style feels most natural.
What you yield |
Result |
|---|---|
"hello" |
Unnamed data event |
("update", {"count": 1}) |
Named event update with JSON data |
("update", {"count": 1}, "evt-42") |
Named event + ID |
{"data": "hi", "event": "greet"} |
Dict mapped to SSEEvent fields |
{"foo": "bar"} (no "data" key) |
Whole dict serialised as JSON data |
SSEEvent(data="x", event="y", id="1") |
Full control |
Dict data and non-string values are serialised to JSON automatically.
โ๏ธ @sse_stream Options
The decorator can be used with or without parentheses:
@sse_stream # no arguments
@sse_stream() # explicit empty call
@sse_stream(retry=3000) # with options
@sse_stream(permission_classes=[IsAuthenticated], retry=3000)
| Parameter | Default | Description |
|---|---|---|
retry |
None |
Browser reconnect delay in ms |
permission_classes |
[] |
Permission classes โ all must pass or returns 403 |
Automatic HTTP headers
The decorator sets these on every response automatically:
| Header | Value |
|---|---|
Content-Type |
text/event-stream; charset=utf-8 |
Cache-Control |
no-cache, no-transform |
X-Accel-Buffering |
no (disables Nginx buffering) |
๐ Permissions
from django_flosse.permissions import BaseSSEPermission
class HasAPIKey(BaseSSEPermission):
def has_permission(self, request) -> bool:
return request.headers.get("X-API-Key") == "secret"
@sse_stream(permission_classes=[HasAPIKey])
def secure_stream(request):
yield "top secret data"
Built-in permissions
| Class | Behaviour |
|---|---|
AllowAny |
Always permits (default when list is empty) |
IsAuthenticated |
Requires request.user.is_authenticated |
IsAdminUser |
Requires request.user.is_staff |
Multiple classes are AND-ed together โ every one must pass.
๐ SSEEvent Reference
from django_flosse import SSEEvent
SSEEvent(
data = {"anything": "json-serialisable"}, # required
event = "my-event", # optional named event type
id = "evt-001", # optional event ID
retry = 5000, # optional reconnect delay (ms)
)
๐ Heartbeats
django-flosse does not manage heartbeats automatically โ and that is intentional.
The decorator iterates your generator directly in the WSGI worker with zero extra threads. If you are behind a proxy (Nginx, AWS ELB, Cloudflare) that closes idle connections, send a keep-alive ping yourself:
import time
from django_flosse import sse_stream, SSEEvent
@sse_stream
def live_feed(request):
while True:
data = get_new_data()
if data:
yield ("update", data)
else:
yield SSEEvent(data="", event="ping") # keeps the proxy alive
time.sleep(5)
Why not automatic? If your stream yields events frequently, a heartbeat is unnecessary noise. If your generator blocks for a long time between events, the right solution is an async setup โ async support is on the roadmap.
๐ ๏ธ How It Works
HTTP request arrives
โ
@sse_stream
โโโ Permission checks (โ 403 if denied)
โโโ StreamingHttpResponse
โ
iterates your generator directly
โ
for item in gen:
โโโ str โ data: <item>\n\n
โโโ tuple โ event: ...\ndata: ...\n\n
โโโ dict โ mapped to SSEEvent fields
โโโ SSEEvent โ encoded as-is
No threads. No queues. No background processes.
๐ Deployment
WSGI (Gunicorn, uWSGI)
Each SSE connection holds one worker for its duration. Use --worker-class=gthread
and tune --threads to serve multiple streams per worker:
gunicorn myproject.wsgi:application --worker-class=gthread --threads 4 --workers 2
Note: ASGI / async support is planned for a future release. For high-concurrency deployments, stay tuned for the async version.
Nginx
X-Accel-Buffering: no is set automatically โ no extra config needed. For long-lived
connections behind a proxy, increase the read timeout:
proxy_read_timeout 3600;
proxy_buffering off;
๐ง Compatibility
| django-flosse | Django | Python |
|---|---|---|
| 0.1.x | 3.2 โ 6.0 | 3.9 โ 3.14 |
๐บ๏ธ Roadmap
- Async support โ native
async defgenerator views (ASGI) - Class-based views โ
SSEViewwith sync + async support - DRF compatibility โ
authentication_classes,permission_classes, throttling - Optional Redis channel โ fan-out broadcasting for multi-client scenarios
๐ค Contributing
Contributions are welcome! Whether it's a bug fix, a new feature, or just improving the docs โ feel free to open an issue or submit a pull request.
git clone https://github.com/youssufshakweh/django-flosse
cd django-flosse
pip install -e ".[test]"
pytest
If you have an idea but are not sure where to start, open an issue and let's discuss it first.
๐ Changelog
See CHANGELOG.md for version history.
๐ License
This project is licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_flosse-0.1.2.tar.gz.
File metadata
- Download URL: django_flosse-0.1.2.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a437d69e2c552cae2113caa91e5493da91298131a9ae0ae6bcc506226bb3ba3
|
|
| MD5 |
c29f6d8cc4302b9ffab4f45787456776
|
|
| BLAKE2b-256 |
48dae15a29c06664db275d13658f541a1a16b8f311124a18e8cf2b74de8c8ccd
|
File details
Details for the file django_flosse-0.1.2-py3-none-any.whl.
File metadata
- Download URL: django_flosse-0.1.2-py3-none-any.whl
- Upload date:
- Size: 10.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abb7394284360378c3266a84cf682824dc9face9dadea2c37fc96f926136b26a
|
|
| MD5 |
fade6ee8b2626558892dcd62abe0f0b6
|
|
| BLAKE2b-256 |
93759471e45ab6ea4b6197843efa441a732fd6fc74237a1b3401124b4d788b2b
|