Part 1: How to build an AI-powered bookmarking app with Python and WebAssembly
Matt Butcher
Posted on January 4, 2024
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!
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
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: /...
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
Here’s a quick overview of the files you can see in the tree
command above:
-
Pipfile
is the usual config file thatpipenv
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)
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 onhttp://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
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"),
)
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 asPOST
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 or404
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
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",
)
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
Alright, we’ve got the basics down. Let’s get on to something a little more complex: Routing.
Posted on January 4, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 4, 2024