:mod:`microdot.multipart` --- multipart/form-data parsing
=========================================================
.. module:: microdot.multipart
:synopsis: multipart/form-data body parser for Microdot
Parses ``Content-Type: multipart/form-data`` request bodies -- the
encoding browsers use for forms that include ````
fields. Two flavors of API:
* A streaming :class:`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 :func:`with_form_data` decorator that buffers everything and
populates :attr:`request.form ` /
:attr:`request.files ` -- the convenient API
for normal-sized uploads.
class FormDataIter
------------------
.. class:: 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 :class:`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 :meth:`FileUpload.read` or :meth:`FileUpload.save`) before
the next ``async for`` iteration -- the underlying stream is
invalidated when iteration advances.
.. attribute:: buffer_size
:type: 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:: FileUpload(filename: str, content_type: str | None, read)
A single uploaded file. Instances are yielded by
:class:`FormDataIter` and collected into
:attr:`request.files ` by
:func:`with_form_data`. Applications do not normally construct
:class:`FileUpload` directly.
.. attribute:: filename
:type: str
The file's original name as the client sent it (untrusted -- do
not pass to :func:`open` without sanitizing).
.. attribute:: content_type
:type: str | None
The MIME type from the part's ``Content-Type`` header, or
``None`` if not provided.
.. attribute:: max_memory_size
:type: int
Class attribute. Threshold (in bytes) above which
:meth:`copy` switches from in-memory buffering to a temporary
file. Default 1024.
.. method:: read(n: int = -1)
:async:
Read up to *n* bytes from the upload stream. ``-1`` reads to end.
.. method:: save(path_or_file)
:async:
Save the upload to *path_or_file*, which can be a filesystem
path or an already-open file object.
.. method:: copy(max_memory_size: int | None = None)
:async:
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
:func:`with_form_data` decorator calls this automatically.
.. method:: close()
:async:
Release any temporary file created by :meth:`copy`. Called
automatically when the request finishes if the upload reached
:attr:`request.files` via :func:`with_form_data`.
Module-level decorators
-----------------------
.. function:: with_form_data(f)
Decorator that parses the multipart body up front and populates
:attr:`request.form ` and
:attr:`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 :meth:`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
:class:`FormDataIter` API; :func:`with_form_data` accumulates the
entire request in memory or on the filesystem before the handler
runs.