Downloading dynamically generated files from a Dash/Flask app
With Dash 1.20.0, you now have a dcc.Download
component for dynamic, user-based downloads. It doesn't require creating a custom button, uuid
and flask.send_file
.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import uuid
stylesheets = [
"https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css", # Bulma
]
# create app
app = dash.Dash(
__name__,
external_stylesheets=stylesheets
)
app.layout = html.Div(
className="section",
children=[
dcc.Textarea(
id="text-area",
className="textarea",
placeholder='Enter a value...',
style={'width': '300px'}
),
html.Button("Enter", id="btn_txt"),
dcc.Download(id="download-text")
]
)
@app.callback(
Output("download-text", "data"),
Input("btn_txt", "n_clicks"),
State("text-area", "value"),
prevent_initial_call=True,
)
def create_download_file(n_clicks, text):
filename = "file.txt"
# Alternatively:
# filename = f"{uuid.uuid1()}.txt"
return dict(content=text, filename=filename)
Since Dash is built upon Flask, flask is not able to locate the URI for the text file that is generated.
The solution is to add the flask routes to redirect to download the resources, There is a simple example in the official plotly dash repository, https://github.com/plotly/dash-recipes/blob/master/dash-download-file-link-server.py
The modified code below solves your problem
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import uuid
import os
import flask
stylesheets = [
"https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css", # Bulma
]
# create app
app = dash.Dash(
__name__,
external_stylesheets=stylesheets
)
app.layout = html.Div(
className="section",
children=[
dcc.Textarea(
id="text-area",
className="textarea",
placeholder='Enter a value...',
style={'width': '300px'}
),
html.Button(
id="enter-button",
className="button is-large is-outlined",
children=["enter"]
),
html.Div(
id="download-area",
className="block",
children=[]
)
]
)
def build_download_button(uri):
"""Generates a download button for the resource"""
button = html.Form(
action=uri,
method="get",
children=[
html.Button(
className="button",
type="submit",
children=[
"download"
]
)
]
)
return button
@app.callback(
Output("download-area", "children"),
[
Input("enter-button", "n_clicks")
],
[
State("text-area", "value")
]
)
def show_download_button(n_clicks, text):
if text == None:
return
# turn text area content into file
filename = f"{uuid.uuid1()}.txt"
path = f"downloadable/{filename}"
with open(path, "w") as file:
file.write(text)
uri = path
return [build_download_button(uri)]
@app.server.route('/downloadable/<path:path>')
def serve_static(path):
root_dir = os.getcwd()
return flask.send_from_directory(
os.path.join(root_dir, 'downloadable'), path
)
if __name__ == '__main__':
app.run_server(debug=True)
Alternatively, you can use the static
directory instead of the downloadable
directory, It will work as well.
More information on flask static directory: http://flask.pocoo.org/docs/1.0/tutorial/static/
Here is the snippet,
#your code
def show_download_button(n_clicks, text):
if text == None:
return
filename = f"{uuid.uuid1()}.txt"
path = f"static/{filename}" # =====> here change the name of the direcotry to point to the static directory
with open(path, "w") as file:
file.write(text)
uri = path
return [build_download_button(uri)]
#your code
Solution here:
import uuid
import dash
from dash.dependencies import Input, Output, State
import flask
from flask.helpers import send_file
import dash_core_components as dcc
import dash_html_components as html
stylesheets = [
"https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css", # Bulma
]
server = flask.Flask('app')
# create app
app = dash.Dash(
__name__,
external_stylesheets=stylesheets,
server=server # <-- do not forget this line
)
# (...) your code here
@server.route("/downloadable/<path>")
def download_file (path = None):
return send_file("downloadable/" + path, as_attachment=True)