Skip to content


You can read the request body directly into a file or bytes. This will read the data from the top level request body, and can only support 1 file. To receive multiple files, see the multipart/form-data documentation.

from xpresso import App, FromFile, Path, UploadFile

async def count_bytes_in_file(file: FromFile[UploadFile]) -> int:
    return len(await

app = App(routes=[Path(path="/count-bytes", put=count_bytes_in_file)])


UploadFile is a class provided by Starlette that buffers the data in memory and overflows to disk if the data is larger than a predefined threshold. This prevents a large file from exhausting your hardware's memory, but does use disk space and CPU cycles for buffering.

Implementation detail

xpresso.UploadFile is just a thin wrapper around starlette.datastructures.UploadFile, but you must use xpresso.UploadFile instead of starlette.datastructures.UploadFile directly, otherwise Xpresso won't know how to build the argument.

As bytes

Xpresso can read the entire file into memory if you'd like:

from xpresso import App, FromFile, Path

async def count_bytes_in_file(data: FromFile[bytes]) -> int:
    return len(data)

app = App(routes=[Path(path="/count-bytes", put=count_bytes_in_file)])

This can be convenient if you know the files are not large.

As a stream

If you want to read the bytes without buffering to disk or memory, use AsyncIterator[bytes] as the type:

from typing import AsyncIterator

from xpresso import App, FromFile, Path

async def count_bytes_in_file(
    data: FromFile[AsyncIterator[bytes]],
) -> int:
    size = 0
    async for chunk in data:
        size += len(chunk)
    return size

app = App(routes=[Path(path="/count-bytes", put=count_bytes_in_file)])

Setting the expected content-type

You can set the media type via the media_type parameter to File() and enforce it via the enforce_media_type parameter:

from xpresso import App, File, Path, UploadFile
from xpresso.typing import Annotated

async def count_image_bytes(
    file: Annotated[
        File(media_type="image/*", enforce_media_type=True),
) -> int:
    return len(await

app = App(routes=[Path(path="/count-bytes", put=count_image_bytes)])

Media types can be a media type (e.g. image/png) or a media type range (e.g. image/*).

If you do not explicitly set the media type, all media types are accepted. Once you set an explicit media type, that media type in the requests' Content-Type header will be validated on incoming requests, but this behavior can be disabled via the enforce_media_type parameter to File().