Workflow

Development workflow

Creating the app

  • On VS code, if you already have your future app.py open, type shinyapp, then press Tab to insert the shiny app snippet.
examples/action-workflow/snippet/app.py
from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui

app_ui = ui.page_fluid(
    ui.input_slider("n", "N", 0, 100, 20),
    ui.output_text_verbatim("txt"),
)


def server(input: Inputs, output: Outputs, session: Session):
    @output
    @render.text
    def txt():
        return f"n*2 is {input.n() * 2}"


app = App(app_ui, server)
Note

shinyapp snippet is available with installation of Shiny extension for VS Code.

Note

It imports more modules from shiny. For example, it imports Inputs, Outputs and Sessions to specify parameter types for server() function. It also imports req module that will be covered in later chapters.

Debugging

Tracebacks in Shiny

The following app is to demonstrate how the traceback appears in the console.

examples/action-workflow/traceback/app.py
from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
from pydataset import data
from matplotlib import pyplot as plt

cars = data('cars')

def f(x):
    return g(x)

def g(x):
    return h(x)

def h(x):
    return x**2

app_ui = ui.page_fluid(
    ui.input_select("n", "N", list(range(1, 11))),
    ui.output_plot("plot"),
)


def server(input: Inputs, output: Outputs, session: Session):
    @output
    @render.plot
    def plot():
        n = f(input.n())
        return plt.scatter(cars[:n]['speed'], cars[:n]['dist'])

app = App(app_ui, server)
Note

Instead of x * 2 in R example in the book, I used x**2 as return value of function h(x) to cause an error when function f() is called in the app. If we write def h(x): return x * 2, it will cause an error not when calling f() but when slicing data frame with cars[:n].

If you run this app, you’ll see an error message in the app and a traceback in the console:

Note

I masked my work directory path with {...} because it will be different from your machine.

Traceback (most recent call last):
  File "{...}/.venv/lib/python3.11/site-packages/shiny/session/_session.py", line 1025, in output_obs
    message[output_name] = renderer_fn()
                           ^^^^^^^^^^^^^
  File "{...}/.venv/lib/python3.11/site-packages/shiny/render/transformer/_transformer.py", line 407, in __call__
    return run_coro_sync(self._run())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "{...}/.venv/lib/python3.11/site-packages/shiny/_utils.py", line 304, in run_coro_sync
    coro.send(None)
  File "{...}/.venv/lib/python3.11/site-packages/shiny/render/transformer/_transformer.py", line 322, in _run
    ret = await self._transformer(
          ^^^^^^^^^^^^^^^^^^^^^^^^
  File "{...}/.venv/lib/python3.11/site-packages/shiny/render/_render.py", line 159, in PlotTransformer
    x = await resolve_value_fn(_fn)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "{...}/.venv/lib/python3.11/site-packages/shiny/render/transformer/_transformer.py", line 802, in resolve_value_fn
    return value_fn()
           ^^^^^^^^^^
  File "{...}/examples/action-workflow/traceback/app.py", line 26, in plot
    n = f(input.n())
        ^^^^^^^^^^^^
  File "{...}/examples/action-workflow/traceback/app.py", line 8, in f
    return g(x)
           ^^^^
  File "{...}/examples/action-workflow/traceback/app.py", line 11, in g
    return h(x)
           ^^^^
  File "{...}/examples/action-workflow/traceback/app.py", line 14, in h
    return x**2
           ~^^~
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Note

In contrast to Shiny for R, Shiny for Python shows trackback in a sequence of calls.

To fix error, you would want to do type casting like n = f(int(input.n())).

examples/action-workflow/traceback/correct-app.py
from matplotlib import pyplot as plt

cars = data('cars')

def f(x):
    return g(x)

def g(x):
    return h(x)

def h(x):
    return x**2

app_ui = ui.page_fluid(
    ui.input_select("n", "N", list(range(1, 11))),
    ui.output_plot("plot"),
)


def server(input: Inputs, output: Outputs, session: Session):
    @output
    @render.plot
    def plot():
        n = f(int(input.n()))
        return plt.scatter(cars[:n]['speed'], cars[:n]['dist'])

app = App(app_ui, server)

Case study

examples/action-workflow/sales-dashboard/app.py
from shiny import App, reactive, render, ui
import pandas as pd

sales = pd.read_csv("sales-dashboard/sales_data_sample.csv", 
                    sep=",", encoding="Latin-1", 
                    na_values=["", "NaN"], keep_default_na=False)
sales = sales[["TERRITORY", "ORDERDATE", "ORDERNUMBER", "PRODUCTCODE", 
               "QUANTITYORDERED", "PRICEEACH"]]

app_ui = ui.page_fluid(
    ui.input_select("territory", "territory", choices=list(sales["TERRITORY"].unique())),
    ui.output_table("selected"),
)


def server(input, output, session):
    @reactive.calc
    def selected():
        return sales[sales["TERRITORY"]==input.territory()]

    @output(id="selected")
    @render.table
    def print_selected():
        return selected()[:11]

app = App(app_ui, server)

Debugging reactivity

In Shiny for R, to use print debugging for reactives, it is recommended to send to message to “standard error” by using message() instead of “standard output” by using print(). Messages that are printed to the standard error stream are recorded in the log.

Caution

I actually do not know whether the “standard output” vs “standard error” argument still holds for Shiny for Python. I hope to come back to this once I deploy a Shiny app elsewhere.

This article shows “3 ways to print to the standard error stream in Python”:

  • Call built-in print() function with file=sys.stderr
  • Call stderr.write() function in sys package
  • Use logging package

First, let us see an example of using the first two. In the code below, side effect _() uses print() with file=sys.stderr, and the reactive expression total() uses sys.stderr.write().

examples/action-workflow/print-debugging/app.py
from shiny import App, ui, render, reactive
import sys

app_ui = ui.page_fluid(
    ui.input_slider("x", "x", value=1, min=0, max=10),
    ui.input_slider("y", "y", value=2, min=0, max=10),
    ui.input_slider("z", "z", value=3, min=0, max=10),
    ui.output_text("total"),
)

def server(input, output, session):
    @reactive.effect
    @reactive.event(input.x)
    def _():
        print(f"Updating y from {input.y()} to {input.x() * 2}", file=sys.stderr)
        ui.update_slider("y", value=input.x()*2, session=session)
    
    @reactive.calc
    def total():
        total = input.x() + input.y() + input.z()
        sys.stderr.write(f"New total is {total}\n")
        return total
    
    @output(id="total")
    @render.text
    def print_total():
        return total()
    
app = App(app_ui, server)
Note

When using sys.stderr.write(), please include newline character \n at the end of message.

Note

Dynamic UI implementation with ui.update_*() in the server function will be discussed in later chapter.

The third option, logging, may provide more flexibility including setting the threshold of logging, e.g. 'INFO', 'WARNING', etc.

examples/action-workflow/print-debugging/app-logging.py
from shiny import App, ui, render, reactive
import logging

# configure logging
logging.basicConfig(format='%(message)s')
log = logging.getLogger('shiny.debugging')
log.setLevel('INFO')

app_ui = ui.page_fluid(
    ui.input_slider("x", "x", value=1, min=0, max=10),
    ui.input_slider("y", "y", value=2, min=0, max=10),
    ui.input_slider("z", "z", value=3, min=0, max=10),
    ui.output_text("total"),
)

def server(input, output, session):
    @reactive.effect
    @reactive.event(input.x)
    def _():
        log.info(f"Updating y from {input.y()} to {input.x() * 2}")
        ui.update_slider("y", value=input.x()*2, session=session)
    
    @reactive.calc
    def total():
        total = input.x() + input.y() + input.z()
        log.info(f"New total is {total}")
        return total
    
    @output(id="total")
    @render.text
    def print_total():
        return total()
    
app = App(app_ui, server)