#33735: Add asynchronous responses for use with an ASGI server --------------------------------+------------------------------------------ Reporter: florianvazelle | Owner: florianvazelle Type: New feature | Status: assigned Component: HTTP handling | Version: 4.0 Severity: Normal | Resolution: Keywords: ASGI async | Triage Stage: Accepted Has patch: 1 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 1 Easy pickings: 0 | UI/UX: 0 --------------------------------+------------------------------------------ Changes (by Carlton Gibson):
* cc: Andrew Godwin, Michael Brown (added) Comment: Hi Florian — Thanks again for this. I **finally** got the time to sit with the PR and that for #32798 to try and think through the right way forward. I'm commenting here, rather than the PR, because I think we need a slightly different approach. Also cc-ing Andrew and Micheal to ask for their input too. So what follows is an RFC... Looking at the [https://github.com/django/django/pull/15727 PR here], and that for #32798 (either the [https://github.com/django/django/pull/14526 complex first suggestion], or the [https://github.com/django/django/pull/14652 simpler but slower alternative]) I think we should consciously opt-out of trying to map between sync and async generators. **Maybe** somebody writes a queue implementation that has sync one end and async at the other, and handles thread-safety, and back-pressure, and who knows what. **Maybe** we could use that in Django if we felt it trust-worthy enough. But I don't think we should try and write one ourselves. Rather, I think we should say that, in the case of streaming responses, users need to adjust their code for whether they're planning to run under WSGI or ASGI. (We will handle the **other** case both ways, but at a performance and memory cost.) To wit: We add `__aiter__` to `StreamingHttpResponse` and adopt `ASGIHandler` to use `async for` in the `if response.streaming` block. Under ASGI you would provide `StreamingHttpResponse` with an async iterator, still yielding bytestrings as content. This should allow the streaming async responses use-case. Under WSGI you'd continue to provide a sync iterator and everything would work as it is already. For each case, `__iter__` and `__aiter__` we handle the **wrong case** by consuming the iterator in a single step (using the `asgiref.sync` utilities, with `thread_sensitive` for `sync_to_async`) and then yield it out in parts as expected. (This is the performance and memory cost.) We log a **warning** (that may be filtered in a logging content if not desired) saying that folks should use an appropriate generator type in this case. Note that this is similar to the `__aiter__` implementation on QuerySet, which [https://github.com/django/django/blob/f30c7e381c94f41d361877d8a3e90f8cfb391709/django/db/models/query.py#L397-L405 fetches all the records once inside a single sync_to_async()] to avoid multiple (costly) context switches there: {{{ def __aiter__(self): # Remember, __aiter__ itself is synchronous, it's the thing it returns # that is async! async def generator(): await sync_to_async(self._fetch_all)() for item in self._result_cache: yield item return generator() }}} I think this should be both good enough and maintainable. In the happy case, where you provide the right kind of iterator for your chosen protocol (ASGI/WSGI) you get the result you want, without us need anything overly complex. In the deviant case it'll work (assuming you're not trying to stream ''Too much™''). It means we're not branching in the handlers to handle each of the different possible iterator types. The abstraction leaks a little bit, but I kind of think that's reasonable at this point. Maybe: If it proves impossible to get the WSGI+async/ASGI+sync fallback to work correctly, just erroring at that point would maybe be acceptable. 🤔 (That you could provide an async iterator to ASGIHandler would allow long- lived responses, that aren't currently feasible.) Thoughts? Thanks! **Assuming** all that makes sense, would you (or Michael maybe) want to take this on? If not I can pick it up, since it would be good to hit Django 4.2 (feature freeze Jan 23) for this. 🎯 -- Ticket URL: <https://code.djangoproject.com/ticket/33735#comment:8> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/01070183c74d3e48-26527c6d-aa7c-4f96-9f8d-e0bc79970311-000000%40eu-central-1.amazonses.com.