pythonfile-uploadbytefastapihttp-upload

How do you save multitype/form data to a hard file in Python with FastAPI UploadFile?


https://fastapi.tiangolo.com/tutorial/request-files/

*Solved Below *

I've gotten an appropriately sized array of bytes and I'm not sure how to parse it correctly to save received form file data.

Almost working:

@app.post("/uploadfiles/")
async def create_files(files: List[bytes] = File(...)):
    out_file = open("files/1.txt", "w") # open for [w]riting as [b]inary
    out_file.write( str([(file) for file in files]))
    out_file.close()
    return {"file_sizes": [len(file) for file in files]}

The script results in a file that is no longer a readable .png. I assume I'm using the libraries incorrectly but I'm not sure which to start with: HTMLResponse, FastAPI, multipart or List maybe? Any ideas or docs on how to properly parse these bytes back together again?

Edit

Per a break and once over review of the FastAPI docs, I spotted that this particular array of byte data is multipart/form-data data (so maybe I'll find a python library for reading form data/images):

Here is a small example (first ~100chars) of the data that is generated:

[b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x03\xe8\x00\x00\x01\xf4\x08\x06\x00\x00\x00\xae(\x07-\x00\x00\x01\x86iCCPICC profile\x00\x00(\x91}\x91=H\

I did it!!!

The secret was pythons native byte recognition libraries and order of ops!!

@app.post("/uploadfiles/")
async def create_files(files: bytes = File(...)):
    out_file = open("files/1.jpg", "wb") # open for [w]riting as [b]inary
    out_file.write( bytes([(file) for file in files]))
    out_file.close()

Still work to do on the file naming system :) GL Everyone!

Edit 2

Since the below answer didn't function, I scripted a file name appender:

@app.post("/uploadfiles/")

async def create_files(files: bytes = File(...)):
    with open('C:/data/sample.txt', 'r', encoding='utf-8') as g:
        data=g.readlines()

    for line in data:
        counter = int(line)
        with open('C:/data/sample.txt', 'w') as f:
            counter = counter + 1
            f.write(str(counter))

    out_file = open("files/" + str(counter) + ".jpg", "wb") # open for [w]riting as [b]inary
    out_file.write( bytes([(file) for file in files]))
    out_file.close()

Thanks for the help everyone! Happy hacking :^)

Edit 3

I've been informed that the method of which I am posting could be incorrect practice in conjunction with FastAPI so to better understand this I am posting the relevant javascript that posts to the backend:

Here is the relevant code from my reactjs form post script:

 onSubmit = (e) => {
    e.preventDefault();

    const formData = new FormData();
if (this.state.images != null) {

    var form = document.getElementById("apiupform");
    document.getElementById("apiupform").hidden = true;
    Array.from(this.state.images).forEach((image) => {
      formData.append("files", image);
    });

    axios
      .post(`https://****.com/uploadfiles/`, formData, {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      })

Thank you everyone for the help with this work. I will update it as I find improvements :)


Solution

  • You can save the uploaded files this way,

    from fastapi import FastAPI, File, UploadFile
    
    app = FastAPI()
    
    
    @app.post("/upload-file/")
    async def create_upload_file(uploaded_file: UploadFile = File(...)):
        file_location = f"files/{uploaded_file.filename}"
        with open(file_location, "wb+") as file_object:
            file_object.write(uploaded_file.file.read())
        return {"info": f"file '{uploaded_file.filename}' saved at '{file_location}'"}
    

    OR,
    maybe a more efficient way using the shutil.copyfileobj(...) method as,

    import shutil
    from fastapi import FastAPI, File, UploadFile
    
    app = FastAPI()
    
    
    @app.post("/upload-file/")
    async def create_upload_file(uploaded_file: UploadFile = File(...)):
        file_location = f"files/{uploaded_file.filename}"
        with open(file_location, "wb+") as file_object:
            shutil.copyfileobj(uploaded_file.file, file_object)
        return {"info": f"file '{uploaded_file.filename}' saved at '{file_location}'"}

    Sample Swagger

    Swagger screenshot