Skip to content
Advertisement

FastAPI FileResponse cannot find file in TempDirectory

I’m trying to write an endpoint that just accepts an image and attempts to convert it into another format, by running a command on the system. Then I return the converted file. It’s slow and oh-so-simple, and I don’t have to store files anywhere, except temporarily.

I’d like all the file-writing to happen in a temporary directory, so it gets cleaned up.

The route works fine if the output file is not in the temporary directory. But if I try to put the output file in the temporary directory, the FileResponse can’t find it, and requests fail.

RuntimeError: File at path /tmp/tmpp5x_p4n9/out.jpg does not exist.

Is there something going on related to the asynchronous nature of FastApi that FileResponse can’t wait for the subprocess to create the file its making? Can I make it wait? (removing async from the route does not help).

@app.post("/heic")
async def heic(img: UploadFile):
    with TemporaryDirectory() as dir:
        inname = os.path.join(dir, "img.heic")
        f = open(inname,"wb")
        f.write(img.file.read())
        f.flush()

        # setting outname in the temp dir fails!
        # outname = os.path.join(dir, 'out.jpg')

        outname = os.path.join('out.jpg')

        cmd = f"oiiotool {f.name} -o {outname}"
        process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
        process.wait()
        return FileResponse(outname, headers={'Content-Disposition':'attachment; filename=response.csv'})

Thank you for any insights!

Advertisement

Answer

According to the documentation of TemporaryDirectory()

This class securely creates a temporary directory using the same rules as mkdtemp(). The resulting object can be used as a context manager (see Examples). On completion of the context or destruction of the temporary directory object, the newly created temporary directory and all its contents are removed from the filesystem.

It seems that the directory and contents are already being released before the FastAPI request is returned. However you can use Dependency injection in FastAPI, so why no inject a temporary directory?

First define the dependency:

async def get_temp_dir():
    dir = TemporaryDirectory()
    try:
        yield dir.name
    finally:
        del dir

And add the dependency to your endpoint:

@app.post("/heic")
async def heic(imgfile: UploadFile = File(...), dir=Depends(get_temp_dir)):
    inname = os.path.join(dir, "img.heic")
    f = open(inname,"wb")
    f.write(imgfile.file.read())
    f.flush()

    outname = os.path.join(dir, 'out.jpg')
    cmd = f"oiiotool {f.name} -o {outname}"
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    process.wait()
    return FileResponse(inname, headers={'Content-Disposition':'attachment; filename=response.csv'})

I’ve tested it with returning the incoming file and that works.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement