Streaming Files

To send a file (or any large or generated body) without loading it into memory, return a StreamableFile. It sets Content-Type, Content-Disposition and Content-Length for you.

from austial import Controller, Get, StreamableFile
 
 
@Controller("reports")
class ReportsController:
    @Get("annual")
    async def annual(self) -> StreamableFile:
        return StreamableFile(
            "reports/2026.pdf",                                    # a path
            type="application/pdf",
            disposition='attachment; filename="annual-2026.pdf"',
        )

The source can be:

SourceStreams
str / os.PathLikethe file at that path, in 64 KiB chunks
bytesthe buffer
an open binary fileread in chunks, then closed
an iterable or async iterable of byteseach chunk as it’s produced
@Get("export")
async def export(self) -> StreamableFile:
    async def rows():
        async for row in self.repo.stream_all():
            yield (row.to_csv() + "\n").encode()
 
    return StreamableFile(rows(), type="text/csv", disposition='attachment; filename="export.csv"')

length= sets Content-Length explicitly; for paths, files and bytes it is worked out for you, and omitted for generators (the response is then sent chunked).

The response status is the route’s usual one (200, or 201 for POST); change it with @HttpCode. Headers set with @Header are added too.

For a plain download straight from disk you can also return Starlette’s FileResponse, which adds range-request support. StreamableFile is the better fit when the bytes come from a generator, a database or object storage.