FastAPI + Zeit.co = 🚀

Hey everyone,

I will talk about some experiments I did with FastAPI and deploying on Zeit.co.

Context: For one side-project, I needed a simple way to retrieve the information from a specific Android application (eg. io.shodan.app) on the Google Store and a download link from APKPure (if any).

I thought that this would have been a great playground to get my hands on FastAPI, a very promising framework that I never had time to play with.

Alright, so let's go straight in the code!

In order to serve my FastAPI application, we are going to use uvicorn.

Based on the FastAPI introduction, you basically just need to install the fastapi and uvicorn dependencies:

pip install fastapi uvicorn

The (bootstraping) Python code is as… simple as that:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

In order to try your app, one simple command will do the work:

$ uvicorn main:app --reload

The last parameter --reload will automatically reload the app as soon as you will perform some changes on the source code. Pretty handy especially when you're prototyping.

Couple of minutes later, I had proof of concept ready to get deployed on zeit.co.

The final source code is :

from fastapi import FastAPI, HTTPException
import play_scraper
import datetime
from bs4 import BeautifulSoup
import requests
import optparse
import sys

app = FastAPI()

@app.get("/app/{app_id}")
def retrieve_info(app_id: str):
    return {
        "google_play" : google_play(app_id),
        "apkpure": apkpure_get_last_version(app_id),
        "app_id": app_id,
        "timestamp": datetime.datetime.now(),
    }
    # return HTTPException(404, {"message": "app_id {} not found".format(app_id)})

def google_play(app_id: str):
    try:
        return play_scraper.details(app_id)
    except:
        return { "message": "Can't find information on Playstore for {}".format(app_id) }

def apkpure_get_last_version(app_id: str):
    versions = apkpure_get_versions(app_id)
    if 'message' in versions:
        return versions
    
    latest_version = versions[0]
    for version in versions[1:]:
        if version['update_date'] > latest_version['update_date']:
            latest_version = version
    return latest_version

def apkpure_get_versions(app_id: str):
    url = 'https://apkpure.com/en/{}/versions'.format(app_id)
    req = requests.get(url)
    soup = BeautifulSoup(req.content, 'html.parser')
    try:
        result = []
        container = soup.findAll('ul', attrs={'class': 'ver-wrap'})[0]
        app_versions = container.findAll('li')
        for app in app_versions:
            try:
                tmp_download = app.find('a', attrs={'class': 'down'})['href'].split('?')[0]
                download_link = "https://apkpure.com{}".format(tmp_download)
            except:
                download_link = "We didn't manage to get it."
            version = app.find('span', attrs={'class': 'ver-item-n'}).text
            size = app.find('span', attrs={'class': 'ver-item-s'}).text
            developer = app.find('p').text
            update_date = app.find('p', attrs={'class': 'update-on'}).text
            result.append({
                'version': version,
                'size': size,
                'developer': developer,
                'update_date': update_date,
                'download_link': download_link
            })
        return result
    except Exception as err:
        return { "message": "Can't find the latest version on APKPure for {}".format(app_id) }

Make sure to create a requirements.txt file in the root directory. I added those dependencies :

play-scraper
fastapi
uvicorn
bs4

And finally, but most importantly part for the deployment phase, I created a now.json file in the root folder.

The content of the file is as follow:

{
    "version": 2,
    "public": false,
    "builds": [{ "src": "main.py", "use": "@now/python" }],
    "routes": [
        { "src": "/", "dest": "main.py" },
        { "src": "/app/(.*)", "dest": "main.py" }
    ]
}

Finally, you are ready to deploy and launch the final command!

$ now .

And.. the app is deployed. You can give it a shot at: https://android-version-checker.now.sh/app/io.shodan.app

And if you prefer to check it out with cURL:

$ curl https://android-version-checker.now.sh/app/io.shodan.app --silent | jq .