How to download file in streamlit

Bernardo_tod’s function to generate the excel file download link works perfectly when I run my app on the local server. However, in the online-version of the app, the link doesn’t react. Anyone have a clue why?

Remember to use return href

1 Like

Exactly the same problem here. Maybe it has to do with some background packages not present on the OS of the server?

2 Likes

I modified an earlier result to download a file from AWS using boto3, might be useful to someone:

import boto3
import base64
import uuid
import re

def download_aws_object(bucket, key):
    """
    Download an object from AWS
    Example key: my/key/some_file.txt
    """
    s3 = boto3.resource('s3')
    obj = s3.Object(bucket, key)
    file_name = key.split('/')[-1] # e.g. some_file.txt
    file_type = file_name.split('.')[-1] # e.g. txt
    b64 = base64.b64encode(obj.get()['Body'].read()).decode()

    button_uuid = str(uuid.uuid4()).replace("-", "")
    button_id = re.sub("\d+", "", button_uuid)

    custom_css = f""" 
        <style>
            #{button_id} {{
                background-color: rgb(255, 255, 255);
                color: rgb(38, 39, 48);
                padding: 0.25em 0.38em;
                position: relative;
                text-decoration: none;
                border-radius: 4px;
                border-width: 1px;
                border-style: solid;
                border-color: rgb(230, 234, 241);
                border-image: initial;
            }} 
            #{button_id}:hover {{
                border-color: rgb(246, 51, 102);
                color: rgb(246, 51, 102);
            }}
            #{button_id}:active {{
                box-shadow: none;
                background-color: rgb(246, 51, 102);
                color: white;
                }}
        </style> """

    dl_link = (
        custom_css
        + f'<a download="{file_name}" id="{button_id}" href="data:file/{file_type};base64,{b64}">Download {file_name}</a><br></br>'
    )
    return dl_link


st.markdown(download_aws_object(bucket, key), unsafe_allow_html=True)
2 Likes

A question. I am testing this code.

output = BytesIO()
st.write("checkpoint 1")
writer = pd.ExcelWriter(output, engine='xlsxwriter')
st.write("checkpoint 2")

When I running it in local, I get 2 checkpoint printed.
When I running it in Heroku, I only get the first checkpoint printed so I can’t write something to excel.

I need to write something to excel and download it from my Heroku App. Is there any solution to fix this?

Thank you

1 Like

Great function, I love our community.

The download works like a charm for dataframes! However, does anyone have a solution for large dfs?

If the Excel is larger than 50MB, an error is produced. This can be avoided by changing MESSAGE_LIMIT_SIZE in site-packages/streamlit/server/server_util.py (see RuntimeError: Data of size 107.9MB exceeds write limit of 50.0MB - Using Streamlit - Streamlit).

BUT, now I get a Tornado error:

future: <Task finished coro=<WebSocketProtocol13.write_message..wrapper() done, defined at /home/argusadmin/notebooks/Datalake.NLP.Demos/.nlp_demos/lib/python3.6/site-packages/tornado/websocket.py:1100> exception=WebSocketClosedError()>
Traceback (most recent call last):
File “/home/argusadmin/notebooks/Datalake.NLP.Demos/.nlp_demos/lib/python3.6/site-packages/tornado/websocket.py”, line 1102, in wrapper
await fut
tornado.iostream.StreamClosedError: Stream is closed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/home/argusadmin/notebooks/Datalake.NLP.Demos/.nlp_demos/lib/python3.6/site-packages/tornado/websocket.py”, line 1104, in wrapper
raise WebSocketClosedError()
tornado.websocket.WebSocketClosedError
2021-03-08 13:46:57.248 Task exception was never retrieved
future: <Task finished coro=<WebSocketProtocol13.write_message..wrapper() done, defined at /home/argusadmin/notebooks/Datalake.NLP.Demos/.nlp_demos/lib/python3.6/site-packages/tornado/websocket.py:1100> exception=WebSocketClosedError()>
Traceback (most recent call last):
File “/home/argusadmin/notebooks/Datalake.NLP.Demos/.nlp_demos/lib/python3.6/site-packages/tornado/websocket.py”, line 1102, in wrapper
await fut
tornado.iostream.StreamClosedError: Stream is closed

I assume that this is due to Tornado’s max byte size, but I’m not sure how I could possibly adjust that from within streamlit. Any ideas?

FYI, I solved the problem by saving the dataframe in streamlit’s static folder: Ability to download data from Streamlit · Issue #400 · streamlit/streamlit (github.com)

2 Likes

Hi everyone, thanks for the sharing, I’m looking to download a read-only excel file from a data frame. Has anyone works on this before? Below is the current downloader. Thanks!

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)
    return f'<a href="data:application/octet-stream;base64,{b64.decode()}" download="Your_File.xlsx">Download Excel file</a>'

I followed the suggested solution, it works fine… But i get the following error message that I cant surpress: UnhashableTypeError : Cannot hash object of type streamlit.delta_generator.DeltaGenerator , found in something.

While caching something, Streamlit encountered an object of type streamlit.delta_generator.DeltaGenerator , which it does not know how to hash.

To address this, please try helping Streamlit understand how to hash that type by passing the hash_funcs argument into @st.cache . For example:

Tried different things,but still get the same result. Can anyone help?

This works perfectly

Has anyone done this for a Parquet file, using df.to_parquet ?

Great Job! Thanks for your work! :ok_hand: :medal_sports:
I have upgraded this code a little bit with my friend, so it uses custom colors that you have in your streamlit app, find a snippet here:

1 Like

didn’t worked. instead showed this:

Hello, thanks for your good job.

when I was running the function, I got a feedback

can not find theme.primaryColor\ theme.backgroundColor\ theme.secondaryBackgroundColor\ theme.textColor\ theme.font

where is these setting locating?

Hi,
first of all, this will work only for versions >=0.79.
I think your feedback suggests that you have an older version installed.

Another thing is that st.config.get_option for theme options will return None if you don’t use a custom theme. So the easiest fix for you would be to add to your config file .streamlit/config.toml something like this:

[theme]
# should look like a light theme
primaryColor  = '#f43365'
backgroundColor = '#000000'
secondaryBackgroundColor = '#f1f3f6'
textColor = '#000000'
font = 'sans serif'

If you don’t have this file just create it in your project or home directory.

1 Like

my streamlit version is 0.80, and I had added the content of you provied to config.toml
when I use this function, no download button shows.
download_button(pd.Dataframe(list), 'example.csv', 'example.csv', pickle_it=False)

when I use this function, no download button shows.

If it does not appear you should use the line with st.markdown - this is the code that you need to place:

tmp_download_link = download_button(df_to_save.reset_index(), f'{file_name}.csv',
                                        button_text='Click here to download your text!')
    
st.markdown(tmp_download_link, unsafe_allow_html=True)

Did you do that?

Hi, can I replace the df_to_save.reset_index() with df or pd.DataFrame(list)?
and replace f’{file_name}.csv’ with f’{example}.csv’ ?

df, pd.DataFrame(list), example is my own setting, it can change in different conditions.

Hi,
‘df_to_save.reset_index’ should be any pd.DataFrame object

file_name should be a string. here you have f’{file_name}.csv’, and file_name is a variable that will become the name of the file

This part tell what to save, what will be the name of the file and the text on the button only:

tmp_download_link = download_button(df_to_save.reset_index(), f'{file_name}.csv',
                                        button_text='Click here to download your text!')

This part shows the button in streamlit:

st.markdown(tmp_download_link, unsafe_allow_html=True)

The part responsible for the button itself is in the snippet.

You can clone my code and look at the files:

Hope it will help you

1 Like