How to download file in streamlit

There are some files generate by my program, I need a button when I press files will be download by my browser, but I don’t know how to do that. could streamlit provide this function?

12 Likes

not directly, but there is a workaround. Its discussed in the following contribution: File Download Workaround added to awesome-streamlit.org.

I have created the following function for myself and use it frequently. It works fine for bigger files too, my files often have thousands of rows and so far I haven’t hit a limit yet.

st.markdown(get_table_download_link(df), unsafe_allow_html=True)
def get_table_download_link(df):
    """Generates a link allowing the data in a given panda dataframe to be downloaded
    in:  dataframe
    out: href string
    """
    csv = df.to_csv(index=False)
    b64 = base64.b64encode(csv.encode()).decode()  # some strings <-> bytes conversions necessary here
    href = f'<a href="data:file/csv;base64,{b64}">Download csv file</a>'
15 Likes

Thanks!! Can’t imagine I missed this post :sweat:,I should search more carefully before I post next time

2 Likes

hey using that opens a new tab with title about:blank#blocked
and nothing happens

please help

1 Like

This works for me, just make sure to return href at the bottom of the get_table_download_link function

3 Likes

Heya, this works for me, but the files download without the .csv extension. Is there a way to make sure that the .csv extension occurs or specify the name of the csv file?

Thank you!

1 Like

I haven’t found a way to do that automatically, but one can always edit the filename to filename.csv when saving the link with the right mouse click.

2 Likes

@marciorpcoelho I see, thank you for putting this workaround together it is very useful!

1 Like

Why did U say that the file is downloaded without the .csv, it’s not true if you have converted your dataframe with the to_csv() method. It’s works like a charm

1 Like

Adding the ‘download’ tag attribute as shown below allows you to provide a file name and extension.

f'<a href="data:file/csv;base64,{b64}" download="myfilename.csv">Download csv file</a>'

18 Likes

Welcome to the community @dplutchok, and thanks for pointing out the download argument!

4 Likes

I’ve been searching for a while, a way to do same thing with excel xlsx file. I finally found
a way to make it work, so I decide to share my code here. I hope it will help someone.

python 3, pandas '0.23.4'

import streamlit as st
import base64
from io import BytesIO

def to_excel(df):
    output = BytesIO()
    writer = pd.ExcelWriter(output, engine='xlsxwriter')
    df.to_excel(writer, sheet_name='Sheet1')
    writer.save()
    processed_data = output.getvalue()
    return processed_data

def get_table_download_link(df):
    """Generates a link allowing the data in a given panda dataframe to be downloaded
    in:  dataframe
    out: href string
    """
    val = to_excel(df)
    b64 = base64.b64encode(val)  # val looks like b'...'
    return f'<a href="data:application/octet-stream;base64,{b64.decode()}" download="extract.xlsx">Download csv file</a>' # decode b'abc' => abc

df = ... # your dataframe
st.markdown(get_table_download_link(df), unsafe_allow_html=True)

# PS: pip install xlsxwriter  # pandas need this
20 Likes

This is a great answer for csv files or excel files.
However does it work on midi (*.mid) files? midi files are binary. like the excel files.

Now I dont think i need to encode/decode to b64. what do you think? if i do use the b64 enc/decoding i get

TypeError: a bytes-like object is required, not '_io.BufferedReader’

if i dont it still doesnt work.
you can find an example midi file here example_midi

example:

import base64
def get_midi_download_link(mid):
    """Generates a link allowing the data in a given midi file to be downloaded
    in:  midi file
    out: href string
    """
    
      # val looks like b'...'
    return f'<a href="data:application/octet-stream;{mid}" download="example.mid">Download midi file with save as</a>' 




with open("105027.mid", 'rb') as inmidi:
    st.markdown(get_midi_download_link(inmidi), unsafe_allow_html=True)
3 Likes

Whenever you download the .xlsx file, index column will come in the sheet1. How we can make index = False? Could you please help me out in this?

1 Like

Hello @goutamborthakur555, welcome to the community :slight_smile: !

I believe from the to_excel docs that index=False should be added to the to_excel function so :

def to_excel(df):
    output = BytesIO()
    writer = pd.ExcelWriter(output, engine='xlsxwriter')
    df.to_excel(writer, index=False, sheet_name='Sheet1') # <--- here
    writer.save()
    processed_data = output.getvalue()
    return processed_data

Fanilo

4 Likes

Thank you!

1 Like

Hi, if I make an excel file downloadable and if my excel downloadable file values are in float of 4 decimal place, how to make it to 2 decimal place? i.e while download the file values should come with 2 decimal places.

import streamlit as st
import base64
from io import BytesIO

def to_excel(df):
    output = BytesIO()
    writer = pd.ExcelWriter(output, engine='xlsxwriter')
    df.to_excel(writer, index = False, sheet_name='Sheet1')
    workbook  = writer.book
    worksheet = writer.sheets['Sheet1']
    format1 = workbook.add_format({'num_format': '0.00'}) # Tried with '0%' and '#,##0.00' also.
    worksheet.set_column('A:A', None, format1) # Say Data are in column A
    writer.save()
    processed_data = output.getvalue()
    return processed_data

def get_table_download_link(df):
    """Generates a link allowing the data in a given panda dataframe to be downloaded
    in:  dataframe
    out: href string
    """
    val = to_excel(df)
    b64 = base64.b64encode(val)  # val looks like b'...'
    return f'<a href="data:application/octet-stream;base64,{b64.decode()}" download="Your_File.xlsx">Download Excel file</a>' # decode b'abc' => abc

st.markdown(get_table_download_link(df), unsafe_allow_html=True)

df = my dataframe

Position
5.7680
2.7680
5.7680
5.7680
7.2680
5.7680
5.7680
5.7680
5.7680
5.7680
5.7680
5.7680
5.9680
5.7680
2.7680
5.7680
5.7680
5.7680
5.7680
5.7680
1 Like

Thank, I got the answer. Was supposed this: float_format="%.2f"

import streamlit as st
import base64
from io import BytesIO

def to_excel(df):
    output = BytesIO()
    writer = pd.ExcelWriter(output, engine='xlsxwriter')
    df.to_excel(writer, index = False, sheet_name='Sheet1',float_format="%.2f")
    writer.save()
    processed_data = output.getvalue()
    return processed_data

def get_table_download_link(df):
    """Generates a link allowing the data in a given panda dataframe to be downloaded
    in:  dataframe
    out: href string
    """
    val = to_excel(df)
    b64 = base64.b64encode(val)  # val looks like b'...'
    return f'<a href="data:application/octet-stream;base64,{b64.decode()}" download="Your_File.xlsx">Download Excel file</a>' # decode b'abc' => abc

st.markdown(get_table_download_link(df), unsafe_allow_html=True)
2 Likes

In case if you need to download image:

result = Image.fromarray(predicted_img)

def get_image_download_link(img):
	"""Generates a link allowing the PIL image to be downloaded
	in:  PIL image
	out: href string
	"""
	buffered = BytesIO()
	img.save(buffered, format="JPEG")
	img_str = base64.b64encode(buffered.getvalue()).decode()
	href = f'<a href="data:file/jpg;base64,{img_str}">Download result</a>'
	return href

st.markdown(get_image_download_link(result), unsafe_allow_html=True)

This code assumes that you convert you image to PIL format from the numpy array.

5 Likes

If it helps anyone, I posted a version of the function that can handle both dataframes and text: Here's a download function that works for dataframes and txt (posted below for reference)

Also, curious if anyone has any ideas about how to generalize file downloads in Streamlit. For example: CSV, Excel, img, txt, midi, pdf, video formats, etc.

It would also be neat to auto download on button click - or interaction with another Streamlit component - rather than a link being returned. I’ve started researching ways to achieve this, but it’s admittedly not my specialty area. Any thoughts on how to achieve this?

import base64

import streamlit as st
import pandas as pd


def download_link(object_to_download, download_filename, download_link_text):
    """
    Generates a link to download the given object_to_download.

    object_to_download (str, pd.DataFrame):  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.

    Examples:
    download_link(YOUR_DF, 'YOUR_DF.csv', 'Click here to download data!')
    download_link(YOUR_STRING, 'YOUR_STRING.txt', 'Click here to download your text!')

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

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