DEV Community

Cover image for Write Your Own Python Async ASGI Web App, No Framework Needed
Jonathan Bowman
Jonathan Bowman

Posted on

Write Your Own Python Async ASGI Web App, No Framework Needed

ASGI is the new WSGI, with an asynchronous flair. ASGI is emerging as a new standard for asynchronous Python web apps. Because web applications spend a lot of time waiting, async web apps make a lot of sense, and have a significant performance boost over synchronous apps. Speedy.

Thankfully, ASGI frameworks like Starlette, Quart, Django Channels, and BlackSheep exist. With any of them, your high performance Python web app can be up and running stably and quickly.

But if you are a roll-your-own, do-it-yourself, disdain-for-abstractions, self-made, tired-of-hyphenation kind of developer, then you want to take a peek under the hood.

The engine

"""A barebones ASGI app that dumps scope."""

import pprint

def pretty_html_bytes(obj):
    """Pretty print a Python object in <pre> tags."""
    pp = pprint.PrettyPrinter(indent=2, width=256)
    prettified = pp.pformat(obj)
    return f"<pre>{prettified}</pre>".encode()

async def app(scope, receive, send):
    """The simplest of ASGI apps, displaying scope."""
    headers = [(b"content-type", b"text/html")]
    body = pretty_html_bytes(scope)
    await send({"type": "http.response.start", "status": 200, "headers": headers})
    await send({"type": "http.response.body", "body": body})

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
Enter fullscreen mode Exit fullscreen mode

Run the above, open a browser window with http://localhost:8000, and you should see a dump of the ASGI app's scope. While scope["path"] is likely the most interesting (change the path and watch that value change), scope["client"], scope["scheme"], and others are of interest as well.

You may also try out the Repl.it I made with the above code.

Customize

Try changing the body, but do make sure it is in bytes, not a string. Appening .encode(), such as in my_string.encode() will usually convert a string to bytes in the proper way. You can make the character encoding explicit with my_string.encode("utf-8").

Add some routing with various if scope["path"] == "/my/path": and elif, etc. Use an else to catch paths that don't match, changing the status to 404.

A static handler is an option as well. I usually place static assets like CSS, Javascript, and images in a particular directory hierarchy. For instance, if scope["path"].startswith("/assets/"): makes a good start. Then open and read the file, setting the body to the bytes read.

You may also try out the other ASGI servers in addition to Uvicorn: Hypercorn and Daphne. I also wrote a summary article about all three.

Enjoy! This is why Python has such a proliferation of web frameworks. Easy to get started and hooked.

Top comments (0)