Automatic Download / Select and Download File with Single Button Click

Hello,

Is there way to automatically start a file download?

My use case is a form where a user selects options and clicks a “submit” button. The program creates a CSV based on the selected options and initiates a download. Seems like a very common application.

I’ve seen the great posts about creating a download link (e.g. https://discuss.streamlit.io/t/heres-a-download-function-that-works-for-dataframes-and-txt/4052). However, this creates a two-step process for the user (click to submit options, click to download). It also uses a hyperlink in HTML rather than a button object. I tried adding a second button on a page with a form, but there are conflicts and form widgets are cleared.

Has any created a one-step operation for this use case?

Thanks,

Ryan

Hi @RyanMaley, welcome to Streamlit community!! :wave: :partying_face:

Thanks for describing your issue. I’ve extended the example you’ve linked and combined it with callbacks to create a one-step operation. A csv file download is triggered when the form submit button is pressed.The only limitation (due to my very limited knowledge of javascript) is that filenames are random.

In the below example, users provide a column name and entries. Clicking on the form submit button creates a dataframe with the user specified attributes and auto downloads the corresponding csv:

# uses https://discuss.streamlit.io/t/heres-a-download-function-that-works-for-dataframes-and-txt/4052
import streamlit as st
import streamlit.components.v1 as components
import pandas as pd
import base64
import os
import json
import pickle

def download_button(
    object_to_download, download_filename, button_text, pickle_it=False
):
    """
    Generates a link to download the given object_to_download.
    Params:
    ------
    object_to_download:  The object to be downloaded.
    download_filename (str): filename and extension of file. e.g. mydata.csv,
    some_txt_output.txt download_link_text (str): Text to display for download
    link.
    button_text (str): Text to display on download button (e.g. 'click here to download file')
    pickle_it (bool): If True, pickle file.
    Returns:
    -------
    (str): the anchor tag to download object_to_download
    Examples:
    --------
    download_link(your_df, 'YOUR_DF.csv', 'Click to download data!')
    download_link(your_str, 'YOUR_STRING.txt', 'Click to download text!')
    """
    if pickle_it:
        try:
            object_to_download = pickle.dumps(object_to_download)
        except pickle.PicklingError as e:
            st.write(e)
            return None

    else:
        if isinstance(object_to_download, bytes):
            pass

        elif isinstance(object_to_download, pd.DataFrame):
            object_to_download = object_to_download.to_csv(index=False)

        # Try JSON encode for everything else
        else:
            object_to_download = json.dumps(object_to_download)

    try:
        # some strings <-> bytes conversions necessary here
        b64 = base64.b64encode(object_to_download.encode()).decode()

    except AttributeError as e:
        b64 = base64.b64encode(object_to_download).decode()

    dl_link = f"""
        <html>
        <head>
        <title>Start Auto Download file</title>
        <script src="http://code.jquery.com/jquery-3.2.1.min.js"></script>
        <script>
        $(function() {{
        $('a[data-auto-download]').each(function(){{
        var $this = $(this);
        setTimeout(function() {{
        window.location = $this.attr('href');
        }}, 500);
        }});
        }});
        </script>
        </head>
        <body>
        <div class="wrapper">
        <a data-auto-download href="data:text/csv;base64,{b64}"></a>
        </div>
        </body>
        </html>"""

    return dl_link

def download_df():
    df = pd.DataFrame(st.session_state.col_values, columns=[st.session_state.col_name])
    filename = "my-dataframe.csv"
    components.html(
        download_button(
            df, filename, f"Click here to download {filename}", pickle_it=False
        ),
        height=0,
    )

with st.form("my_form", clear_on_submit=False):
    st.text_input("Column name", help="Name of column", key="col_name")
    st.multiselect(
        "Entries", options=["A", "B", "C"], help="Entries in column", key="col_values"
    )
    submit = st.form_submit_button("Download dataframe", on_click=download_df)

autodownload-csv

You’ll have to fix the JS in the dl_link object to specify a filename.
Source: html - How can I download a file automatically without click on button? - Stack Overflow

Happy Streamlit’ing! :balloon:
Snehan

1 Like