-
Notifications
You must be signed in to change notification settings - Fork 85
Getting started
This is an RStudio-internal guide to getting started with early builds of Shiny for Python.
This guide assumes you already know how to build apps using Shiny for R, and you also already know Python. (Future public-facing docs will not assume you already know Shiny for R.)
It is only intended to get you writing basic Shiny apps; our current goal is to get feedback about how the API feels, especially to experienced Python folks.
If you are a venv/virtualenv type of person, feel free to create/activate one before you proceed.
You'll need the shiny
and htmltools
packages, which are not yet on PyPI. Currently, the easiest way to install is to run the following in a new directory (which will also house your app.py
):
curl https://rstudio.github.io/prism/requirements.txt > requirements.txt
python3 -m pip install -r requirements.txt
Create an app.py
file and paste this in:
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("n", "N", 0, 100, 20),
ui.output_text_verbatim("txt", placeholder=True),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.calc()
def r():
return input.n() * 2
@output()
@render_text()
async def txt():
val = r()
return f"n*2 is {val}, session id is {session.id}"
app = App(app_ui, server)
Take a moment to really study the above app.py
code. If you're already very comfortable with Python and Shiny for R, you can probably guess what's happening here.
To run the app, run this command from the shell, in the same directory as app.py
:
shiny run --reload
This should start your app and automatically launch a web browser as well.
The --reload
flag means that file changes in the current directory tree will cause the Python process to reload, so your workflow is 1) save changes to app.py
, 2) reload web browser.
Like Shiny for R, Shiny for Python apps are composed of two parts: a ui
object and a server
function. These are then combined using a ShinyApp
object.
Δ: In R, the
shinyApp()
isn't assigned to a variable, it just needs to be the last expression on the page. In Python, theShinyApp
object needs to be assigned to theapp
variable.
One useful parameter for the ShinyApp
constructor is debug
; set it to True
to see websocket messages emitted to stdout.
Shiny for Python uses a UI paradigm that's carried over mostly intact from R. It's based on a Python port of htmltools
. The two main differences are:
In R, you say fluidPage
; in Python, it's page_fluid
. In R, you say selectInput
; in Python, it's input_select
. (The reversing of the order of adjective and noun is something we would do in R as well, if we could start from scratch today.)
Like in R, htmltools
treat keyword (i.e. named) arguments as attributes:
>>> a(href="help.html")
<a href="help.html"></a>
And positional (i.e. unnamed) arguments as children:
>>> span("Hello", strong("world"))
<span>
Hello
<strong>world</strong>
</span>
Sadly, when calling a function, Python requires all keyword arguments to come after all positional arguments--exactly the opposite of what we would prefer when it comes to HTML.
This works, but it's a little awkward that the href
and class
are stacked at the end instead of the beginning:
>>> a(span("Learn more", class_ = "help-text"), href="help.html")
<a href="help.html">
<span class="help-text">Learn more</span>
</a>
And you can imagine how much worse it would be for, say, a call to panel_tabset
with many lines of code of panel_tab
children, and then the tabset's own attributes (like inputId
) appearing only at the very end.
As a workaround, there's a second way to pass children: use the children
keyword argument.
a(href = "help.html", children = [
span(class_ = "help-text", children = [
"Learn more"
])
])
It's sufficiently clunky that I'd personally reserve it for cases where the children are multiline and there are (other) keyword arguments.
In Shiny for Python, server functions take three arguments: input
, output
, and session
. (TODO: note types)
input.x()
is equivalent to input$x
.
Note that unlike in R, the ()
is necessary to retrieve the value. This aligns the reading of inputs with the reading of reactive values and reactive expressions. It also makes it easier to pass inputs to module server functions.
If you need to access an input by a name that is not known until runtime, you can do that with [
:
input_name <- "x"
input[input_name]() # equivalent to input["x"]()
(We don't currently have a supported/documented way of having type hints when accessing inputs, but we're working on it.)
Define a no-arg function whose name matches a corresponding outputId
in the UI. Then apply a render decorator and the @session.output
decorator.
@session.output()
@render_plot()
def plot1():
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(437)
fig, ax = plt.subplots()
ax.hist(x, session.input["n"], density=True)
return fig
(Note: The order of the decorators is important! We need the session.output()
to be applied last, and in Python, decorators are applied from the bottom up.)
This is equivalent to this R code:
output$plot1 <- renderPlot({
...
})
Reactive expressions are created by defining (no-arg) functions, and adding the @reactive
decorator.
@reactive()
def foo:
return input["x"] + 1
You access the value of this foo
reactive by calling it like a function: foo()
.
TODO: debug=True
in shiny.App
constructor