WSGI and ASGI as interfaces, and Django's two application objects
coreintermediateWSGI and ASGI are *calling conventions* between a server and a Python application — they are not servers, and they are not frameworks. WSGI is the older, synchronous one: the server calls `application(environ, start_response)`, gets an iterable of bytes back, and that is the whole protocol, so one request occupies one worker for its full duration. ASGI is the async-capable successor: the server awaits `application(scope, receive, send)`, where `scope` describes the connection, and `receive`/`send` are awaitable channels for messages. That message-passing shape is what lets ASGI carry things WSGI structurally cannot — WebSockets, server-sent events, long-lived connections and background lifespan hooks. `startproject` generates both `wsgi.py` and `asgi.py`; which one you point your server at is a deployment decision.
Think of it as
The difference is not "sync versus async" so much as "one call versus a conversation". WSGI models a request as a single function call that returns a response, which is a complete description of HTTP request/response and nothing else — there is no way to express "the client sent another frame" or "the connection is still open", so WebSockets are not an omission from WSGI, they are unrepresentable in it. ASGI replaces the single call with a scope plus two awaitable channels, so a connection becomes a stream of messages the application can read from and write to over time. HTTP is then one protocol expressed in that shape, and WebSocket is another. Two practical consequences follow. First, ASGI is a superset in capability, so a sync Django view runs fine under ASGI — Django adapts it into a thread — which means migrating is a deployment change plus a middleware audit rather than a rewrite. Second, `asgi.py` and `wsgi.py` both exist in every generated project and are not interchangeable at the server level: a WSGI server cannot serve `asgi.py`, and pointing Gunicorn at the wrong module produces a startup error rather than a subtly degraded site.
What we're doing: Serve HTTP and WebSocket from one ASGI application, so both share the Django settings, apps and ORM.
- 4–6
- The order is load-bearing: `get_asgi_application()` populates the app registry, so any model import above this line raises `AppRegistryNotReady`.
- 8
- The imports sit below the call for the same reason. This is one of the few places where an import genuinely cannot go at the top of the file.
- 13
- `scope["type"] == "http"` routes to the ordinary Django application, so every existing view, middleware and URL keeps working unchanged.
- 14
- WebSocket connections take a different branch entirely — the shape ASGI adds, and the reason a WSGI deployment cannot serve them at all.
Why this works: One ASGI entry point serving both protocols means the WebSocket side shares settings, the app registry and the ORM with the HTTP side, rather than being a second service that has to duplicate all three.
Importing models above `get_asgi_application()`
Wrong
Better
What you see: `django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.` at startup — under an ASGI server only, while `manage.py runserver` may work, because it initialises the registry by a different path.
Why: `get_asgi_application()` is what calls `django.setup()` and populates the app registry. Any module imported before it that touches `models` asks the registry for something it has not built yet. This is the same rule as `wsgi.py`, but it bites harder here because ASGI routing usually needs consumer imports in the same file — which is why the imports go *below* the call.
- WSGI — a function call
- The server calls the app once and reads the returned iterable.
- The worker is occupied for the whole request, whatever it is waiting on.
- There is nowhere to put a message that arrives later — so no WebSockets.
- Concurrency is entirely a matter of how many workers you run.
- Still the right answer for a purely synchronous, request/response application.
- ASGI — a scope and two channels
- scope describes the connection; receive and send carry messages over time.
- HTTP is one protocol in this shape; WebSocket is another.
- A worker can hold many connections at once if nothing blocks the loop.
- Sync Django views still work — Django adapts them into a thread.
- Required for streaming, long-polling, and anything long-lived.
The two interfaces, side by side
Together
Remember: WSGI and ASGI are calling conventions, not servers. WSGI is one call returning one response, which is why WebSockets are unrepresentable in it rather than merely absent. ASGI is a scope plus awaitable `receive`/`send` channels, so a connection becomes a stream of messages and HTTP is just one protocol in that shape. ASGI is a superset — sync views run under it unchanged — so migrating is a deployment change plus a middleware audit. And `asgi.py` needs an ASGI worker class: the module name does not choose the protocol.
See also: servers workers and lifecycle · async views and the asgi requirement · asgi py · wsgi py

