Uploads and downloads
Upload
Server
examples/action-transfer/upload-multiple/app.py
from shiny import App, ui, render
import pandas as pd
app_ui = ui.page_fluid(
ui.input_file("upload", None, button_label="Upload...", multiple=True),
ui.output_table("files"),
)
def server(input, output, session):
@render.table
def files():
return pd.DataFrame(input.upload())
app = App(app_ui, server)Uploading data
action-transfer/uploading-data/app.py
from shiny import App, ui, render, reactive, req
import os
import pandas as pd
app_ui = ui.page_fluid(
ui.input_file("file", None, accept=[".csv", ".tsv"]),
ui.input_numeric("n", "Rows", value=5, min=1, step=1),
ui.output_ui("out_container"),
)
def server(input, output, session):
@reactive.calc
def data():
req(input.file())
_, ext = os.path.splitext(input.file()[0]["name"])
match ext:
case ".csv":
return pd.read_csv(input.file()[0]["datapath"])
case ".tsv":
return pd.read_csv(input.file()[0]["datapath"], delimiter="\t")
case _:
return None
@render.ui
def out_container():
if isinstance(data(), pd.DataFrame):
return ui.output_table("head")
else:
return ui.markdown("**Invalid file; Please upload a .csv or .tsv file**")
@render.table
def head():
req(isinstance(data(), pd.DataFrame))
return data().head(input.n())
app = App(app_ui, server)Download
Downloading data
examples/action-transfer/downloading-data/app.py
from shiny import App, ui, render, reactive, req
import pandas as pd
from pydataset import data
datasets = list(data()["dataset_id"])
app_ui = ui.page_fluid(
ui.input_select("dataset", "Pick a dataset", datasets),
ui.output_table("preview"),
ui.download_button("download_tsv", "Download .tsv"),
)
def server(input, output, session):
@reactive.calc
def df():
return data(input.dataset())
@render.table
def preview():
req(isinstance(df(), pd.DataFrame))
return df().head()
@session.download(filename=lambda: f"{input.dataset()}.tsv")
def download_tsv():
yield df().to_csv(None, sep="\t", index=False)
app = App(app_ui, server)Please note that filename argument in session.download() should be a lambda function when using reactive input.
Downloading reports
Let us render quarto document (.qmd) to html format.
report.qmd
---
title: "My Document"
format:
html:
embed-resources: true
jupyter: python3
---
```{python}
#| tags: [parameters]
n = 10
```
```{python}
import numpy as np
from matplotlib import pyplot as plt
plt.scatter(np.random.normal(size=n), np.random.normal(size=n))
```examples/action-transfer/downloading-report/app.py
from shiny import App, ui
import subprocess
import tempfile
import shutil
app_ui = ui.page_fluid(
ui.input_slider("n", "Number of points", 1, 100, 50),
ui.download_button("report", "Generate report"),
)
def server(input, output, session):
@session.download(filename="report.html")
def report():
id = ui.notification_show(
"Rendering report...",
duration=None,
close_button=False
)
with tempfile.TemporaryDirectory() as tmpdirname:
source_file = '/'.join([tmpdirname, "report.qmd"])
shutil.copy("report.qmd", source_file)
subprocess.run([
'quarto', 'render', source_file,
'-P', f"n:{input.n()}"
])
output_file = '/'.join([tmpdirname, "report.html"])
html_file = open(output_file, 'r')
html_docs = html_file.read()
yield html_docs
app = App(app_ui, server)Please note that I copied report.qmd file to temporary directory and rendered document within the temporary directory. If I render .qmd directly within app directory, the app will be automatcially reloaded when it runs with --reload option. VS Code extension for Shiny for Python runs the app with --reload option. You can avoid automatic reload by running the app in terminal with shiny run --launch-browser app.py. However, it would still be good to consider using temporary directory when it is applicable.
Shiny for python version 0.7.0 (released on 2024-01-25) added @render.download as a replacement of @session.download. As a result, @session.download has been deprecated. This book is written with Shiny for python version 0.6.1, which @render.download was not available yet.
Case study
examples/action-transfer/cleaning-data/app.py
from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
import pandas as pd
import janitor
import os
ui_upload = ui.layout_sidebar(
ui.sidebar(
ui.input_file("file", "Data", button_label="Upload..."),
ui.input_text("delim", "Delimiter (leave blank to guess)", ""),
ui.input_numeric("skip", "Rows to skip", 0, min=0),
ui.input_numeric("rows", "Rows to preview", 10, min=1),
),
ui.h3("Raw data"),
ui.output_table("preview1"),
)
ui_clean = ui.layout_sidebar(
ui.sidebar(
ui.input_checkbox("snake", "Rename columns to snake case?"),
ui.input_checkbox("constant", "Remove constant columns?"),
ui.input_checkbox("empty", "Remove empty cols?"),
),
ui.h3("Cleaner data"),
ui.output_table("preview2"),
)
ui_download = ui.row(
ui.column(12, ui.download_button("download", "Download cleaner data", class_="btn-block")),
)
app_ui = ui.page_fluid(
ui_upload,
ui_clean,
ui_download,
)
def server(input: Inputs, output: Outputs, session: Session):
# Upload ----------------------------------
@reactive.calc
def raw():
req(input.file())
delim = None if input.delim() == "" else input.delim()
res = pd.read_csv(
input.file()[0]["datapath"],
delimiter=delim,
skiprows=input.skip())
return res
@render.table
def preview1():
return raw().head(input.rows())
# Clean ------------------------------------
@reactive.calc
def tidied():
out = raw()
if input.snake():
out = out.clean_names(case_type='snake')
if input.empty():
out.dropna(how='all', axis=1, inplace=True)
if input.constant():
out = out.drop_constant_columns()
return out
@render.table
def preview2():
return tidied().head(input.rows())
# Download --------------------------------
@session.download(filename=lambda: f"{os.path.splitext(input.file()[0]['name'])[0]}.tsv")
def download():
yield tidied().to_csv(None, sep="\t", index=False)
app = App(app_ui, server)Exercise
- Create an app to upload CSV file, select a variable, and then perform a t-test.
solutions/action-transfer/ttest/app.py
from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
import numpy as np
import pandas as pd
from scipy.stats import ttest_1samp
app_ui = ui.page_fluid(
ui.input_file("file", "Upload CSV", accept=".csv"),
ui.input_select("var", "Variable", choices=[None]),
ui.output_text_verbatim("ttest"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.calc
def data():
req(input.file())
res = pd.read_csv(input.file()[0]["datapath"])
return res.select_dtypes(include=[np.number])
@reactive.effect
@reactive.event(data)
def _():
ui.update_select("var", choices=data().columns.tolist())
@render.text
def ttest():
req(input.var())
return ttest_1samp(data()[input.var()], 0)
app = App(app_ui, server)- Create an app to upload CSV file, select one variable, draw a histogram, and then download the histogram.
solutions/action-transfer/histogram/app.py
from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import io
app_ui = ui.page_fluid(
ui.input_file("file", "Upload CSV", accept=".csv"),
ui.input_select("var", "Variable", choices=[None]),
ui.output_plot("histogram"),
ui.row(
ui.column(4,
ui.input_select("ext", "Download file type",
choices=['png', 'pdf', 'svg'],
selected='png')),
ui.column(6, ui.download_button("download_hist", "Download histogram")),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.calc
def data():
req(input.file())
res = pd.read_csv(input.file()[0]["datapath"])
return res.select_dtypes(include=[np.number])
@reactive.effect
@reactive.event(data)
def _():
ui.update_select("var", choices=data().columns.tolist())
@reactive.calc
def hist_plot():
req(input.var())
fig = plt.figure()
plt.hist(data()[input.var()])
return fig
@render.plot
def histogram():
return hist_plot()
@session.download(filename=lambda: f"histogram.{input.ext()}")
def download_hist():
with io.BytesIO() as buf:
hist_plot().savefig(buf, format=input.ext())
yield buf.getvalue()
app = App(app_ui, server)- Break up
tidied()reactive into multiple pieces.
solutions/action-transfer/cleaning-data/app.py
from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
import pandas as pd
import janitor
import os
ui_upload = ui.layout_sidebar(
ui.sidebar(
ui.input_file("file", "Data", button_label="Upload..."),
ui.input_text("delim", "Delimiter (leave blank to guess)", ""),
ui.input_numeric("skip", "Rows to skip", 0, min=0),
ui.input_numeric("rows", "Rows to preview", 10, min=1),
),
ui.h3("Raw data"),
ui.output_table("preview1"),
)
ui_clean = ui.layout_sidebar(
ui.sidebar(
ui.input_checkbox("snake", "Rename columns to snake case?"),
ui.input_checkbox("constant", "Remove constant columns?"),
ui.input_checkbox("empty", "Remove empty cols?"),
),
ui.h3("Cleaner data"),
ui.output_table("preview2"),
)
ui_download = ui.row(
ui.column(12, ui.download_button("download", "Download cleaner data", class_="btn-block")),
)
app_ui = ui.page_fluid(
ui_upload,
ui_clean,
ui_download,
)
def server(input: Inputs, output: Outputs, session: Session):
# Upload ----------------------------------
@reactive.calc
def raw():
req(input.file())
delim = None if input.delim() == "" else input.delim()
res = pd.read_csv(
input.file()[0]["datapath"],
delimiter=delim,
skiprows=input.skip())
return res
@render.table
def preview1():
return raw().head(input.rows())
# Clean ------------------------------------
@reactive.calc
def tidied_snake():
out = raw()
if input.snake():
out = out.clean_names(case_type='snake')
return out
@reactive.calc
def tidied_empty():
out = tidied_snake()
if input.empty():
out.dropna(how='all', axis=1, inplace=True)
return out
@reactive.calc
def tidied_constant():
out = tidied_empty()
if input.constant():
out = out.drop_constant_columns()
return out
@render.table
def preview2():
return tidied_constant().head(input.rows())
# Download --------------------------------
@session.download(filename=lambda: f"{os.path.splitext(input.file()[0]['name'])[0]}.tsv")
def download():
yield tidied_constant().to_csv(None, sep="\t", index=False)
app = App(app_ui, server)