I have a FastAPI app that gets an image from an API. This image is stored in a variable with type: bytes.
I want to display the image in HTML/Jinja2 template (without having to download it). I followed many tutorials but couldn't find the solution.
Here is what I came up with so far:
#app.get("/{id}")
async def root(request: Request, id: str):
picture = await get_online_person()
data = base64.b64encode(picture) # convert to base64 as bytes
data = data.decode() # convert bytes to string
# str_equivalent_image = base64.b64encode(img_buffer.getvalue()).decode()
img_tag = '<img src="data:image/png;base64,{}">'.format(data)
return templates.TemplateResponse(
"index.html", {"request": request, "img": img_tag}
)
All I get in the HTML is this: (as text on the page, not from source code)
<img src="data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAQECAQEBAgICAgICAgICAQICAgICAgICAgL/2wBDAQEBAQEBAQEBAQECAQEBAgICAgI
CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgL/wgARCAQABAADASIAA
hEBAxEB/8QAHgAAAQUBAQEBAQAAAAAAAAAABQIDBAYHAQgACQr/xAAcAQACAwEBAQEAAAAAAAAAAAACAwABBAUGBwj/2gAMAwEAAhADEAAAAfEpwSR+a+9IPR3c7347iwscmWyYchEIJjn+MbJj/c4FFbbb9J5....................
Note: For people who are marking my question to a duplicate talking about urllib, I cannot use urllib because the image I'm getting is from ana API, and using their direct url will result in a 403 Forbidden, so I should use their python API to get the image.
On server side—as shown in the last section of this answer—you should return only the base64-encoded string in the context of the TemplateResponse (without using the <img> tag, as shown in your question):
# ...
base64_encoded_image = base64.b64encode(image_bytes).decode("utf-8")
return templates.TemplateResponse("index.html", {"request": request, "myImage": base64_encoded_image})
On client side, you could display the image as follows:
<img src="data:image/jpeg;base64,{{ myImage | safe }}">
Alternative approaches can be found here.
I am creating a web application that receives some text, converts the text into speech, and returns an mp3 file, which is saved to a temporary directory.
I want to be able to download the file from the html page (i.e., the frontend), but I don't know how to do that properly.
I know with Flask you can do this:
from app import app
from flask import Flask, send_file, render_template
#app.route('/')
def upload_form():
return render_template('download.html')
#app.route('/download')
def download_file():
path = "html2pdf.pdf"
return send_file(path, as_attachment=True)
if __name__ == "__main__":
app.run()
HTML Example:
<!doctype html>
<title>Python Flask File Download Example</title>
<h2>Download a file</h2>
<p>Download</p>
So how do I replicate this with FastAPI?
FastAPI Code:
from fastapi import FastAPI, File, Request, Response, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from gtts import gTTS
templates = Jinja2Templates(directory="templates")
def text_to_speech(language:str, text: str) -> str:
tts = gTTS(text=text, lang=language, slow=False)
tts.save("./temp/welcome.mp3")
#os.system("mpg321 /temp/welcome.mp3")
return "Text to speech conversion successful"
#app.get("/")
def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
#app.get("/text2speech")
async def home(request: Request):
if request.method == "POST":
form = await request.form()
if form["message"] and form["language"]:
language = form["language"]
text = form["message"]
translate = text_to_speech(language, text)
path = './temp/welcome.mp3'
value = FileResponse("./temp/welcome.mp3", media_type="audio/mp3")
return value
# return templates.TemplateResponse(
# "index.html",
# {"request": request, "message": text, "language": language, "download": value},
# )
Sample HTML File:
<!doctype html>
<title>Download MP3 File</title>
<h2>Download a file</h2>
<p>Download</p>
Use the Form keyword to define Form-data in your endpoint, and more specifically, use Form(...) to make a parameter required, instead of using await request.form() and manually checking if the user submitted the required parameters. After processing the received data and generating the audio file, you can use FileResponse to return the file to the user. Note: use the headers argument in FileResponse to set the Content-Disposition header using the attachment parameter—as described in this answer—to have the file downloaded to your device. Failing to set the headers, or using the inline parameter isntead, would lead to 405 Method Not Allowed error, as the browser attempts to access the file using a GET request (however, only POST requests are allowed to the /text2speech endpoint). Have a look at Option 1 in the examples below.
If you wanted the /text2speech endpoint supporting both GET and POST requests (as shown in your question), you could either use #app.api_route("/text2speech", methods=["GET", "POST"]) and use request.method to check which one has been called, or define two different endpoints e.g., #app.post('/text2speech') and #app.get('/text2speech'). However, you don't necessarily need to do that in this case. Additionally, you have added a Download hyperlink to your template for the user to download the file. However, you haven't provided any information as to how you expect this to work. This wouldn't work in a scenario where you don't have static files, but dynamically generated audio files (as in your case), as well as multiple users accessing the API at the same time; unless, for example, you generated random UUIDs for the filenames and saved the files in a StaticFiles directory—or added that unique identifier as a query/path parameter (you could also use cookies instead, see here and here) to the URL in order to identify the file to be downloaded—and sent the URL back to the user. In that case, you would need a Javascript interface/library, such as Fetch API, to make an asynchronous HTTP request—as described in this answer—in order to get the URL to the file and display it in the Download hyperlink. Have a look at Option 2 below. Note: The example in Option 2 uses a simple dict to map the filepaths to UUIDs, for demo purposes. In a real-world scenario, where multiple users access the API and several workers might be used, you may consider using a database storage, or Key-Value stores (Caches), as described here and here. You would also need to have a mechanism for deleting the files from the database and disk, once they have been downloaded, as well as make sure that users do not have unauthorised access to other users' audio files.
Option 1
app.py
from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import FileResponse
import os
app = FastAPI()
templates = Jinja2Templates(directory="templates")
#app.get('/')
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
#app.post('/text2speech')
def convert(request: Request, message: str = Form(...), language: str = Form(...)):
# do some processing here
filepath = './temp/welcome.mp3'
filename = os.path.basename(filepath)
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return FileResponse(filepath, headers=headers, media_type="audio/mp3")
An alternative to the above would be to read the file data inside your endpoint—or, in case the data were fully loaded into memory beforehand, such as here, here and here—and return a custom Response directly, as shown below:
from fastapi import Response
#app.post('/text2speech')
...
with open(filepath, "rb") as f:
contents = f.read() # file contents could be already fully loaded into RAM
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return Response(contents, headers=headers, media_type='audio/mp3')
In case you had to return a file that is too large to fit into memory—e.g., if you have 8GB of RAM, you can’t load a 50GB file—you could use StreamingResponse, which would load the file into memory in chunks and process the data one chunk at a time (If you find yield from f being rather slow, please have a look at this answer for faster alternatives):
from fastapi.responses import StreamingResponse
#app.post('/text2speech')
...
def iterfile():
with open(filepath, "rb") as f:
yield from f
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return StreamingResponse(iterfile(), headers=headers, media_type="audio/mp3")
templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>Convert Text to Speech</title>
</head>
<body>
<form method="post" action="http://127.0.0.1:8000/text2speech">
message : <input type="text" name="message" value="This is a sample message"><br>
language : <input type="text" name="language" value="en"><br>
<input type="submit" value="submit">
</form>
</body>
</html>
Using JavaScript to download the file
In case you used a JavaScript interface, such as Fetch API, in the frontend to issue the file-download request—instead of using an HTML <form>, as demonstrated above—please have a look at this answer, as well as this answer and this answer on how to download the file in the frontend through JavaScript.
Option 2
app.py
from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import FileResponse
import uuid
import os
app = FastAPI()
templates = Jinja2Templates(directory="templates")
files = {}
#app.get('/')
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
#app.get('/download')
def download_file(request: Request, fileId: str):
filepath = files.get(fileId)
if filepath:
filename = os.path.basename(filepath)
headers = {'Content-Disposition': f'attachment; filename="{filename}"'}
return FileResponse(filepath, headers=headers, media_type='audio/mp3')
#app.post('/text2speech')
def convert(request: Request, message: str = Form(...), language: str = Form(...)):
# do some processing here
filepath = './temp/welcome.mp3'
file_id = str(uuid.uuid4())
files[file_id] = filepath
file_url = f'/download?fileId={file_id}'
return {"fileURL": file_url}
templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>Convert Text to Speech</title>
</head>
<body>
<form method="post" id="myForm">
message : <input type="text" name="message" value="This is a sample message"><br>
language : <input type="text" name="language" value="en"><br>
<input type="button" value="Submit" onclick="submitForm()">
</form>
<a id="downloadLink" href=""></a>
<script type="text/javascript">
function submitForm() {
var formElement = document.getElementById('myForm');
var data = new FormData(formElement);
fetch('/text2speech', {
method: 'POST',
body: data,
})
.then(response => response.json())
.then(data => {
document.getElementById("downloadLink").href = data.fileURL;
document.getElementById("downloadLink").innerHTML = "Download";
})
.catch(error => {
console.error(error);
});
}
</script>
</body>
</html>
Removing a File after it's been downloaded
For Option 1 above, to remove a file after it has been downloaded by the user, you can simply define a BackgroundTask to be run after returning the response. For example:
from fastapi import BackgroundTasks
import os
#app.post('/text2speech')
def convert(request: Request, background_tasks: BackgroundTasks, ...):
filepath = 'welcome.mp3'
# ...
background_tasks.add_task(os.remove, path=filepath)
return FileResponse(filepath, headers=headers, media_type="audio/mp3")
For Option 2, however, you would have to make sure to delete the key (i.e., file_id) pointing to the given filepath from the cache as well. Hence, you should create a background task function, as shown below:
from fastapi import BackgroundTasks
import os
files = {}
def remove_file(filepath, fileId):
os.remove(filepath)
del files[fileId]
#app.get('/download')
def download_file(request: Request, fileId: str, background_tasks: BackgroundTasks):
filepath = files.get(fileId)
if filepath:
# ...
background_tasks.add_task(remove_file, filepath=filepath, fileId=fileId)
return FileResponse(filepath, headers=headers, media_type='audio/mp3')
More details and examples on background tasks can be found here, as well as here.
If you know how to send a file to FastAPI server and access it in /predict endpoint for prediction using my models please help me out.
I have deployed the model using /predict endpoint and done uvicorn main:app and it's deployed but the only thing is input that is a document is in my local pc so how can I sent it to FastAPI?
I have went through the documentation of FastAPI and I have found this example code there, but the challenge is that this code creates an UI for uploading file which is not what I"m looking for.
from typing import Optional
from fastapi import FastAPI
from fastapi import FastAPI, File, UploadFile
from pydantic import BaseModel
from typing import List
from fastapi.responses import HTMLResponse
app = FastAPI()
class User(BaseModel):
user_name: dict
#app.post("/files/")
async def create_files(files: List[bytes] = File(...)):
return {"file_sizes": [len(file) for file in files]}
#app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile] = File(...)):
return {"filenames": [file.filename for file in files]}
#app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
FASTAPI CODE
This will be your endpoint.
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
#app.post("/file")
async def upload_file(file: UploadFile = File(...)):
# Do here your stuff with the file
return {"filename": file.filename}
JAVASCRIPT CODE
This is your javascript code (assuming you are using javascript for uploading the file)
form = new FormData();
form.append("file", myFile);
let response = await fetch('/file', {
method: 'POST',
body: form
});
let result = await response.json();
EDIT: Python file upload
I'm using httpx, but technically it should be fully compatible with requests.
import httpx
# Create a dict with a key that has the same name as your file parameter and the file in binary form (the "b" in "rb")
f = {'file': open('foo.png', 'rb')}
r = httpx.post("your_url/file", files=f)
You can see more configurations/examples on the official documentation of httpx at https://www.python-httpx.org/quickstart/#sending-multipart-file-uploads.
Again, I did not test the code, since I'm tight with time at the moment.
END OF EDIT
Be aware that the parameter name of the file, MUST match the one used for sending the file.
In case, there is also another answer from me on how to test it with POSTMAN. See How to send file to fastapi endpoint using postman
NOTE
I did not test the code as I don't have time right now. In case, there is also a link with a previous answer of mine that works (unless FASTAPI introduced breaking changes).
I have a webpage where the user uploads an image and my python code will make some adjustments to it and do some analysis. I want the newly generated image to be displayed to the user on the webpage as soon as it's generated, and then continue doing the analysis that needs to be done, and then update the webpage with that information. However, I am not sure how to communicate from flask to the webpage halfway through the function (once the new image is generated) that the website can display the newly generated image, as using render_template can only be done at the end of the function.
My python (flask) code is as follows:
#app.route('/uploaded', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
filename = secure_filename(file.filename)
f = request.files['file']
f.save(secure_filename(f.filename))
image = cv2.imread(filename)
im = Image.fromarray(image)
# make some adjustments to the image (not shown here) and then save it...
im.save(os.path.join(app.config['UPLOAD_FOLDER'], 'your_file.jpg'))
# after the new image is generated, display it on the website at {{ place_for_generated_image }}
# do some more analysis, then finally:
return render_template('index.html', analysis = analysis)
HTML is straightforward:
<form action = "http://localhost/uploaded" method = "POST"
enctype = "multipart/form-data">
<input type = "file" name = "file" class="form-control-file">
<input type = "submit" class="btn btn-info" value="Upload Image" button id="uploadfile" onclick="uploadfile()">
</form>
{{ place_for_generated_image }}
{{ analysis }}
You need to use multiple ajax calls to achieve that. Something like the below:
First make a route to handle the image upload
#app.route('/uploaded', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
filename = secure_filename(file.filename)
f = request.files['file']
f.save(secure_filename(f.filename))
image = cv2.imread(filename)
im = Image.fromarray(image)
# make some adjustments to the image (not shown here) and then save it...
im.save(os.path.join(app.config['UPLOAD_FOLDER'], 'your_file.jpg'))
# Here you need to convert your image to a base64 string and return that string to your ajax call
# do some more analysis, then finally:
return 'YOUR BASE64 ENCODED IMAGE STRING'
This route will handle your analysis. Your second nested ajax call will communicate with this route
from flask import Response
#app.route('/analyze', methods=['GET', 'POST'])
def analyze():
# run some analysis
return Response(status=200, response="Anlysis Done")
This what your javascript code should look like. You can place it in a script tag in your template. If you place it in a separate JS file, make sure to look at my comment for the url_for in the second ajax call
$('form').submit(function(e){
e.preventDefault();
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: $(this).serialize()
}).done(function(res){
// Here you write code that will display the returned base64 image
// from the upload_file route Just update your img tag src to the
// base64 string you received as the response
// SECOND AJAX CALL TO RUN THE ANALYTICS
$.ajax({
url: "{{url_for('analysis')}}", // if this JS code in a separate JS file you will have to declare a URL variable in a script tag in your template as {{url_for}} is only accessible from your template
type: 'POST',
data: 'any data you want to send can be in JSON format'
}).done(function(res){
// analysis is done
})
})
})
I am trying to upload a file to s3 from a form via ajax. I am using fineuploader http://fineuploader.com/ on the client side and webapp2 on the server side. it sends the parameter as qqfile in the request and I can see the image data in the request headers but I have no idea how to get it back in browsers that do not use the multipart encoding.
This is how I was doing it in the standard html form post with multipart.
image = self.request.POST["image"]
this gives me the image name and the image file
currently I have only been able to get the image filename back not the data with
image = self.request.get_all('image')
[u'image_name.png']
when using the POST I get a warning about the content headers being application/octet-stream
<NoVars: Not an HTML form submission (Content-Type: application/octet-stream)>
How do I implement a BlobStoreHandler in webapp2 outside of GAE?
Your question code is not very clear to me. But you can use an example.
Have a look at this article from Nick Johnson. He implements a dropbox service using app engine blobstore and Plupload in the client : http://blog.notdot.net/2010/04/Implementing-a-dropbox-service-with-the-Blobstore-API-part-2
I ended up using fineuploader http://fineuploader.com/ which sends a multipart encoded form to my handler endpoint.
inside the handler I could simply just reference the POST and then read the FieldStorage Object into a cStringIO object.
image = self.request.POST["qqfile"]
imgObj = cStringIO.StringIO(image.file.read())
# Connect to S3...
# create s3 Key
key = bucket.new_key("%s" % uuid.uuid4());
# guess mimetype for headers
file_mime_type = mimetypes.guess_type(image.filename)
if file_mime_type[0]:
file_headers = {"Content-Type": "%s" % file_mime_type[0]}
else:
file_headers = None
key.set_contents_from_string(imgObj.getvalue(), headers=file_headers)
key_str = key.key
#return JSON response with key and append to s3 url on front end.
note: qqfile is the parameter fineuploader uses.
I am faking the progress but that is ok for my use case no need for BlobStoreUploadHandler.