Clean way to download table data

I’m trying to find a clean way to add download links to tables I’ve generated. Here’s some sample code (using this post as a baseline example):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pandas as pd
import flask
from StringIO import StringIO

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt
from dash.dependencies import Input, Output


DF = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
DFS = {'2002': DF[DF['year'] == 2002],
       '2007': DF[DF['year'] == 2007],
       }


def tabbed_tables():
    table1 = dt.DataTable(rows=DFS['2002'].to_dict('records'),
                          columns=DFS['2002'].columns
                          )
    button = html.Button('Download', id='download-button-0', value='table-0')
    link1 = dcc.Link(button, href='/', id='download-link-0')

    table2 = dt.DataTable(rows=DFS['2007'].to_dict('records'),
                          columns=DFS['2007'].columns
                          )
    button = html.Button('Download', id='download-button-1', value='table-1')
    link2 = dcc.Link(button, href='/', id='download-link-1')

    tab1 = dcc.Tab(label="2002", children=[table1, link1], id='table-0-tab')
    tab2 = dcc.Tab(label="2007", children=[table2, link2], id='table-1-tab')

    return dcc.Tabs(id="tabs", children=[tab1, tab2],
                    style={'fontFamily': 'system-ui'},
                    content_style={
                      'borderLeft': '1px solid #d6d6d6',
                      'borderRight': '1px solid #d6d6d6',
                      'borderBottom': '1px solid #d6d6d6',
                      'padding': '44px'
                    },
                    )


app = dash.Dash()
app.css.append_css({"external_url":
                    "https://codepen.io/chriddyp/pen/bWLwgP.css"})

app.layout = html.Div(tabbed_tables(), style={
                 'width': '100%',
                 'maxWidth': '1200px',
                 'margin': '0 auto'
                })

@app.callback(
    Output('download-link-0', 'href'),
    [Input('download-button-0', 'n_clicks')])
def trigger_table_1_download_link(n_clicks):
    # Don't trigger when loading page the first time
    if n_clicks == "None":
            return ''

    return '/download?value={}'.format('2002')


@app.callback(
    Output('download-link-1', 'href'),
    [Input('download-button-1', 'n_clicks')])
def trigger_table_2_download_link(n_clicks):
    # Don't trigger when loading page the first time
    if n_clicks == "None":
            return ''

    return '/download?value={}'.format('2007')


@app.server.route('/download')
def download_csv():
    value = flask.request.args.get('value')
    print("Downloading table ", value)
    df = DFS[value]

    buff = StringIO()
    df.to_csv(buff, encoding='utf-8')
    buff.seek(0)
    return flask.send_file(buff,
                           mimetype='text/csv',
                           attachment_filename=value + '.csv',
                           as_attachment=True)


if __name__ == '__main__':
    app.run_server(debug=True)

The one piece of functionality I’m missing is that It doesn’t download a file. When I click the button on the first tab, I see /download?value=2002 appended to the url, but it doesn’t actually follow through. I have to click on my address bar and hit enter to follow the link and download the file. I’ve tried adding dcc.Location(id='url', refresh=True) to my layout, but that doesn’t trigger the download. Any ideas?

There’s also a few cleanliness issues I have with this approach:

  1. Ideally, there should only be one callback defined, but I’d have to be able to parametrize the component_ids of the Inputs and Outputs. Is that possible? I remember reading that callbacks can’t be generated at runtime.
  2. It’d be nice to not have to generate a URL and go through a route to download a file. Is that possible?
3 Likes

I hope this helps Download raw data - #7 by carlottanegri

Even better and cleaner alternative: Show and Tell - Download Component

1 Like

There are also data table alternative which implement a download button such as

from data_table import DataTable

or

from dash_tabulator import DashTabulator

.

If you render the table anyway you could use those solutions instead of

import dash_table_experiments as dt