Intermediate upload and download app example

I’m looking for an intermediate example of an app which you can upload a file and download a file.

I’m trying to find something between the docs (which give examples for notebook dev):
https://panel.holoviz.org/reference/widgets/FileInput.html
https://panel.holoviz.org/reference/widgets/FileDownload.html

and more extensive app examples:
https://panel.holoviz.org/gallery/simple/file_download_examples.html
https://panel.holoviz.org/gallery/param/download_upload_csv.html

Ideally I would use something like panel serve app.py --show --autoreload for the app.

The app would have you upload a csv file and download it again.

You can generate a csv as:

import pandas as pd
pd.DataFrame({"a": [0]}).to_csv("in.csv", index=False)

In my head the app would look something like:

import io
import panel as pn
import pandas as pd

pn.extension()

file_input = pn.widgets.FileInput()
pn.panel(file_input).servable()
if file_input.value is not None:
    df = pd.read_csv(io.BytesIO(file_input.value))
    file_download = pn.widgets.FileDownload(
        io.BytesIO(df.to_csv().encode()), filename="out.csv"
    )
    pn.panel(file_download).servable()

I’m pretty sure i’m missing some param usage for this or using bind as @hoxbro showed here: Convert CSV into Dataframe using FileInput - #4 by Hoxbro

I have some streamlit code which does what I expect:

import pandas as pd
import streamlit as st

uploaded_file = st.file_uploader(label="Choose a file")
if uploaded_file is not None:
    df = pd.read_csv(uploaded_file)
    st.download_button(
        label="Download data",
        data=df.to_csv(index=False),
        file_name="out.csv",
    )

You need to use callback of the file_download:

import io
import panel as pn

pn.extension()


def _callback():
    if file_input.value is not None:
        return io.BytesIO(file_input.value)
    else:

        return io.BytesIO()  # For no file_input


file_input = pn.widgets.FileInput()
file_download = pn.widgets.FileDownload(
    filename="out.csv",
    callback=_callback,
)

pn.Column(file_input, file_download).servable()
1 Like

Thanks @Hoxbro. Here is the same with a mini pandas ETL. Not sure how to hide/not allow clicking the download button before a file is uploaded.

import io
import pandas as pd
import panel as pn

pn.extension()


def _callback():
    if file_input.value is not None:
        df = pd.read_csv(io.BytesIO(file_input.value))
        return io.BytesIO(df.iloc[0].to_csv(index=False).encode())
    else:
        return io.BytesIO()


file_input = pn.widgets.FileInput(accept=".csv", mime_type="text/csv")
file_download = pn.widgets.FileDownload(
    filename="out.csv",
    callback=_callback,
    name="Upload csv first then press download button below",
)

pn.Column(file_input, file_download).servable()

Here I would use bind

import io
import panel as pn

pn.extension()


def _callback():
    if file_input.value is not None:
        return io.BytesIO(file_input.value)
    else:
        return io.BytesIO()  # For no file_input


def _disable_download(event):
    if file_input.value is None:
        file_download.disabled = True
    else:
        file_download.disabled = False


file_input = pn.widgets.FileInput()
file_download = pn.widgets.FileDownload(
    filename="out.csv", callback=_callback, disabled=True
)

pn.bind(_disable_download, file_input.param.value, watch=True)
pn.Column(file_input, file_download).servable()
1 Like

This is awesome! Thanks @Hoxbro. Thanks for helping extend my app in a way that I can easily understand what’s going on.

Final python code for my mini pandas ETL for completeness:

import io
import pandas as pd
import panel as pn

pn.extension()


def _callback():
    if file_input.value is not None:
        df = pd.read_csv(io.BytesIO(file_input.value))
        return io.BytesIO(df.iloc[0].to_csv(index=False).encode())
    else:
        return io.BytesIO()


def _disable_download(event):
    if file_input.value is None:
        file_download.disabled = True
    else:
        file_download.disabled = False


file_input = pn.widgets.FileInput(accept=".csv", mime_type="text/csv")
file_download = pn.widgets.FileDownload(
    filename="out.csv",
    callback=_callback,
    disabled=True,
)
pn.bind(_disable_download, file_input.param.value, watch=True)
pn.Column(file_input, file_download).servable()
1 Like