Search code examples
pythonpython-imaging-libraryfastapi

How to save an uploaded image to FastAPI using Python Imaging Library (PIL)?


I am using image compression to reduce the image size. When submitting the post request, I am not getting any error, but can't figure out why the images do not get saved. Here is my code:

@app.post("/post_ads")
async def create_upload_files(title: str = Form(),body: str = Form(), 
    db: Session = Depends(get_db), files: list[UploadFile] = File(description="Multiple files as UploadFile")):
    for file in files:
        im = Image.open(file.file)
        im = im.convert("RGB")
        im_io = BytesIO()
        im = im.save(im_io, 'JPEG', quality=50) 

Solution

  • PIL.Image.open() takes as fp argumnet the following:

    fp – A filename (string), pathlib.Path object or a file object. The file object must implement file.read(), file.seek(), and file.tell() methods, and be opened in binary mode.

    Using a BytesIO stream, you would need to have something like the below (as shown in client side of this answer):

    Image.open(io.BytesIO(file.file.read()))
    

    However, you don't really have to use an in-memory bytes buffer, as you can get the actual file object using the .file attribute of UploadFile. As per the documentation:

    file: A SpooledTemporaryFile (a file-like object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.

    Example - Saving image to disk:

    # ...
    from fastapi import HTTPException
    from PIL import Image
    
    @app.post("/upload")
    def upload(file: UploadFile = File()):
        try:        
            im = Image.open(file.file)
            if im.mode in ("RGBA", "P"): 
                im = im.convert("RGB")
            im.save('out.jpg', 'JPEG', quality=50) 
        except Exception:
            raise HTTPException(status_code=500, detail='Something went wrong')
        finally:
            file.file.close()
            im.close()
    

    Example - Saving image to an in-memory bytes buffer (see this answer):

    # ...
    from fastapi import HTTPException
    from PIL import Image
    
    @app.post("/upload")
    def upload(file: UploadFile = File()):
        try:        
            im = Image.open(file.file)
            if im.mode in ("RGBA", "P"): 
                im = im.convert("RGB")
            buf = io.BytesIO()
            im.save(buf, 'JPEG', quality=50)
            # to get the entire bytes of the buffer use:
            contents = buf.getvalue()
            # or, to read from `buf` (which is a file-like object), call this first:
            buf.seek(0)  # to rewind the cursor to the start of the buffer
        except Exception:
            raise HTTPException(status_code=500, detail='Something went wrong')
        finally:
            file.file.close()
            buf.close()
            im.close()
    

    For more details and code examples on how to upload files/images using FastAPI, please have a look at this answer and this answer. Also, please have a look at this answer for more information on defining your endpoint with def or async def.