使用ipywidget按钮如何下载文件?

11

我创建了一个ipywidget按钮。当按钮被点击时,我希望程序能够进行计算,得到一个结果字符串,然后用户可以将该字符串作为文件下载。

代码如下:

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

我已经多次阅读了 ipywidgets 文档,但没有找到解决方法。

现在我使用了另一种方式(这严重影响了用户体验):创建一个 HTML widget,并在下载按钮被点击时将 HTML widget 的值更改为链接 data:text/plain;charset=utf-8,{res},以便让用户点击并下载,但是否有一种单击即可完成此操作的方式?

如果有任何帮助,将不胜感激。

3个回答

13

我见过的最优雅的方式是解决方案1 此处(稍作修改并如下所示):

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))

5
谢谢您!但我不确定这对我有多大帮助。在我的情况下,在初始化按钮时文件并不存在,而是在单击按钮后计算出来的。实际上,我正在寻找一种方法让按钮触发计算和下载两个操作。 - loopy
为什么要进行编码然后再解码呢? - Nathan B

8
迟到了,但如果其他人遇到此问题并需要动态文件内容,则可以采用以下方法。该代码受@Poompil答案的启发。此外,可能有更优雅的方式来绕过浏览器缓存,但无法在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>
"""))

现在我们可以简单地添加

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

这将添加一个下载按钮,当用户点击该按钮时生成下载文件的内容。

在此输入图片描述


太好了 - 我现在已经开始在我的实现中使用它了。谢谢! - Poompil
2
这在Jupyter中很好用,但在JupyterLab中不起作用(已测试3.2.5)。有什么办法可以让它在JupyterLab中工作吗? - Florian Feldhaus

4
在Solara中,我们使用FileDownload组件解决了这个问题。 Solara重新使用整个ipywidgets堆栈,这意味着您可以在Jupyter笔记本中使用它。
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")

Solara file upload component

请注意,Solara默认不提供小部件,因此如果您想将其嵌入到更大的ipywidget应用程序中,则应使用.widget方法:
import ipywidgets as widgets
widgets.VBox([
    solara.FileDownload.widget(data=my_work, filename="my_file.csv")
])

谢谢分享 - 这看起来很棒:简单而有效。您是否知道Solara小部件在voila中是否可用? - Vojta F
谢谢。是的,它们应该可以直接使用,因为它们是基于ipywidget堆栈构建的。 - Maarten Breddels

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接