How to download a file using ipywidget button?

Viewed 5283

I build a ipywidget button. I hope that when the button is clicked, the program do the computing, and get a result string, then user can download the string as a file.

THe codes are like this:

import ipywidgets as widgets


download_button = widgets.ToggleButton()
download_button.on_click(do_some_work)

def do_some_work(content)-> str:
    res = compute()
    # Here comes the problem: how to let user download the res as a file?

def compute()-> str:
    # ... do_some_compute
    return res

I have read ipywidgets doc for plenty times, but could not find a solution.

I use an alternative way now (which seriously affected the user experience): create a HTML widget and when download_button is clicked, change the value of HTML widget to an link to data:text/plain;charset=utf-8,{res} to let user click and download, but is there any way to achieve this with single one click?

Any help will be much appreciate.

2 Answers

The most elegant way I've seen is Solution 1 here (slightly modified and presented below):

from ipywidgets import HTML
from IPython.display import display

import base64

res = 'computed results'

#FILE
filename = 'res.txt'
b64 = base64.b64encode(res.encode())
payload = b64.decode()

#BUTTONS
html_buttons = '''<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<a download="{filename}" href="data:text/csv;base64,{payload}" download>
<button class="p-Widget jupyter-widgets jupyter-button widget-button mod-warning">Download File</button>
</a>
</body>
</html>
'''

html_button = html_buttons.format(payload=payload,filename=filename)
display(HTML(html_button))

Late to the game but if anyone else faces this issue and needs dynamic file content, here is an approach for this. The code is inspired by the answer by @Poompil . Also, there might be more elegant way to bypass browser cache but could not make it to work in Jupyter.

import base64
import hashlib
from typing import Callable

import ipywidgets
from IPython.display import HTML, display


class DownloadButton(ipywidgets.Button):
    """Download button with dynamic content

    The content is generated using a callback when the button is clicked.
    """

    def __init__(self, filename: str, contents: Callable[[], str], **kwargs):
        super(DownloadButton, self).__init__(**kwargs)
        self.filename = filename
        self.contents = contents
        self.on_click(self.__on_click)

    def __on_click(self, b):
        contents: bytes = self.contents().encode('utf-8')
        b64 = base64.b64encode(contents)
        payload = b64.decode()
        digest = hashlib.md5(contents).hexdigest()  # bypass browser cache
        id = f'dl_{digest}'

        display(HTML(f"""
<html>
<body>
<a id="{id}" download="{self.filename}" href="data:text/csv;base64,{payload}" download>
</a>

<script>
(function download() {{
document.getElementById('{id}').click();
}})()
</script>

</body>
</html>
"""))

Now we can simply add

DownloadButton(filename='foo.txt', contents=lambda: f'hello {time.time()}', description='download')

which adds a download button and the contents of the downloaded file is generated when the button is clocked.

enter image description here

Related