将内存流中的文件提供给 Bokeh 应用程序

Serve a file from a memory stream to a Bokeh app

我正在尝试向 Bokeh 网络应用程序的用户提供文件下载(.csv 和 .png)。目前这是通过在本地保存文件副本并执行读取该文件的 Javascript 来实现的(根据 this example and ):

def save_file(self, save_function, file_name):
    """
    :param save_function: callable that takes a single argument: the file path to save to
    :param file_name: default name for the downloaded file
    """
    js_download = """
      fetch(local_path, {cache: "no-store"}).then(response => response.blob())
                .then(blob => {
                    //addresses IE
                    if (navigator.msSaveBlob) {
                        navigator.msSaveBlob(blob, save_name);
                    }
                    else {
                            const link = document.createElement('a')
                            link.href = URL.createObjectURL(blob)
                            link.download = save_name
                            link.target = '_blank'
                            link.style.visibility = 'hidden'
                            link.dispatchEvent(new MouseEvent('click'))
                    }
                    return response.text();
                });
                """
    # Make a tempory file name to avoid conflicts
    temp_name = next(tempfile._get_candidate_names())
    temp_path = f'static/{temp_name}.tmp'
    save_function(temp_path)
    self.exectute_js(CustomJS(args=dict(local_path='/AzDataTools/' + temp_path,
                                        save_name=file_name), code=js_download))


def exectute_js(self, custom_js):
    # Setup a dummy plot to trigger the javascript
    dummy = self.get_any_figure().circle([1], [2], alpha=0)
    dummy.glyph.js_on_change('size', custom_js)
    dummy.glyph.size = 1

理想情况下,我希望通过保存到内存流来跳过本地文件步骤。我对此的尝试如下:

def save_file_via_memory(self, save_function, file_name):
    """
    :param save_function: callable that takes a single argument: the file path to save to
    :param file_name: default name for the downloaded file
    """
    js_download = """
        var blob = new Blob(data)
        //addresses IE
        if (navigator.msSaveBlob) {
            navigator.msSaveBlob(blob, save_name);
        }
        else {
                const link = document.createElement('a')
                link.href = URL.createObjectURL(blob)
                link.download = save_name
                link.target = '_blank'
                link.style.visibility = 'hidden'
                link.dispatchEvent(new MouseEvent('click'))
        };
    """
    with io.BytesIO() as data:
        save_function(data)
        self.exectute_js(CustomJS(args=dict(data=data, save_name=file_name), code=js_download))

但是,这会产生错误,指示无法序列化字节流。有人知道如何将字节流传递给 JavaScript 吗? (JavaScript也可能有误,第一次用。。。)

我最终采用的解决方法是将文件转换为文本字符串。对于 csvs 和图像,这看起来像:

def save_text_file(self, text, file_name):
    """
    :param text: text to save
    :param file_name: default name for the downloaded file
    """
    js_download = """
    const blob = new Blob([text_string], { type: 'text/csv;charset=utf-8;' })
    
    //addresses IE
    if (navigator.msSaveBlob) {
        navigator.msSaveBlob(blob, file_name)
    } else {
        const link = document.createElement('a')
        link.href = URL.createObjectURL(blob)
        link.download = file_name
        link.target = '_blank'
        link.style.visibility = 'hidden'
        link.dispatchEvent(new MouseEvent('click'))
    }
    """
    self.exectute_js(CustomJS(args=dict(text_string=text, file_name=file_name), code=js_download))

def image_to_png_string(image_bytes):
    data_uri = base64.b64encode(image_bytes).decode('utf-8')
    return 'data:image/png;base64,' + data_uri

def save_image_file(self, image, file_name):
    """
    :param image: image bytes to save
    :param file_name: default name for the downloaded file
    """
    js_download = """
    fetch(image_string, {cache: "no-store"}).then(response => response.blob())
    .then(blob => {
        //addresses IE
        if (navigator.msSaveBlob) {
            navigator.msSaveBlob(blob, file_name);
        }
        else {
                const link = document.createElement('a')
                link.href = URL.createObjectURL(blob)
                link.download = file_name
                link.target = '_blank'
                link.style.visibility = 'hidden'
                link.dispatchEvent(new MouseEvent('click'))
        }
        return response.text();
    });
    """
    self.exectute_js(CustomJS(args=dict(image_string=image_to_png_string(image), file_name=file_name),
                              code=js_download))