Description
When using django-ninja's SSE streaming with an async generator under ASGI (granian/uvicorn), Django emits this warning on every SSE connection:
StreamingHttpResponse must consume synchronous iterators in order to serve them
asynchronously. Use an asynchronous iterator instead.
The warning is cosmetic — SSE streaming works correctly via Django's sync-to-async fallback — but it indicates that the async generator is not being detected as async by Django's StreamingHttpResponse.
Root Cause
In ninja/compatibility/streaming.py, the create_streaming_response function (Django 4.2+ path) wraps the async content generator in another async generator (with_lazy_headers) and passes it to StreamingHttpResponse:
async def with_lazy_headers() -> Any:
async for chunk in content_gen:
yield chunk
_copy_temporal_headers(temporal_response, response)
response = StreamingHttpResponse(
with_lazy_headers(),
content_type=content_type,
status=status,
)
Django's StreamingHttpResponse._set_streaming_content then does:
def _set_streaming_content(self, value):
try:
self._iterator = iter(value) # ← succeeds on async generators!
self.is_async = False # ← wrongly set to False
except TypeError:
self._iterator = aiter(value)
self.is_async = True
The issue: in Python 3.12+, iter() on an async generator does not raise TypeError. It returns a (useless) iterator wrapper. So Django's detection logic fails — it classifies the async generator as synchronous.
When Django's ASGI handler later tries to iterate via __aiter__, it catches the TypeError from the sync iterator, logs the warning, and falls back to sync_to_async(list) consumption — which defeats the streaming purpose.
Environment
- django-ninja 1.6
- Django 6.0
- Python 3.14
- granian ASGI server
Reproduction
from ninja import NinjaAPI
from ninja.streaming import SSE
from collections.abc import AsyncIterator
import asyncio
api = NinjaAPI()
class Event:
message: str
@api.get("/stream", response=SSE[Event])
async def stream(request) -> AsyncIterator[Event]:
for i in range(5):
yield Event(message=f"event {i}")
await asyncio.sleep(1)
Run under any ASGI server and connect to /stream. The warning appears in the server logs for every connection.
Suggested Fix
In ninja/compatibility/streaming.py, check for async iterator first before trying iter():
from collections.abc import AsyncIterator
# In create_streaming_response, before passing to StreamingHttpResponse:
if isinstance(content_gen, AsyncIterator):
# Wrap in a class that only exposes __aiter__, not __iter__,
# so Django's _set_streaming_content detects it correctly.
class _AsyncOnly:
def __init__(self, ait):
self._ait = ait
def __aiter__(self):
return self._ait.__aiter__()
response = StreamingHttpResponse(
_AsyncOnly(with_lazy_headers()),
content_type=content_type,
status=status,
)
Alternatively, this could be fixed in Django's _set_streaming_content by checking isinstance(value, AsyncIterator) before iter(). But since django-ninja controls the intermediary, a fix here is more practical.
Description
When using django-ninja's SSE streaming with an async generator under ASGI (granian/uvicorn), Django emits this warning on every SSE connection:
The warning is cosmetic — SSE streaming works correctly via Django's sync-to-async fallback — but it indicates that the async generator is not being detected as async by Django's
StreamingHttpResponse.Root Cause
In
ninja/compatibility/streaming.py, thecreate_streaming_responsefunction (Django 4.2+ path) wraps the async content generator in another async generator (with_lazy_headers) and passes it toStreamingHttpResponse:Django's
StreamingHttpResponse._set_streaming_contentthen does:The issue: in Python 3.12+,
iter()on an async generator does not raiseTypeError. It returns a (useless) iterator wrapper. So Django's detection logic fails — it classifies the async generator as synchronous.When Django's ASGI handler later tries to iterate via
__aiter__, it catches theTypeErrorfrom the sync iterator, logs the warning, and falls back tosync_to_async(list)consumption — which defeats the streaming purpose.Environment
Reproduction
Run under any ASGI server and connect to
/stream. The warning appears in the server logs for every connection.Suggested Fix
In
ninja/compatibility/streaming.py, check for async iterator first before tryingiter():Alternatively, this could be fixed in Django's
_set_streaming_contentby checkingisinstance(value, AsyncIterator)beforeiter(). But since django-ninja controls the intermediary, a fix here is more practical.