user88484
user88484

Reputation: 1557

How can I make a temporaty zip object downlaodable for user using Jupyter Notebook and Voila

I writing a jupyter notebook, which I would like to render using Voila to create a small web app/tool. What the tool does is to get one Geojson file containing many polygons from the user and return a ZIP file containing many GeoJSON files (a file per polygon). For example, if the user uploads a GeoJSON file with 20 polygons (they are all in the same file), then the output should be a ZIP file containing 20 separate GeoJSON files - file per polygon. I am able to do it locally and the ZIP file is saved as needed.

However, I would like to render it using Voila so that it can later work from anywhere, meaning the ZIP file will be created in-memory/on-the-fly/as-a-buffer (not sure which term is the accurate one) and then the user will be able to download the ZIP file, via automatic download, or by clicking on a button or with a pop-up window to download, it really does not matter here.

Here is a snippet of my code (let me know if it's not enough):

def on_button_clicked(event):
    with output:
        clear_output()
        df = gpd.GeoDataFrame().from_features(json.loads(upload.data[0])) # if the file is geojson
        display(HTML(f'<h4><left>There are {len(df)} polygons in the file</left></h4>'))
        
        # make results.zip in temp directory
        # https://gist.github.com/simonthompson99/362404d6142db3ed14908244f5750d08
        tmpdir = tempfile.mkdtemp()
        zip_fn = os.path.join(tmpdir, 'results.zip')
        zip_obj = zipfile.ZipFile(zip_fn, 'w')

        for i in range(df.shape[0]):

            if len(field_names_col.value)==0:
                field_name = f'field_{str(i+1)}'
            else:
                field_name = df[field_names_col.value][i]

            output_name = f'{field_name}.{output_format.value.lower()}'

            df.iloc[[i]].to_file(f'{tmpdir}\\{output_name}', driver=output_format.value)

        for f in glob.glob(f"{tmpdir}/*"):
            zip_obj.write(f, os.path.basename(f)) # add file to archive, second argument is the structure to be represented in zip archive, i.e. this just makes flat strucutre

        zip_obj.close()

button_send.on_click(on_button_clicked)

vbox_result = widgets.VBox([button_send, output])

The important part is near the end:

for f in glob.glob(f"{tmpdir}/*"):
    zip_obj.write(f, os.path.basename(f)) # add file to archive, second argument is the structure to be represented in zip archive, i.e. this just makes flat strucutre

zip_obj.close()

I iterate over the temporary separate files and create a temporary ZIP file (results.zip) stored in the zip_obj. How can I "push" this ZIP object to the user for download using Jupyter Notebook?

I tried using (just before or just after zip_obj.close()):

local_file = FileLink(os.path.basename(f), result_html_prefix="Click here to download: ")
display(local_file)

But I get an error when rendering it with Voila:

Path (results.zip) doesn't exist. It may still be in the process of being generated, or you may have the incorrect path.

For example, to save it locally I did:

with zipfile.ZipFile('c:/tool/results.zip', 'w') as zipf:
    for f in tmpdir.glob("*"):
      zipf.write(f, arcname=f.name)

Upvotes: 0

Views: 340

Answers (1)

Wayne
Wayne

Reputation: 9800

"I iterate over the temporary separate files and create a temporary ZIP file (results.zip) stored in the zip_obj. How can I "push" this ZIP object to the user for download using Jupyter Notebook?"

There's an example of making a download link that will show up in the Voila rendering here. (Learned of it from here, although the focus there was on file upload. The example covers downloading the results, too.) A simpler take on this is found at the bottom of this answer here, converted to your case:

%%html
<a href="./voila/static/the_archive.zip" download="demo.xlsx">Download the Resulting Files as an Archive</a>

These two options are illustrated for a different type of file in the bottom section (everything below the line break presently there) of the answer here.

Upvotes: 0

Related Questions