ASGI (Asynchronous Server Gateway Interface)

This chapter contains information about using ASGI with Pyramid. Read about the ASGI specification.

The example app below uses the WSGI to ASGI wrapper from the asgiref library to transform normal WSGI requests into ASGI responses. This allows the application to be run with an ASGI server, such as uvicorn or daphne.

WSGI -> ASGI application

This example uses the wrapper provided by asgiref to convert a WSGI application to ASGI, allowing it to be run by an ASGI server.

Please note that not all extended features of WSGI may be supported, such as file handles for incoming POST bodies.

 1# app.py
 2
 3from asgiref.wsgi import WsgiToAsgi
 4from pyramid.config import Configurator
 5from pyramid.response import Response
 6
 7def hello_world(request):
 8    return Response("Hello")
 9
10# Configure a normal WSGI app then wrap it with WSGI -> ASGI class
11
12with Configurator() as config:
13    config.add_route("hello", "/")
14    config.add_view(hello_world, route_name="hello")
15    wsgi_app = config.make_wsgi_app()
16
17app = WsgiToAsgi(wsgi_app)

Extended WSGI -> ASGI WebSocket application

This example extends the asgiref wrapper to enable routing ASGI consumers alongside the converted WSGI application. This is just one potential solution for routing ASGI consumers.

  1# app.py
  2
  3from asgiref.wsgi import WsgiToAsgi
  4
  5from pyramid.config import Configurator
  6from pyramid.response import Response
  7
  8
  9class ExtendedWsgiToAsgi(WsgiToAsgi):
 10
 11    """Extends the WsgiToAsgi wrapper to include an ASGI consumer protocol router"""
 12
 13    def __init__(self, *args, **kwargs):
 14        super().__init__(*args, **kwargs)
 15        self.protocol_router = {"http": {}, "websocket": {}}
 16
 17    async def __call__(self, scope, *args, **kwargs):
 18        protocol = scope["type"]
 19        path = scope["path"]
 20        try:
 21            consumer = self.protocol_router[protocol][path]
 22        except KeyError:
 23            consumer = None
 24        if consumer is not None:
 25            await consumer(scope, *args, **kwargs)
 26        await super().__call__(scope, *args, **kwargs)
 27
 28        if consumer is not None:
 29            await consumer(scope, *args, **kwargs)
 30        try:
 31            await super().__call__(scope, *args, **kwargs)
 32        except ValueError as e:
 33            # The developer may wish to improve handling of this exception.
 34            # See https://github.com/Pylons/pyramid_cookbook/issues/225 and
 35            # https://asgi.readthedocs.io/en/latest/specs/www.html#websocket
 36            pass
 37        except Exception as e:
 38            raise e
 39
 40
 41    def route(self, rule, *args, **kwargs):
 42        try:
 43            protocol = kwargs["protocol"]
 44        except KeyError:
 45            raise Exception("You must define a protocol type for an ASGI handler")
 46
 47        def _route(func):
 48            self.protocol_router[protocol][rule] = func
 49
 50        return _route
 51
 52
 53HTML_BODY = """<!DOCTYPE html>
 54<html>
 55    <head>
 56        <title>ASGI WebSocket</title>
 57    </head>
 58    <body>
 59        <h1>ASGI WebSocket Demo</h1>
 60        <form action="" onsubmit="sendMessage(event)">
 61            <input type="text" id="messageText" autocomplete="off"/>
 62            <button>Send</button>
 63        </form>
 64        <ul id='messages'>
 65        </ul>
 66        <script>
 67            var ws = new WebSocket("ws://127.0.0.1:8000/ws");
 68            ws.onmessage = function(event) {
 69                var messages = document.getElementById('messages')
 70                var message = document.createElement('li')
 71                var content = document.createTextNode(event.data)
 72                message.appendChild(content)
 73                messages.appendChild(message)
 74            };
 75            function sendMessage(event) {
 76                var input = document.getElementById("messageText")
 77                ws.send(input.value)
 78                input.value = ''
 79                event.preventDefault()
 80            }
 81        </script>
 82    </body>
 83</html>
 84"""
 85
 86# Define normal WSGI views
 87def hello_world(request):
 88    return Response(HTML_BODY)
 89
 90# Configure a normal WSGI app then wrap it with WSGI -> ASGI class
 91with Configurator() as config:
 92    config.add_route("hello", "/")
 93    config.add_view(hello_world, route_name="hello")
 94    wsgi_app = config.make_wsgi_app()
 95
 96app = ExtendedWsgiToAsgi(wsgi_app)
 97
 98# Define ASGI consumers
 99@app.route("/ws", protocol="websocket")
100async def hello_websocket(scope, receive, send):
101    while True:
102        message = await receive()
103        if message["type"] == "websocket.connect":
104            await send({"type": "websocket.accept"})
105        elif message["type"] == "websocket.receive":
106            text = message.get("text")
107            if text:
108                await send({"type": "websocket.send", "text": text})
109            else:
110                await send({"type": "websocket.send", "bytes": message.get("bytes")})
111        elif message["type"] == "websocket.disconnect":
112            break

Running & Deploying

The application can be run using an ASGI server:

$ uvicorn app:app

or

$ daphne app:app

There are several potential deployment options, one example would be to use nginx and supervisor. Below are example configuration files that run the application using uvicorn, however daphne may be used as well.

Example nginx configuration

 1upstream app {
 2    server unix:/tmp/uvicorn.sock;
 3}
 4
 5server {
 6
 7    listen 80;
 8    server_name <server-name>;
 9
10    location / {
11        proxy_pass http://app;
12        proxy_set_header Host $host;
13        proxy_set_header X-Real-IP $remote_addr;
14        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
15        proxy_set_header X-Forwarded-Proto $scheme;
16        proxy_buffering off;
17        proxy_http_version 1.1;
18        proxy_set_header Upgrade $http_upgrade;
19        proxy_set_header Connection "Upgrade";
20        proxy_redirect off;
21    }
22
23    location /static {
24      root </path-to-static>;
25    }
26}

Example Supervisor configuration

1[program:asgiapp]
2directory=/path/to/app/
3command=</path-to-virtualenv>/bin/uvicorn app:app --uds /tmp/uvicorn.sock --workers 2 --access-log --log-level error
4user=<app-user>
5autostart=true
6autorestart=true
7redirect_stderr=True
8
9[supervisord]