Async Python Programming and Uvicorn

Async Python Programming and Uvicorn

Async Programming is a form of parallelism in which you are attempting to perform several jobs at the same time. 

Async works by using two main keywords "async" which goes before a method or function and "await" which tells the program to wait for a job to finish before continuing. 

For example if you had a program that would send out information to be processed and you know it takes 10 seconds for the program to return information, you could set up synchronous call that sends the request, sits and waits for the info, and then continues OR *you could set up an async call that sends the information to be processed, then sends another to be processed, and another, until that method receives the necessary information and then continues with the rest of its instructions*

In a simpler example, think about tasks that you do day to day. When you make coffee, my guess would be that you put the pot on, and then go off and start making your oatmeal. You don't stand there doing nothing until the coffee finishes. You let the coffee finish while you do something else. 

Resources: https://realpython.com/async-io-python/

Here is a good example of asynchronous work:

```

Chess master Judit Polgár hosts a chess exhibition in which she plays multiple amateur players. She has two ways of conducting the exhibition: _synchronously_ and _asynchronously_.

Assumptions:

- 24 opponents

- Judit makes each chess move in 5 seconds

- Opponents each take 55 seconds to make a move

- Games average 30 pair-moves (60 moves total)

**Synchronous version**: Judit plays one game at a time, never two at the same time, until the game is complete. Each game takes _(55 + 5) * 30 == 1800_ seconds, or 30 minutes. The entire exhibition takes _24 * 30 == 720_ minutes, or **12 hours**.

**Asynchronous version**: Judit moves from table to table, making one move at each table. She leaves the table and lets the opponent make their next move during the wait time. One move on all 24 games takes Judit _24 * 5 == 120_ seconds, or 2 minutes. The entire exhibition is now cut down to _120 * 30 == 3600_ seconds, or just **1 hour**.

```

In Async programming there is a such thing as a *coroutine.* A coroutine is an object **that can suspend its execution and resume it later.**

- In the meantime it can pass the control to an event loop which can execute another coroutine

- A coroutine function and an asynchronous function are the same thing. We define this in python by using the async def construct. An example is below. 

```

import asyncio

async def count():

 print("One")

 await asyncio.sleep(1)

 print("Two")

 await asyncio.sleep(1)

async def main():

 await asyncio.gather(count(), count(), count())

if __name__ == "__main__":

 import time

 start = time.perf_counter()

 asyncio.run(main())

 elapsed = time.perf_counter() - start

 print(f"{__file__} executed in {elapsed:0.2f} seconds.")

```

**Each time the await keyword is used it is providing the program with access to the event loop.**

This means that when this is ran, it will print "One" then trigger sleep, at the same time the event loop is run again and so "One" will print again, and again (if called multiple times") each time it is waiting for the sleep method to finish before continuing on to the next portion of the program. 

Steps to create an asynchronous function. 

1. Define the function with the "async" constructor

2. Use the "await" keyword for any portion of the program that might have longstanding calls to make. 

**Uvicorn**

Now for making asynchronous calls its important to remember that to make programs (specifically web applications) that can leverage asynchronous calls, we need to leverage a web server gateway interface that can handle async calls.

The most common WSGI for Flask applications is Gunicorn, but recently a new kid is on the block called **Uvicorn.** Uvicorn is more or a less a wrapper around a python application that turns the application not into just a simple callable (IE An HTTP request goes to the application and the application responds with a response). Instead, each application is an **asynchronous callable** that both sends asynchronous requests (Ex: API calls, Web Requests, etc.) and receives them.

This is a major improvement from gunicorn's synchronous nature which limits the ability for python applications to use protocols like websockets for retrieving real time data.

**Getting Wild: Tom is Gonna Read Documentation and Try to Translate It**

OK, so the thing to remember is that everything in this blog might not be perfect. I am not an expert on async programming, and oftentimes I am awful and deciphering documentation, but this is something I know I need to get better at, so I am going to do my best to offer my understanding of what this page https://asgi.readthedocs.io/en/latest/specs/main.html is saying to hopefully help someone out there who might be confused. I am going to try to explain this as simply as I can and hopefully get it right. Let's see how this goes.

1. The articles begins by discussing the benefits of WSGI (gunicorn), but highlights that gunicorn was limited to strictly HTTP/1 type requests (aka someone sends a request to the app, it sends one back when it's ready)

2. This is great and all, but there is now the HTTP/2 specification as well as websockets which are not compatible with gunicorn as it stands. In comes Uvicorn.

3. ASGI is attempting to preserve the simple app interface while abstracting (aka hiding complexity beneath the surface) the logic which allows for data to be sent and received at any time (non-synchronous).

4. ASGI is trying to make it as simple as possible for applications to be able to process new types of protocols as well. ASGI should be thought of as two parts:

5. A standard interface for communication on how servers should be built.

6. A set of standard message formats for each protocol (aka Uvicorn should receive messages that are compatible with their system and be able to translate them into something the python application can understand)

7. The ASGI specification aims to make it possible for Python applications to be able to leverage both asynchronous programming as well as synchronous programming based on the application's needs.

**Breaking down ASGI Components (There are only 2!)**

1. **Protocol Server:** This server handles the initial message sent to your python application and translates it as needed (opens connections, closes connections)

2. **Application:** This lives inside the protocol server and is called once per connection and handles event messages as they happen. When needed the app will also send it's own messages back.

**Like WSGI but Cooler**

As discussed before, WSGI was great and was wrapped around a Python application which would handle a request and then send back information synchronously. What is new is that unlike WSGI, all applications inside an ASGI server must act as an **Asynchronous Callable** aka it must support async/ await coroutines.

Now lets talk about how these connections work. Again there are two parts:

1. **Connection Scope:** Scope == Protocol Connection (HTTP/1, HTTP/2, Websocket). These live until the connection closes for one reason or another.

2. **Events:** Events are equivalent to the information sent over the Protocol Connection while they are open.

**When Someone Uses Your App:**

Ok, so you're hosting a seafood website. You want everyone to see how cool crabs are or whatever. When someone goes to your site, what happens exactly?

Well from what I am reading here, it looks like it could be something like this:

1. User sends a request to crabsarecool.com

2. This results in a call to your Python application called crab_app

3. Three pieces of information are passed to your ASGI application

4. **Scope:** aka HTTP/1 or HTTP/2 in this case.

5. **Receive**: An _awaitable callable_ which will yield a new _event dictionary_ when one is available.

6. Aka this method that is passed will be run and wait for information to return from your app. (Send an HTTP request, app receives it)

7. **Send:** An awaitable callable taking a single event dictionary as a positional argument that will return once the **send has been completed OR the connection has been closed**.

8. The function sends information back out.

The application is called once per "connection" which depends on the protocol. For websockets, its a single websocket connection. For HTTP requests, its a single request.