Adding ASGI support to runserver

This thread is continuing conversations started here.

I have a little asgi runserver django app that adds asgi support to runserver.

I also have a pr to django to add get_response_async to make sure static files work with the ASGIStaticFilesHandler

These are 4 options I have thought of so far for a path to asgi support with runserver:

  1. Update runserver in django in a way that keeps the daphne import optional.
  2. Add daphne as a dependency to django and update runserver.
  3. Maintain a separate django app that holds the command that integrates daphne with runserver. Reference the app in asgi section of the django documentation.
  4. Add the commands to the daphne repository, perhaps allowing us to add daphne as an installed app so the command is discovered, and reference this in the asgi section of the django documentation.

Right now I have some code as option (3). I feel like option (4) could make sense as well. This code is specifically tied to daphne. Since daphne is already a project under the django organization, maybe it’s ok to put a command in that package and add as a django app. It’s one fewer separate dependency to install. (1) and (2) would probably involve lots of discussion.

If you’d like to help on this task, you could make a pr to asgi-runserver app to help refactor code paths between asgi/wsgi, unless I get to it first :slight_smile: . Alternatively, you could try out option (4) against daphe and see what it’s like.

1 Like

I think the right path for Django in general is option (1), honestly, which is to have the runserver command be ready to do this (behind an --async flag), but the import is delayed until you ask for it, and then it tries, with a nice warning message if it fails.

I will add to keep daphene as an optional dependency. I may prefer to run under uvicorn on production and don’t want daphene around there.

ticket and pr created. Let me know if you have any feedback!

Grrr. I’m not immediately sure that bundling the dependency on Daphne is the way to go.

Yes, if runserver just worked would be good, but the draft PR is quite big, and doesn’t allow for other ASGI servers at all.

What’s the alternative? Install channels and add to INSTALLED_APPS. This is Option 3. This would allow an uvicorn-runserver option and so on. It’s not as neat but it might be a lot cleaner. It’d be more flexible too. :grimacing:

Issue: https://code.djangoproject.com/ticket/31626
Draft PR: https://github.com/django/django/pull/12969/files

Grrr. I’m not immediately sure that bundling the dependency on Daphne is the way to go.

I don’t love it either

Yes, if runserver just worked would be good, but the draft PR is quite big, and doesn’t allow for other ASGI servers at all.

The pr begins to address this with a run_asgi method that could be implemented by an alternative package, while the run_wsgi path would stay the same, with as much of the code paths shared. It’s not perfect right now. For example, it would take some work to use uvicorn with its built in hot reloading instead of the django hot reloading.

What’s the alternative? Install channels and add to INSTALLED_APPS . This is Option 3. This would allow an uvicorn-runserver option and so on. It’s not as neat but it might be a lot cleaner. It’d be more flexible too. :grimacing:

It’s funny I never even considered installing channels to solve this problem. I’m not sure if that’s a good or bad thing. This may be naivety, but a suggestion to install channels to in order to do local development blurs the line for me on what native asgi support in core django means. I look at channels as a solution for distributed messaging (eg, websockets specifically). In the case of just wanting to run async python on an http server, where does channels fall into the ecosystem?

To me, django is opinionated and just works. At a high level, I had no issues running uvicorn myproject.asgi:application for local development, but then I realized that I wasn’t getting hot reloading. That’s easy enough to fix, and then I realized my migrations were out of date, also easy. Then I noticed I wasn’t seeing system checks. These are all small things but they normally just work and they don’t for asgi. It was enough for me to think “maybe I could make this better for the next person who tries to run django with asgi”.

1 Like

Yes. And these are all good points. But ASGI support is new and immature. It’s developing rapidly, but it’s not clear that we have to bundle everything into Django itself at the first pass. Relying on twisted (at this stage) is a big ask, I think.

What are we expecting? That most folks will keep using WSGI, and add maybe a sprinkling of async def views. Then that a % will want to run under ASGI. For those, at this stage, I’m not sure a “pip install and then use runserver” is too much to ask.

(Note “not sure”.)

What are we expecting? That most folks will keep using WSGI, and add maybe a sprinkling of async def views. Then that a % will want to run under ASGI. For those, at this stage, I’m not sure a “pip install and then use runserver” is too much to ask.

  1. That’s a great question. For non greenfield projects, do we think that some people will sidecar their asgi application alongside wsgi due to compatibility/performance reasons? Or will it be all wsgi or all asgi?

  2. Despite me making the pr, I agree with you. I don’t think it’s too much to ask either which is why i started there. I can see both sides. Maybe @andrewgodwin wants to chime in here. If we do choose the separate installable package route, I think we should add something in the docs. I’ll also defer to y’all as to whether it makes sense to just use channels, as it exists and would work, or if we want a separate package to handle it.

Yes, we should definitely doc something here for 3.1.

What people do in production I think is less of a concern; if people are going to mix async and sync views inside a Django project as we outright recommend you do, we should have a single, async runserver solution.

That said, I am also fine with this being something you have to pip install. The way I think this should work is:

  • You run manage.py runserver --asgi and it gives you an error saying you need to install daphne or uvicorn.
  • You install them using pip/poetry/etc.
  • manage.py runserver --asgi now just works
  • If you run manage.py runserver (no --asgi) and Django detects that you have async views, a warning is printed about async views under WSGI (optional, we could instead always just use an ASGI server if one is installed)
  • You run manage.py runserver --asgi and it gives you an error saying you need to install daphne or uvicorn .
  • You install them using pip/poetry/etc.

If it’s a separate package, we can add daphne as a dependency of that package, so the error wouldn’t be required. It would only support daphne to start.

  • manage.py runserver --asgi now just works

:white_check_mark:

  • If you run manage.py runserver (no --asgi ) and Django detects that you have async views, a warning is printed about async views under WSGI (optional, we could instead always just use an ASGI server if one is installed)

The warning is a nice touch.

I think if we want to settle on the separate package approach for now (it can always be merged in the future into the core as asgi matures with django):

  1. Should some of the code from the pr still get merged into django? Adding the get_response_async is required to get static files to work with asgi. get_internal_asgi_application isn’t necessarily required, but it seems like it would be nice to have, and i imagine it would eventually find its way into django.
  2. We can improve the existing app with the warning about async views when running wsgi.
  3. We can make a documentation update to include the a note about the package.

With the above, I think it’ll be a solid improvement!

I was thinking about this topic the other day.

Question: is asgiref.server.StatelessServer suitable for building a lightweight server for that would work with runserver that we could bundle in as we do with the current WSGIServer?

I honestly would prefer it was built into Django if we can get it in for 3.1 in time (though it may be too late now); it feels bad to ship something this core outside in a separate package,

@carltongibson: StatelessServer is for building other protocol servers, it doesn’t have any HTTP handling in it, so I doubt it would work. It’s possible asgiref could grow something akin to SimpleHTTPServer in wsgiref, but that’ll take extra work and likely require pulling in a HTTP parsing library, which I’m not super keen on for obvious reasons.

The difficulty with 3.1 is that the feature freeze was a month ago. We’re less than two weeks from the beta. It’s not clear we can add any features now.

Then there’s the separate question of bringing in a dependency on (say) twisted which is pretty full on.

Options to install channels and use runserver from there, or uvicorn, which had a --reload flag don’t seem too onerous: this stuff is all still evolving after all. (I appreciate this point is where the debate is.)

As I was imagining this:

  • An ASGI server based off of Python’s http.server would be ideal.
  • We could work on a package, such as a hypothetical django-asgi-runserver, that would give the command until it can be merged (let’s say 3.2) and then maintained until 3.1 was EOL.

Summer Project™ — I haven’t had a moment since the pandemic began hit here hard in March so not sure how realistic that is. (But that was the path forward that I imagined…)

Well, I would not bring in any dependency in Django, just make runserver use daphne/uvicorn if it is available. But yes, the beta is close, and I don’t really want to cram this in last-minute, even if it will never affect production installs as it’s in runserver. Still sucks that 3.1 will ship without a working async runserver, though.

Yes. It would be nice.

(I think what’s there is amazing. I can’t feel too bad about any lacks…:slightly_smiling_face:)

I like this approach, I think we should do it more with Django.

Hi @massover — just wanted to add, looking at your asgi-runserver app whilst reviewing the ASGIStaticFilesHandler PR, it’s really nice.

Looks like the get_response_async pr got merged today. From the original big PR, what do y’all think about a ticket for the small addition of get_internal_asgi_application to keep feature parity with wsgi?

Also, as far as the django-asgi-runserver app, what about starting a more generic app? django-asgi or django-asgi-utils. Perhaps this could be an app where we grow asgi specific code until it matures. Code could live there forever, like in django-model-utils, or make its way over to django as it evolves. For example, one thing I’ve been working on that might be at home there is an ASGIHandler + AsyncStreamingHttpResponse for async streaming responses, such as SSE. Not sure if it’s an anti goal to the problem at hand, just figured I’d ask what y’all thought.

edit: what might have been unclear, my suggestion was instead of having just an “official” django-asgi-runserver app, putting the runserver command inside the “official” django-asgi-utils app.

While I like having things built in, shipping an official django-asgi-runserver package would also work for me.

@massover, I do think get_internal_asgi_application makes sense to add; while I’ve never been a fan of get_internal_wsgi_application, it does serve a purpose for runserver.