microdot.multipart — multipart/form-data parsing

Parses Content-Type: multipart/form-data request bodies – the encoding browsers use for forms that include <input type="file"> fields. Two flavors of API:

  • A streaming FormDataIter that yields fields one at a time – useful when the application has to handle very large uploads piece by piece on a memory-constrained device.

  • A with_form_data() decorator that buffers everything and populates request.form / request.files – the convenient API for normal-sized uploads.

class FormDataIter

class microdot.multipart.FormDataIter(request)

Async iterator over the parts of request’s multipart body. Yielded values are (name, value) tuples; value is a str for regular fields and a FileUpload for file fields.

Used directly when memory matters more than ergonomics:

from microdot.multipart import FormDataIter, FileUpload

@app.post('/upload')
async def upload(request):
    async for name, value in FormDataIter(request):
        if isinstance(value, FileUpload):
            await value.save('/sdcard/' + value.filename)
        else:
            print(name, '=', value)
    return 'ok'

Note

When iterating over file fields, the file must be consumed (via FileUpload.read() or FileUpload.save()) before the next async for iteration – the underlying stream is invalidated when iteration advances.

buffer_size: int

Class attribute. Chunk size used while reading from the request stream. Default 256 bytes. Increase for higher throughput at the cost of RAM.

class FileUpload

class microdot.multipart.FileUpload(filename: str, content_type: str | None, read)

A single uploaded file. Instances are yielded by FormDataIter and collected into request.files by with_form_data(). Applications do not normally construct FileUpload directly.

filename: str

The file’s original name as the client sent it (untrusted – do not pass to open() without sanitizing).

content_type: str | None

The MIME type from the part’s Content-Type header, or None if not provided.

max_memory_size: int

Class attribute. Threshold (in bytes) above which copy() switches from in-memory buffering to a temporary file. Default 1024.

async read(n: int = -1)

Read up to n bytes from the upload stream. -1 reads to end.

async save(path_or_file)

Save the upload to path_or_file, which can be a filesystem path or an already-open file object.

async copy(max_memory_size: int | None = None)

Buffer the upload (either in RAM or in a temp file, depending on max_memory_size) so the rest of the multipart body can be parsed without the original stream being invalidated. The with_form_data() decorator calls this automatically.

async close()

Release any temporary file created by copy(). Called automatically when the request finishes if the upload reached request.files via with_form_data().

Module-level decorators

microdot.multipart.with_form_data(f)

Decorator that parses the multipart body up front and populates request.form and request.files before the handler runs:

from microdot import Microdot
from microdot.multipart import with_form_data

app = Microdot()

@app.post('/upload')
@with_form_data
async def upload(request):
    print('fields:', dict(request.form))
    for name, file in request.files.items():
        await file.save('/sdcard/' + sanitize(file.filename))
    return 'ok'

File uploads are buffered via FileUpload.copy(), so the handler can iterate request.files and request.form freely. Temporary files are cleaned up automatically when the request ends.

For uploads larger than a couple of megabytes, prefer the streaming FormDataIter API; with_form_data() accumulates the entire request in memory or on the filesystem before the handler runs.