-
-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support ASGI on Django 4.2+ #148
Conversation
After pushing this first draft, I’m noticing that this won’t work anymore with WSGI because Also, mypy is not happy with what I did, but I’m not sure why that is (especially the And finally, I should probably add some async tests. |
Ok, so I read https://code.djangoproject.com/ticket/33735#comment:8 after my first comment. So we would definitively need to provide both sync and async generator and figure out a way to know in which case we are. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take a look at the docs for StreamingHTTPResponse in Django 4.2. There is says that the iterator needs to match whether you're running under WSGI or ASGI.
We can't just make this an async function and iterator, because most folks at still running under WSGI, and it just won't work for earlier Django versions.
I think we're likely going to need two views, and to tell folks to route the appropriate one depending on how they're running Django. (I'm not sure we can detect that? 🤔)
Maybe there's a nicer solution... 🤔 -- but I'd get that running first.
We can detect ASGI with |
That might work likely, yes. 🤔 The view doesn't necessarily have to be async itself. Just the iterator passed to the response. |
Thanks. My first hope is to be able to detect if it's an ASGI or WSGI context from the view. If this doesn't work, then 2 views, and have the Middleware (that should be aware?) insert the proper url? |
4bfdee4
to
c3a178d
Compare
Ok, so I started from scratch and went for something simpler. Also, I kind of randomly picked |
Something that we will probably want to look at as well: I get the following messages when testing it: |
I looked a bit more today and tracked the issue to the fact that we are going through https://github.com/django/django/blob/6bdc3c58b65eb32fd63cd41849f00a17a36b4473/django/test/client.py#L110 I’ll actually look at bit more in how the async tests are written in the framework, because it feels weird that we can just do
with an async generator and expect this to work 🤔 |
I’m now convinced there is a bug in how django’s async_client behave. |
The bug is now fixed in the beta version of 4.2, so I think except the decision that needs to be taken for how to tackle the error for async and Django < 4.2, this is now ready for a proper review. |
Hey, With Django 4.2 coming up officially in a couple weeks, it would be great to have this merged. Is there anything else I can do to get this moving? |
If this works, can we get it merged. I would love to use tailwind but cant since it keep crashing. |
e9e08fc
to
da67324
Compare
I took some time to cleanup my branch and sync it with All checks are now passing (https://github.com/Alexerson/django-browser-reload/pull/1/checks), but it seems the pre-commit hooks are still failing… I’m opening typeddjango/django-stubs#1483 to have it fixed upstream. |
Now that typeddjango/django-stubs#1484 is merged, I ran I think it’s just a matter of doing: -event = next(response.streaming_content)
+response_iterable = iter(response)
+event = next(response_iterable) in a few places. I feel this should be a different PR though (probably the one in which you’ll move to Edit: The whole changes would be something like this (obviously, the version is not released yet, so this would fail): Alexerson@c58f57f |
Thank you for keeping this updated all this time! I am looking into it now. |
Of course! This forced me to finally commit something in |
src/django_browser_reload/views.py
Outdated
if DJANGO_VERSION < (4, 2): | ||
err_msg = "We cannot support hot reload with ASGI for Django < 4.2" | ||
warnings.warn(err_msg, stacklevel=2) | ||
return HttpResponse(status=HTTPStatus.NOT_ACCEPTABLE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the JavaScript should handle this and log a message as the connection fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working on these changes.
src/django_browser_reload/views.py
Outdated
response = StreamingHttpResponse( | ||
event_stream(), | ||
content_type="text/event-stream", | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be extracted to common branch by using event_stream
as the name for the async generator too
src/django_browser_reload/views.py
Outdated
err_msg = "We cannot support hot reload with ASGI for Django < 4.2" | ||
warnings.warn(err_msg, stacklevel=2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure a Python warning is helpful, leaving a message in the browser console seems more logical
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some playing around, leaving a browser console message would be unnecessarily complicated. I'm only going to put it in the HTTP response content, which users can debug if necessary. The ASGI support is documented as Django 4.2+ so hopefully no one will be surprised when it doesn't work on older versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, especially because this was not working at all up until now (and was actually hanging the browser), so it’s not breaking any existing support, and anyone wanting to use it will (hopefully) read the README and/or changelog.
Run the sync WSGI server with: | ||
|
||
.. code-block:: sh | ||
|
||
DEBUG=1 python manage.py runserver --noasgi | ||
|
||
Run the async ASGI server with: | ||
|
||
.. code-block:: sh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding ASGI to the example project was key to testing this... did you not see the example project before? You tested only in your own project?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly, it was just easier for me to simply add this to my own project than to run the example project. But as this was a couple months ago, my memory might fail me and it’s also very well possible that I just didn’t see it.
tests/test_views.py
Outdated
if sys.version_info >= (3, 10): | ||
event = await anext(aiter(response)) | ||
else: | ||
event = await response.__aiter__().__anext__() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
repeating this pattern is a bit verbose... adding backports of anext
/ aiter
await asyncio.sleep(PING_DELAY) | ||
yield message("ping", versionId=version_id) | ||
|
||
if should_reload_event.is_set(): | ||
should_reload_event.clear() | ||
yield message("reload") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that this is not using an asyncio event, but always sleeping a second between checks. That's imposing an average of 0.5 seconds delay on reloads, which is small but noticeable. Fixing in #178.
#87