Framework

Uploads

File uploads from the browser — form-based, programmatic, chunked, and progress tracking.

Uploads

SPRAG provides two upload methods in the browser Module, both with progress tracking and automatic chunked upload for large files.

Form-based upload

Use upload_form() when you have a standard file input in a form:

class UploadModule(Module):
    def on_start(self):
        self.delegate(self.element, "submit", "form", self.on_submit)

    def on_submit(self, event, target):
        event.prevent_default()
        self.upload_form("avatar", event, self.on_progress)

    def on_progress(self, progress):
        self.set_state({
            "percent": progress.percent,
            "phase": progress.phase,
        })

The first argument ("avatar") is the action name on the server. The event is the form submit event — SPRAG extracts the file inputs automatically.

Programmatic upload

Use upload() for drag-and-drop or any case where you have a File object directly:

def on_drop(self, event, target):
    event.prevent_default()
    file = event.dataTransfer.files[0]
    self.upload("process_image", file, {"resize": True}, self.on_progress)

The signature is upload(action_name, file, payload_dict, on_progress).

Progress object

Both methods call your progress callback with an object containing:

PropertyDescription
percentUpload progress 0–100
loadedBytes uploaded so far
totalTotal file size in bytes
phase"uploading", "processing", or "complete"
file_nameOriginal filename

Server side

Handle uploaded files in your controller action:

@action()
def avatar(self):
    file = self.request.file("file")
    file.save(f"uploads/{file.filename}")
    return {"avatar_url": f"/uploads/{file.filename}"}

UploadedFile

The file() method returns an UploadedFile with:

Property/MethodDescription
filenameOriginal filename
sizeFile size in bytes
content_typeMIME type
text()Read file content as text
save(path)Save to disk

For multiple files, use self.request.files_list("files") to get a list.

Chunked uploads

Files above 2MB are automatically chunked — split into pieces, uploaded in sequence, and reassembled on the server. This is transparent to both the browser author and the server action. The progress callback still reports overall progress.

Post-upload processing

For expensive processing after upload (image resizing, video transcoding), use a deferred action or queue:

@action(defer=True)
def process_image(self, **data):
    file = self.request.file("file")
    resize = data.get("resize", False)
    # Runs in background — browser gets immediate acknowledgment
    result = process(file, resize=resize)
    return {"processed_url": result.url}

For bounded concurrent processing with progress, use a QueueService instead.