ipywidgets
ipywidgets copied to clipboard
widgets.Button for handling download
Hi, I would like to create a download button from widgets.Button, is it possible to do that?
I know that the download can be achieved using link tag, i.e.
<a href="link/to/file" download>test_download</a>
Using button, I am not sure what to put to handle the download of the temporary file I created.
...
def on_button_download_clicked(b):
# TODO: Handle download
# Disable button after downloading
btn_download.disabled = True
btn_download.on_click(on_button_download_clicked)
...
Cheers
I have been trying to do this as well. I think this is a lot harder than it seems since I think this is a common request, but I have found no real solution yet. Currently I am here:
(1) If you are (unlikely) running everything locally (i.e., you run Jupyter on your local machine) and you are not on a Windows machine, then this works:
import webbrowser
button = widgets.Button(description='My Button')
df = pd.DataFrame(np.random.rand(10,25))
def on_button_download_clicked(b):
df.to_excel('/tmp/data.xlsx')
url = 'file:///tmp/data.xlsx'
webbrowser.open(url)
button.on_click(on_button_download_clicked)
(2) If you can suffer through not having a real button, and just using an ugly weblink, then the function create_download_link(...) in this solution works.
(3) If you want a real solution and Jupyter is running on a server where your browser doesn't have access to the filesystem, then the best thing I could come up with so far (which admittedly seems a little Rube Goldberg crazy), is to run a simple HTTP server and then link to those files. Specifically, on the server I run python -m http.server 5005 -d /tmp. Then I use ipyvuetify as
import ipyvuetify as v
df.to_excel('/tmp/data.xlsx')
b = v.Btn(children=['Button'])
b.href = 'http://the-server-name:5005/data.xlsx'
b
and it works. I would love to hear any other solutions!
Duplicate of https://github.com/jupyter-widgets/ipywidgets/issues/2445 ?
Yes, I think you are right -- it's a duplicate. #2445 is closed but I admit that I don't readily follow that accepted answer. I can't seem to find anything anywhere that gives an example of basic and core button functionality:
- How do I have an
ipywidgetsbutton that when clicked, follows a link to another webpage? - How do you have an
ipywidgetsbutton send a file to the browser?
#2445 asks how to "attach an href to a button that automatically updates itself as other items in the page change", which comes from the need of generating a "download button" or a "download popup". So I don't think it's a duplicate, and I'd vote +1 on this feature.
@marketneutral the accepted answer on #2445 describes how to make a Button update an HTML with a proper link, which is one of the possible solutions for this use case.
I found this thread trying to do exactly what @marketneutral said. I had the same thought that it must be a very easy/standard thing. I had issues when trying to use even an anchor within the notebook. It was getting Forbidden errors. I found something called FileLink.
Quick example:
from IPython.display import display, FileLink
local_file = FileLink('./demo.xlsx', result_html_prefix="Click here to download: ")
display(local_file)
I did some more searching and found this StackOverflow post. I added my answer (based on comments) if anyone else needs.
Download button
I found this stackoverflow post useful, noting it is only useful for relatively small files as it will hold the file in ram.
import ipywidgets
import base64
def download_button(buffer, filename: str, button_description: str):
"""Loads data from buffer into base64 payload embedded into a HTML button. Recommended for small files only.
buffer: open file object ready for reading.
A file like object with a read method.
filename: str
The name when it is downloaded.
button_description: str
The text that goes into the button.
"""
payload = base64.b64encode(buffer.read()).decode()
html_button = f"""<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<a download="{filename}" href="data:text/csv;base64,{payload}" >
<button class="p-Widget jupyter-widgets jupyter-button widget-button mod-warning">{button_description}</button>
</a>
</body>
</html>
"""
return ipywidgets.HTML(html_button)
Usage
import io
import pandas as pd
f = io.BytesIO()
pd.DataFrame({'a':[1,2,3]}).to_csv(f)
f.seek(0)
download_button(f, 'my_file.csv','Download')
FileLink
I also found that FileLink needs to be modified if you want to download the file instead of open it with JupyterLab.
class DownloadFileLink(FileLink):
html_link_str = "<a href='{link}' download={file_name}>{link_text}</a>"
def __init__(self, path, file_name=None, link_text=None, *args, **kwargs):
super(DownloadFileLink, self).__init__(path, *args, **kwargs)
self.file_name = file_name or os.path.split(path)[1]
self.link_text = link_text or self.file_name
def _format_path(self):
from html import escape
fp = "".join([self.url_prefix, escape(self.path)])
return "".join(
[
self.result_html_prefix,
self.html_link_str.format(
link=fp, file_name=self.file_name, link_text=self.link_text
),
self.result_html_suffix,
]
)
Usage
DownloadFileLink(path_to_file_in_jupyter_workspace, "my_file.csv", "Download")
But it doesn't work with Voila.
with jupyter lab 3.1.12, this works in Voilà and Jupyter Lab:
from IPython.display import HTML
#...
HTML('<a href="http://apps.vps_jupyter:8888/files/salesforecasts/customer.xlsx" title="click to download">Download</a>')
I've been looking into this, and it seems there are two reliable solutions.
- base64 encode the file and put it in an HTML widget. Important to remember that this will place the data in the HTML page and therefore the user's browser, so trying to put a lot of data there could lock up their system.
- set up a dedicated file serving system and save your files there, then generate valid links to those files. This could be through your http server setup (e.g. setting a folder of files to be served by NGINX) or a cloud system (like pushing the files to S3).
Other solutions will tend to not work in some situations (e.g. within jupyterhub, voila, behind reverse proxies, etc...)
In solara, we now have the FileDownload component which can lazily read/transport the data after a button click, if you pass it a function.
import solara
import time
import pandas as pd
# create a dummy dataframe with measurements
df = pd.DataFrame(data={'instrument': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'], 'values': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
def my_work():
time.sleep(3) # mimic a long running job
dff = df[df['values'] > 5]
# .to_csv returns a string
return dff.to_csv(index=False)
solara.FileDownload(data=my_work, filename="my_file.csv")
This works in Jupyter notebook and Voila, because the data is transported via the websocket (in binary, not base64 encoded, so efficient!).
Demo:

This component will display itself, but if you want to use it in a larger classic ipywidgets application (as opposed to a Reacton/Solara component based application), you can use the .widget(...) method on the component:
import ipywidgets as widgets
widgets.VBox([
solara.FileDownload.widget(data=my_work, filename="my_file.csv")
])