How to Create and Host a Cloud Python 3 Web App FREE (Part 3)

Glitch.com screenshot

Introduction

In part 1 and part 2 you learned how to create a simple asynchronous Python 3 web app. It doesn’t do anything besides spit out some text. In this tutorial we’ll re-arrange the files slightly, and add some further cool asynchronous functionality.

Python 3 project file structure

Many complicated web applications will have a dozen or more routes, such as /tracks, /artists, /albums, and so on. And each of these routes needs a handler to perform the appropriate functionality.

Without splitting up the code into multiple files, or modules, you’ll end up with messy, hard to maintain code.

A common file structure for an imaginary Python 3 web app project called Zeus could be

zeus/
├── tests/
│   ├── test_zeus.py
│   └── test_utilities.py
├── zeus/
|   ├── handlers.py
|   ├── routes.py
│   ├── utilities.py
│   └── zeus.py
├── app.py
├── README.md
├── requirements.txt
└── start.sh

Where you have the a folder called zeus/ containing all of the files.

Unit Tests

The tests/ folder contains the Unit Tests for your project. Unit tests aid you in maintaining the accurate functionality of your application, as you start to expand and improve upon it.

As a trivial example of a unit test, imagine your zeus/utilities.py module had a function to calculate the total length of a music album, by taking in a list of tracks objects (each object has properties like track_name, artist_name, and track_length_seconds).

To make sure that this function named calculate_album_length worked as expected you could create a unit test that looked like this:

from zeus.utilities import calculate_album_length

def test_calculate_album_length():
    """"This function tests the calculate_album_length function
    from zeus/utilities.py
    """
    # Create a list of dummy tracks
    tracks = [{
      'track_name': 'What Makes You Happy',
      'artist_name': 'Edie Brickell',
      'track_length': 242,
    },
    {
      'track_name': 'Superhero',
      'artist_name': 'Edie Brickell',
      'track_length': 274,
    }]

    calculated_length = calculated_album_length(tracks)
    assert calculated_length == 516

The above code first imports a function from the utilities module. It then creates a Python list of dummy tracks called tracks (each track is respresented as a simple Python dict ).

The tracks variable is then passed into the calculated_album_length function from the .../zeus/utilities.py file, and we ensure that what was calculated is equal to 516 (242 + 274).

The unit test above doesn’t care how calculate_album_length actually calcuates the total length, it just cares that the final answer is correct.

It’s highly recommended for any even slightly significate applications to have accurate unit tests to make sure you don’t unintentionally break some functionality as you expand upon the functionality of the app, and especially as you optimize/improve existing code.

If you start your large project by thinking about, and creating the unit tests first you’ll usually end up writing better code. Writing & thiking about code and apps in this tests-first way is like thinking of what a consumer actually wants, and even what consumer’s experience will be like for any consumer product.

I find that, especially if you’re an in-experienced developer, by diving straight into implementation details immediately you end up wasting time creating code you don’t need, and the code you do write is more difficult to use.

Core Code

Under this main folder there’s another zeus folder with the main guts of the application. * utilities.py could contain some general utility functions, such as the calculate_album_length function as described above * handlers.py could contain the functions to handle the routes for the aiohttp web application. * routes.py could map the routes, like /login and /tracks/ to the appropriate handlers

handlers.py
# /zeus/zeus/handlers.py
from zeus.zeus import get_tracks

async def index_handler(request):
    ...
async def tracks_handler(request):
    # get the album name from the request
    album_name = request.match_info.get('album_name')
    tracks_text = await get_tracks(album_name)
    return web.Response(text=tracks_text)

async def login_handler(request):
   ...
routes.py
# /zeus/zeus/handlers.py
from zeus.handlers import index_handler, tracks_handler, login_handler

def setup_routes(app):
    """take an aiohttp app object and setup the routes"""

    app.router.add_routes([
      web.get('/', index_handler),
      web.get('/tracks', tracks_handler),
      web.post('/login', login_handler)
    ])

The zeus.py could have the main code, for example the imaginary get_tracks function.

async def get_tracks(album_name):
   """make an HTTP request to some external service to 
   get a list of tracks"""
   ...

Finally app.py ties all of the above together into an aiohttp web Application, similar to what you created in Part 1 of this tutorial.

# app.py

from aiohttp import web
from zeus.routes import setup_routes

if __name__ == '__main__':
  app = web.Application()
  #app.router.add_get('/', handle_index)
  setup_routes(app)
  web.run_app(app)

The end result is an app that you can use to get a list of track names from an album

Python 3 app track list

You now have a working web app that uses aiohttp for two purposes 1. aiohttp.web to run a web app service with two independant functional routes 2. aiohttp.ClientSession() to make HTTP requests to an external server to get a resource.

Next steps

In Part 4 we’ll expand upon the app and take full advantage of the asynchronous nature of aiohttp to show how powerful async is, as compared to non-async code.

Follow me on Twitter

My homepage

My podcast