How to delete the file after a `return FileResponse(file_path)`
Question:
I’m using FastAPI to receive an image, process it and then return the image as a FileResponse.
But the returned file is a temporary one that need to be deleted after the endpoint return it.
@app.post("/send")
async def send(imagem_base64: str = Form(...)):
# Convert to a Pillow image
image = base64_to_image(imagem_base64)
temp_file = tempfile.mkstemp(suffix = '.jpeg')
image.save(temp_file, dpi=(600, 600), format='JPEG', subsampling=0, quality=85)
return FileResponse(temp_file)
# I need to remove my file after return it
os.remove(temp_file)
How can I delete the file after return it ?
Answers:
You can delete a file in a background task, as it will run after the response is sent.
import os
import tempfile
from fastapi import FastAPI
from fastapi.responses import FileResponse
from starlette.background import BackgroundTasks
app = FastAPI()
def remove_file(path: str) -> None:
os.unlink(path)
@app.post("/send")
async def send(background_tasks: BackgroundTasks):
fd, path = tempfile.mkstemp(suffix='.txt')
with os.fdopen(fd, 'w') as f:
f.write('TESTn')
background_tasks.add_task(remove_file, path)
return FileResponse(path)
Another approach is to use dependency with yield. The finally
block code will be executed after the response is sent and even after all background tasks have been completed.
import os
import tempfile
from fastapi import FastAPI, Depends
from fastapi.responses import FileResponse
app = FastAPI()
def create_temp_file():
fd, path = tempfile.mkstemp(suffix='.txt')
with os.fdopen(fd, 'w') as f:
f.write('TESTn')
try:
yield path
finally:
os.unlink(path)
@app.post("/send")
async def send(file_path=Depends(create_temp_file)):
return FileResponse(file_path)
Note: mkstemp() returns a tuple with a file descriptor and a path.
Returning a StreamingResponse
would be a better choice in your case and would be more memory efficient since a file operation will block the entire execution of your event loop.
Since you are receiving the data as b64encoded
. You can read it as a byte and return a StreamingResponse
from that.
from fastapi.responses import StreamingResponse
from io import BytesIO
@app.post("/send")
async def send(imagem_base64: str = Form(...)):
in_memory_file = BytesIO()
image = base64_to_image(imagem_base64)
image.save(in_memory_file, dpi=(600, 600), format='JPEG', subsampling=0, quality=85)
in_memory_file.seek(0)
return StreamingResponse(in_memory_file.read(), media_type="image/jpeg")
You can pass the cleanup task as a parameter of FileResponse
:
from starlette.background import BackgroundTask
# ...
def cleanup():
os.remove(temp_file)
return FileResponse(
temp_file,
background=BackgroundTask(cleanup),
)
UPDATE 12-08-2022
If someone is generating the filename dynamically, then one may pass the parameters to the background task, e.g., as follows
return FileResponse(
temp_file,
background=BackgroundTask(cleanup, file_path),
)
The cleanup
function then needs to be adapted to accept a parameter, which will be the filename, and call the os.remove
function with the filename as parameter instead of the global variable
It is recommended to send FileResponse
with a background task attached that deletes the file or folder.
click here for more info
The background task will run after the response has been served, so it can safely delete the file/folder.
# ... other important imports
from starlette.background import BackgroundTasks
@app.post("/send")
async def send(imagem_base64: str = Form(...), bg_tasks: BackgroundTasks):
# Convert to a Pillow image
image = base64_to_image(imagem_base64)
temp_file = tempfile.mkstemp(suffix = '.jpeg')
image.save(temp_file, dpi=(600, 600), format='JPEG', subsampling=0, quality=85)
bg_tasks.add_task(os.remove, temp_file)
return FileResponse(
temp_file,
background=bg_tasks
)
I’m using FastAPI to receive an image, process it and then return the image as a FileResponse.
But the returned file is a temporary one that need to be deleted after the endpoint return it.
@app.post("/send")
async def send(imagem_base64: str = Form(...)):
# Convert to a Pillow image
image = base64_to_image(imagem_base64)
temp_file = tempfile.mkstemp(suffix = '.jpeg')
image.save(temp_file, dpi=(600, 600), format='JPEG', subsampling=0, quality=85)
return FileResponse(temp_file)
# I need to remove my file after return it
os.remove(temp_file)
How can I delete the file after return it ?
You can delete a file in a background task, as it will run after the response is sent.
import os
import tempfile
from fastapi import FastAPI
from fastapi.responses import FileResponse
from starlette.background import BackgroundTasks
app = FastAPI()
def remove_file(path: str) -> None:
os.unlink(path)
@app.post("/send")
async def send(background_tasks: BackgroundTasks):
fd, path = tempfile.mkstemp(suffix='.txt')
with os.fdopen(fd, 'w') as f:
f.write('TESTn')
background_tasks.add_task(remove_file, path)
return FileResponse(path)
Another approach is to use dependency with yield. The finally
block code will be executed after the response is sent and even after all background tasks have been completed.
import os
import tempfile
from fastapi import FastAPI, Depends
from fastapi.responses import FileResponse
app = FastAPI()
def create_temp_file():
fd, path = tempfile.mkstemp(suffix='.txt')
with os.fdopen(fd, 'w') as f:
f.write('TESTn')
try:
yield path
finally:
os.unlink(path)
@app.post("/send")
async def send(file_path=Depends(create_temp_file)):
return FileResponse(file_path)
Note: mkstemp() returns a tuple with a file descriptor and a path.
Returning a StreamingResponse
would be a better choice in your case and would be more memory efficient since a file operation will block the entire execution of your event loop.
Since you are receiving the data as b64encoded
. You can read it as a byte and return a StreamingResponse
from that.
from fastapi.responses import StreamingResponse
from io import BytesIO
@app.post("/send")
async def send(imagem_base64: str = Form(...)):
in_memory_file = BytesIO()
image = base64_to_image(imagem_base64)
image.save(in_memory_file, dpi=(600, 600), format='JPEG', subsampling=0, quality=85)
in_memory_file.seek(0)
return StreamingResponse(in_memory_file.read(), media_type="image/jpeg")
You can pass the cleanup task as a parameter of FileResponse
:
from starlette.background import BackgroundTask
# ...
def cleanup():
os.remove(temp_file)
return FileResponse(
temp_file,
background=BackgroundTask(cleanup),
)
UPDATE 12-08-2022
If someone is generating the filename dynamically, then one may pass the parameters to the background task, e.g., as follows
return FileResponse(
temp_file,
background=BackgroundTask(cleanup, file_path),
)
The cleanup
function then needs to be adapted to accept a parameter, which will be the filename, and call the os.remove
function with the filename as parameter instead of the global variable
It is recommended to send FileResponse
with a background task attached that deletes the file or folder.
click here for more info
The background task will run after the response has been served, so it can safely delete the file/folder.
# ... other important imports
from starlette.background import BackgroundTasks
@app.post("/send")
async def send(imagem_base64: str = Form(...), bg_tasks: BackgroundTasks):
# Convert to a Pillow image
image = base64_to_image(imagem_base64)
temp_file = tempfile.mkstemp(suffix = '.jpeg')
image.save(temp_file, dpi=(600, 600), format='JPEG', subsampling=0, quality=85)
bg_tasks.add_task(os.remove, temp_file)
return FileResponse(
temp_file,
background=bg_tasks
)