Part 1: How to build an AI-powered bookmarking app with Python and WebAssembly

Matt Butcher - Jan 4 - - Dev Community

Python is a fantastic language for building HTTP apps, and it’s super easy to build such an app using Spin. To kick off the new year, I thought I’d write a series showcasing how to use Python and AI together with Spin. In this series, we’re going to build a Python web app for bookmarking pages. We’ll start simple, building just a basic HTML page and Python form handler. And we’ll work our way all the way to the point of adding an AI-powered summarizer.

And we’ll do all of this in 150 lines of code!

The app we will build is a bookmarking tool with AI summarizer

We’ll see how the Spin framework makes it super easy (and super fast) to write this style of app.

Key technologies we’ll work with:

  • Serverless functions
  • Python 3.11
  • The pipenv dependency manager
  • The Spin Framework
  • Key/Value Storage (a kind of NoSQL database)
  • Django2 templates
  • The Python http-router
  • AI LLM inferencing with Meta’s LLaMa2

Spin is a tool for creating serverless apps. A serverless app is simply an app where you (as the developer) do not need to create the a software server (a daemon) to listen for requests. Instead, you just write request handler functions, and leave it to the environment to handle all the server stuff like starting a socket, listening on a port, managing TLS, and so on.

With that background, let’s dive into part 1. In this part, we will scaffold out a Python app. This will be the foundation for the next part, where we build a bookmarking app. But if you’re just interested in getting a Python serverless function going, this part may be all you need to kickstart your next project.

Prerequisites

For this project, we’re going to use spin with the Python plugin and templates. We’re also going to make use of pipenv and pip.

You’ll need Spin 2.0.1 or greater. If the spin plugins list command does not show you py2wasm (the Python to Wasm compiler), you will want to install that and the Python templates:

$ spin plugin install py2wasm
$ spin templates install --git https://github.com/fermyon/spin-python-sdk --update
Enter fullscreen mode Exit fullscreen mode

I use pipenv to manage Python projects, and will be using it in this project.

Starting a New Project

With these things installed, it’s quick and easy to get started on our new project. We’ll use the Python http-py template to scaffold the project:

$ spin new -t http-py bookmarker
Description: A bookmark app
HTTP path: /...
Enter fullscreen mode Exit fullscreen mode

The command above uses the HTTP Python template (-t http-py) to create a new app named bookmarker. At this point, we can change into the bookmarker directory and start coding.

$ cd bookmarker
$ tree .
.
├── Pipfile
├── README.md
├── app.py
└── spin.toml

0 directories, 4 files
Enter fullscreen mode Exit fullscreen mode

Here’s a quick overview of the files you can see in the tree command above:

  • Pipfile is the usual config file that pipenv will use to track the project and its dependencies.
  • README.md is the main README for the project.
  • app.py is the main code file, and we’ll start working on it in just a moment
  • spin.toml is the Spin configuration file

For the time being, we don’t need to do anything with any of those files. We’re just going to build the scaffolded code and test it out.

$ spin build --up
Building component bookmarker with `spin py2wasm app -o app.wasm`
Spin-compatible module built successfully
Finished building all Spin components
Logging component stdio to ".spin/logs/"

Serving http://127.0.0.1:3000
Available Routes:
  bookmarker: http://127.0.0.1:3000 (wildcard)
Enter fullscreen mode Exit fullscreen mode

The spin build --up command does two things:

  • It builds our Python app into a WebAssembly binary
  • Then it starts a local HTTP server (--up) that listens on http://localhost:3000

We can use a browser or a command line program like curl to check out our app.

$ curl localhost:3000
Hello from the Python SDK
Enter fullscreen mode Exit fullscreen mode

And there we have it! A running app! Now let’s turn it into our own thing. You can use CTRL-C (or CMD-C on macOS) to stop the server.

Starting Some Code

The code in app.py looks like this:

from spin_http import Response
def handle_request(request):
    return Response(
        200,
        {"content-type": "text/plain"},
        bytes(f"Hello from the Python SDK", "utf-8"),
    )
Enter fullscreen mode Exit fullscreen mode

The handle_request() function is called each time a new inbound HTTP request comes in. It takes one argument, a Response object from the spin_http package. The Request object has a few different properties:

  • uri is the path that invoked this request
  • method is the HTTP verb (e.g. GET, POST, etc) that was sent by the client
  • headers is a dictionary of HTTP headers, as well as the special Spin headers
  • body contains the request body (such as POST data) if any was sent by the client

In the code above, we sent a Response object (also from spin_http). Constructing a Response takes three bits of information:

  • The HTTP status code (such as 200 for success or 404 for “Not Found”)
  • A dictionary of headers, with things like content-type to set the content type
  • The body of the request, which is sent back to the web browser to render

If we use curl’s -v flag, we can see all of that on the command line:

$ curl -v localhost:3000/
*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain
< content-length: 25
< date: Wed, 29 Nov 2023 00:02:32 GMT
<
* Connection #0 to host localhost left intact
Hello from the Python SDK
Enter fullscreen mode Exit fullscreen mode

Now we can do one quick change to see the results:

from spin_http import Response
def handle_request(request):
    return Response(
        200,
        {"content-type": "text/plain"},
        b"Hello from Bookmarker",
    )
Enter fullscreen mode Exit fullscreen mode

All we did here is change the returned body to b”Hello from Bookmarker”. The b decorator tells Python to return the string as an array of bytes, which is shorter than using the bytes() function.

If we do spin build --up again and test it with curl, we see the new result:

$ curl localhost:3000
Hello from Bookmarker
Enter fullscreen mode Exit fullscreen mode

Alright, we’ve got the basics down. Let’s get on to something a little more complex: Routing.

. . . . . . . . . . . . . . . . . .