'Response file stream from S3 FastAPI
I am trying to return a response of a picture from S3. In StreamingResponse.stream_response
I see, that chunks are read from the stream and sent to the socket. But on the other side, nothing comes.
import uvicorn
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from aiobotocore.session import get_session
from aioboto3 import session
app = FastAPI()
@app.get("/")
async def main():
sess = get_session()
async with sess.create_client(
's3',
endpoint_url="",
aws_secret_access_key="",
aws_access_key_id=""
) as client:
result = await client.get_object(Bucket="", Key="")
return StreamingResponse(result["Body"], media_type="image/jpeg")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)```
Solution 1:[1]
just as an addition, if you use boto3 you don't need to write a wrapper around the StreamingResponse but can just pass the result of result["Body"].iter_chunks()
as content:
import boto3
import uvicorn
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
app = FastAPI()
s3 = boto3.client("s3")
@app.get("/")
async def main():
try:
result = s3.get_object(Bucket="my_awesome_bucket", Key="path/to/file")
return StreamingResponse(content=result["Body"].iter_chunks())
except Exception as e:
if hasattr(e, "message"):
raise HTTPException(
status_code=e.message["response"]["Error"]["Code"],
detail=e.message["response"]["Error"]["Message"],
)
else:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)
Did not try it with aioboto3 yet
Solution 2:[2]
On my side I manage to have it work by simply doing:
async def get_by_uuid(uuid: str):
async def s3_stream():
async with session.client("s3") as s3:
result_object = await s3.get_object(
Bucket=s3_bucket_name,
Key=f'{uuid}.json',
)
async for chunk in result_object['Body']:
yield chunk
return StreamingResponse(s3_stream())
Solution 3:[3]
After return from main, boto3 client context closing. And what I see in send it is only buffer data. It's sample work for me.
import typing
import uvicorn
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from aiobotocore.session import get_session
from starlette.background import BackgroundTask
app = FastAPI()
class S3Stream(StreamingResponse):
def __init__(
self,
content: typing.Any,
status_code: int = 200,
headers: dict = None,
media_type: str = None,
background: BackgroundTask = None,
Key: str = None, Bucket: str = None
) -> None:
super(S3Stream, self).__init__(content, status_code, headers, media_type, background)
self.Key = Key
self.Bucket = Bucket
async def stream_response(self, send) -> None:
await send(
{
"type": "http.response.start",
"status": self.status_code,
"headers": self.raw_headers,
}
)
sess = get_session()
async with sess.create_client(
's3',
endpoint_url="",
aws_secret_access_key="",
aws_access_key_id=""
) as client:
result = await client.get_object(Bucket=self.Bucket, Key=self.Key)
async for chunk in result["Body"]:
if not isinstance(chunk, bytes):
chunk = chunk.encode(self.charset)
await send({"type": "http.response.body", "body": chunk, "more_body": True})
await send({"type": "http.response.body", "body": b"", "more_body": False})
@app.get("/")
async def main():
return S3Stream(content=None, Bucket="test", Key="priroda_kartinki_foto_03.jpeg")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Peter Csala |
Solution 2 | |
Solution 3 | Sean McGrath |