Async Generator Support
django-flosse natively supports asynchronous generators out of the box. Starting from version 0.2.0, you can write SSE streams using async def and async for without any additional configuration or dependencies.
Why go async?
Async generators shine when your data source is I/O bound (DB queries, external APIs, message brokers) or event-driven (Redis pub/sub, Django signals). They keep the event loop unblocked and scale efficiently under high concurrency.
Quick Start
Simply decorate an async generator with @sse_stream. The decorator automatically detects async generators and routes them through Django's async response pipeline.
from django_flosse import sse_stream
from django_flosse.events import SSEEvent
@sse_stream
async def live_metrics(request):
async for metric in fetch_async_metrics():
yield SSEEvent(event="metric", data=metric)
No config required
The same decorator works for both def and async def. All yield formats (str, tuple, dict, SSEEvent) behave identically.
Common Patterns
1. Non-Blocking Polling
import asyncio
from django_flosse import sse_stream
@sse_stream
async def dashboard_feed(request):
while True:
data = await fetch_latest_stats()
yield ("update", data)
await asyncio.sleep(2) # ✅ Non-blocking wait
Avoid time.sleep()
Using synchronous time.sleep() inside an async generator will block the entire event loop. Always use asyncio.sleep().
2. Redis Pub/Sub Streaming
import redis.asyncio as redis
from django_flosse import sse_stream
from django_flosse.events import SSEEvent
r = redis.Redis.from_url("redis://localhost:6379/0")
@sse_stream
async def redis_channel(request):
async with r.pubsub() as pubsub:
await pubsub.subscribe("live-feed")
async for message in pubsub.listen():
if message["type"] == "message":
yield SSEEvent(event="update", data=message["data"])
3. Graceful Cancellation & Cleanup
When a client disconnects, Django cancels the async generator. Use try/except asyncio.CancelledError to release resources cleanly.
@sse_stream
async def managed_stream(request):
resource = await acquire_async_resource()
try:
async for data in resource.stream():
yield ("data", data)
except asyncio.CancelledError:
await resource.release()
# ✅ No need to re-raise. Django handles cancellation silently.
Deployment Requirements
✅ Use an ASGI Server
Async generators require an ASGI-compatible server to run efficiently:
- Recommended: uvicorn, daphne, hypercorn
- ⚠️ Fallback: WSGI servers (Gunicorn, uWSGI) will run async code in a thread pool, negating most performance benefits.
Example asgi.py:
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = get_asgi_application()
Run with Uvicorn:
🔧 Reverse Proxy Configuration
Disable buffering to prevent connection timeouts:
location /stream/ {
proxy_pass http://backend;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
}
Cloudflare / CDN users
SSE connections may be terminated by caching proxies. Add a Cache-Control: no-cache header (handled by django-flosse automatically) and ensure your CDN respects streaming responses.
Troubleshooting
| Issue | Solution |
|---|---|
RuntimeError: Event loop is closed |
Ensure you're running on an ASGI server, not WSGI |
| Stream stops unexpectedly | Check for unhandled exceptions; wrap yields in try/except |
| High memory usage | Avoid accumulating payloads in memory; yield incrementally |
CancelledError spam in logs |
Normal on disconnect. Handle gracefully or let Django suppress it |
| Async view runs slowly | Verify you're not using synchronous blocking calls (requests, time.sleep, etc.) |