Skip to main content

Advanced File Handling

To handle file uploads efficiently in this project, use the UploadFile class. Unlike using bytes, which loads the entire file into memory, UploadFile uses a "spooled" file that stays in memory until a certain size limit and then switches to disk, making it suitable for large files.

Efficient File Processing with UploadFile

To accept a file upload and access its metadata, declare a parameter with the UploadFile type.

from fastapi import FastAPI, UploadFile

app = FastAPI()

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {
"filename": file.filename,
"content_type": file.content_type,
"size": file.size
}

Key Attributes

The UploadFile object provides several useful attributes:

  • filename: The original name of the uploaded file (e.g., my_image.png).
  • content_type: The MIME type (e.g., image/jpeg).
  • file: A BinaryIO object (a standard Python file-like object).
  • headers: A Headers object containing the request headers.
  • size: The size of the file in bytes.

Async Methods

UploadFile provides async methods that run in a threadpool to prevent blocking the event loop:

  • await file.read(size): Reads a number of bytes from the file.
  • await file.write(data): Writes bytes to the file.
  • await file.seek(offset): Moves the file cursor to a specific position.
  • await file.close(): Closes the file.

Adding Metadata and Validation

Use the File class with Annotated to add OpenAPI metadata or validation constraints to your file parameters.

from typing import Annotated
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/upload-with-metadata/")
async def upload_file(
file: Annotated[UploadFile, File(description="A profile picture for the user")]
):
return {"filename": file.filename}

Handling Multiple Files

To accept multiple files in a single request, annotate a parameter with list[UploadFile].

from typing import Annotated
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/uploadfiles/")
async def create_upload_files(
files: Annotated[
list[UploadFile], File(description="Multiple files as UploadFile")
],
):
return {"filenames": [file.filename for file in files]}

Combining Files and Form Fields

Because file uploads are sent as multipart/form-data, you can combine File and Form parameters in the same request.

from typing import Annotated
from fastapi import FastAPI, File, Form, UploadFile

app = FastAPI()

@app.post("/files/")
async def create_file(
file: Annotated[UploadFile, File()],
token: Annotated[str, Form()],
):
return {
"token": token,
"file_content_type": file.content_type,
}

Advanced: Blocking vs Async Access

If you are using a standard def function instead of async def, or if you need to pass the file to a library that does not support async, access the raw file object directly via file.file.

from fastapi import FastAPI, UploadFile

app = FastAPI()

@app.post("/sync-upload/")
def sync_upload(file: UploadFile):
# Use the blocking standard Python file object
content = file.file.read()
return {"size": len(content)}

Troubleshooting and Gotchas

Memory Usage

Avoid using bytes for large file uploads (e.g., file: bytes = File()). This forces FastAPI to read the entire file into memory at once. Always prefer UploadFile for better performance and lower memory overhead.

JSON and File Conflicts

You cannot declare Body (JSON) parameters alongside File or Form parameters in the same path operation. A single HTTP request body can only have one encoding: either application/json or multipart/form-data. If you need to send data alongside a file, use Form fields.

Automatic Closing

FastAPI automatically closes UploadFile objects after the request is finished. You generally do not need to call await file.close() manually unless you are performing complex manual file manipulations within the endpoint. This behavior is verified in tests/test_datastructures.py where the file.closed attribute is checked after the request lifecycle.

Seeking After Reading

If you read the file content (e.g., await file.read()), the file cursor moves to the end. If you need to read it again or pass it to another function, you must reset the cursor:

await file.seek(0)