pythonfastapimime-types

How can I manually create an UploadFile in FastAPI?


Background

I am trying to write tests for my FastAPI application. I have an object, ImageRecord, which uploads an image to S3. Here's the method signature:

async def upload_image(file: UploadFile, s3_client=None) -> 'ImageRecord':

This method is called by the API logic (i.e. the actual @app.post function) to upload an object. I want to test this method by uploading an image file to moto - so a mocked S3 bucket. This method, in ImageRecord, expects an UploadFile because it is called from the function - so I can't just pass in the bytes. Additionally, I call file.content_type in my code to get the content type.

The Issue

I can't create an instance of my actual API and call it because, in order to inject the mocked S3 object, I have to call the Python method from ImageRecord directly - not via a HTTP request. However, this means that I need to create the UploadFile object manually. Here is how I am creating it:

contents = open("resc/valid_image.jpg", "rb").read()
upload_file = UploadFile(filename="test_image.jpg", file=io.BytesIO(contents))

However, when I upload it to ImageRecord, I get the error:

Invalid type for parameter ContentType, value: None, type: <class 'NoneType'>, valid types: <class 'str'>

Because upload_file.content_type is None. In real API code, when FastAPI creates the file, the source code says:

The content type of the request, from the headers

But my manually created file has no headers, and does not accept a content_type argument in init.

TL;DR

A manually created FastAPI.UploadFile has no content_type, because that must be inferred from the headers. Is there any way to create an UploadFile instance with a content_type for tests? Or do I need to refactor my code so that it infers the content type from the contents, and skips UploadFile.content_type alltogether?


Solution

  • I had the same issue and managed to resolve it by setting the headers and specifying the

    content-type

    in the headers object, before passing it to UploadFile:

    headers = {
                'content-type': mime_type
              }
    media_file = UploadFile(file=file_content, filename=file_name, headers=headers)
    

    Now calling media_file.content_type correctly returns the content type for the file.