Upload Multiple Images with FastHTML
I’ve been experimenting with FastHTML for making quick demo apps, often involving language models.
It’s a pretty simple but powerful framework, which allows me to deploy a client and server in a single main.py
— something I appreciate a lot for little projects I want to ship quickly.
I currently use it how you might use streamlit
.
I ran into an issue where I was struggling to submit a form with multiple images.
I started with an app that could receive a single image upload from this example.
These examples assume the code is in a main.py
file and run with
uvicorn main:app --reload
from fasthtml.common import *
app, rt = fast_app()
@rt("/")def get(): inp = Input(type="file", name="image", multiple=False, required=True) add = Form( Group(inp, Button("Upload")), hx_post="/upload", hx_target="#image-list", hx_swap="afterbegin", enctype="multipart/form-data", ) image_list = Div(id="image-list") return Title("Image Upload Demo"), Main( H1("Image Upload"), add, image_list, cls="container" )
@rt("/upload")async def upload_image(image: UploadFile): contents = await image.read() print(contents) filename = image.filename return filename
The contents of the images prints in the console and the filename shows up in the browser.
To support multiple files/images, I tried the following:
from fasthtml.common import *import uvicornimport os
app, rt = fast_app()
@rt("/")def get(): inp = Input(type="file", name="images", multiple=True, required=True) add = Form( Group(inp, Button("Upload")), hx_post="/upload", hx_target="#image-list", hx_swap="afterbegin", enctype="multipart/form-data", ) image_list = Div(id="image-list") return Title("Image Upload Demo"), Main( H1("Image Upload"), add, image_list, cls="container" )
@rt("/upload")async def upload_image(images: List[UploadFile]): print(images) filenames = [] for image in images: contents = await image.read() filename = image.filename filenames.append(filename) return filenames
When we pick and upload multiple files, this code breaks, but with the print statement we can see the data we uploaded.
[UploadFile(filename=None, size=None, headers=Headers({})), UploadFile(filename=None, size=None, headers=Headers({}))]
Not quite what I expected.
After a bit of searching, I learned that a fasthtml
function signature can be any compatible starlette
function signature (source).
With this knowledge, I tried the following approach:
from fasthtml.common import *
app, rt = fast_app()
@rt("/")def get(): inp = Input( type="file", name="images", multiple=True, required=True, accept="image/*" ) add = Form( Group(inp, Button("Upload")), hx_post="/upload", hx_target="#image-list", hx_swap="afterbegin", enctype="multipart/form-data", ) image_list = Div(id="image-list") return Title("Image Upload Demo"), Main( H1("Image Upload"), add, image_list, cls="container" )
@rt("/upload")async def upload_image(request: Request): form = await request.form() images = form.getlist("images") print(images) filenames = [] for image in images: contents = await image.read() filenames.append(image.filename) return filenames
This approach successfully rendered the titles of two images when I uploaded them in a single request as expected.
Recommended
FastHTML Loading Spinner
I've enjoyed using fasthtml to deploy small, easily hosted webpages for little apps I've been building. I'm still getting used to it but it almost no...
Add Alt Text to an Image with an LLM and Cursor
Using Cursor, we can easily get a first pass at creating alt text for an image using a language model. It's quite straightforward using a multi-modal...
Multi-Modal Models with ollama
I spent some time experimenting with multi-modal model (also called vision models on the ollama site) to see how they perform. You try these out with...