Image Upload ¶
Simple image upload system using automatic base64 encoding into signals.
Endpoint 1: Upload Form Page ¶
from stario import Stario
from stario.datastar import Actions, Attributes, FileSignal, Signal
from stario.html import button, div, form, input_, label, p
from stario.toys import toy_page
app = Stario()
@app.query("/upload")
async def upload_page(attr: Attributes, act: Actions):
"""Render image upload page with signal-based file handling."""
return toy_page(
form(
attr.signals({
"files": [],
"success": False,
"errors": {},
}),
# Image Upload Section
div(
p("Upload Images"),
label(
p("Choose image files (max 5MB each)"),
input_(
{
"type": "file",
"accept": "image/*",
"multiple": True,
},
# This will automatically bind base64 encoded files into signal
attr.bind("files"),
),
),
# Error display
div(
attr.show("$errors.files"),
p(attr.text("$errors.files")),
),
),
# Success Message
div(
attr.show("$success"),
p("Images uploaded successfully!"),
),
# Submit Button
button(
{"type": "button"},
attr.attr({"disabled": "$uploading || !$files.length"}),
attr.on("click", act.post("/upload-images")),
attr.text("$uploading ? 'Uploading...' : 'Upload Images'"),
attr.indicator("uploading"),
),
# Loading indicator
div(
attr.show("$uploading"),
p("Uploading images..."),
),
),
)
Endpoint 2: Image Upload Processing ¶
@app.command("/upload-images")
async def upload_images(files: Signal[list[FileSignal]]):
"""Process and save uploaded images."""
if not files:
yield {"errors": {"files": "No images selected"}}
return
# Using FileSignal object for easier access to file properties
for file_info in files:
# VALIDATION:
# Let's just check the file size here:
if file_info.size() > (5 * 1024 * 1024): # 5MB:
# You could also just patch the html elements
# rather than updating the signals - up to you!
error_msg = f"File too large: {file_info.name} ({file_info.size() / 1024 / 1024:.1f}MB > 5MB)"
yield {"errors": {"files": error_msg}}
return
# PROCESSING:
# Do anything with the file here...
# Process, save, schedule etc...
# Success! Clear form and show results
yield {
"files": [],
"success": True,
"errors": {},
}
How It Works ¶
Image Upload Flow ¶
- User selects images using
<input type="file" data-bind="files" multiple/> - Files are automatically encoded as base64 and bound to the
filessignal asFileSignalobjects (Signal binding) - Submit button becomes enabled when files are selected (
!$files.lengthdisables it) - User clicks upload button
attr.indicator("uploading")automatically creates an$uploadingsignal that tracks request state (trueduring request,falsewhen complete)- Server receives
FileSignalobjects with convenient methods like.size()and.name - Server validates and processes files, then returns success/error signals
Key Points ¶
- Files bind automatically as
FileSignalobjects with.name,.size(),.mime,.contents attr.indicator("uploading")creates$uploadingsignal for request state- Always validate file size/type on server before processing
