Skip to main content

Dead-simple Server-Sent Events for Django via a single decorator.

Project description

django-flosse ๐ŸŸ

PyPI version Python versions Django versions License Tests Coverage

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 def generator views (ASGI)
  • Class-based views โ€” SSEView with 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_flosse-0.1.2.tar.gz (14.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_flosse-0.1.2-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

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

Hashes for django_flosse-0.1.2.tar.gz
Algorithm Hash digest
SHA256 0a437d69e2c552cae2113caa91e5493da91298131a9ae0ae6bcc506226bb3ba3
MD5 c29f6d8cc4302b9ffab4f45787456776
BLAKE2b-256 48dae15a29c06664db275d13658f541a1a16b8f311124a18e8cf2b74de8c8ccd

See more details on using hashes here.

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

Hashes for django_flosse-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 abb7394284360378c3266a84cf682824dc9face9dadea2c37fc96f926136b26a
MD5 fade6ee8b2626558892dcd62abe0f0b6
BLAKE2b-256 93759471e45ab6ea4b6197843efa441a732fd6fc74237a1b3401124b4d788b2b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page