Lecture 11: Python Apps without Server

BAA1028 - Workflow & Data Management

Damien Dupré

Python Apps without Server

Enter the world of interactive Python in the browser. Running code directly in the browser opens the door for many new and exciting uses for data science on the web.

This is thanks to the amazing technology brought to you by Pyodide. But first, let’s talk about WebAssembly.

What is WebAssembly?

  • WebAssembly is a binary instruction format designed with safety in mind
    • Containerization/sandboxing (isolated “user-space” environments)
  • It has “near-native execution speed” in-browser or on system
  • Available in most web browsers

The logo for WebAssembly

WebAssembly

WebAssemby is a low-level, high-performance binary instruction format that allows code to run in web browsers.

WebAssembly

Python has had a browser solution for a while. Pyodide compiles the Python interpreter CPython to WebAssembly using Emscripten, making it possible to run Python in the browser.

It is possible to run python code directly in a web browser without the need for traditional servers to execute the code. All that is required is a web server such as GitHub Pages or Netlify.

WebAssembly and Python: Pyodide

  • Pyodide is a version of the Python interpreter built for WebAssembly.
  • Features a robust seamless Javascript ⟺ Python foreign function interface.
  • Allows for Python code to be directly run in a web browser, without a Python server.

Pyodide REPL

🔗 https://pyodide.org/en/stable/console.html

JupyterLab

🔗 JupyterLite’s JupyterLab Version

JupyterNotebook

🔗 JupyterLite’s JupyterNotebook Version

Wait, what is a “server”?

A server is a type of computer that is operating 24/7 on the internet that is interacting with your own computer.

We can think of servers in two ways:

  1. Compute
  2. Web

Note

There are more types of servers available; but, our discussion rests solely on those two.

Data Science with Web Servers

Note

We can substitute the R logo with Python’s in these diagrams.

How many Python packages are available?

Outside of the Python packages built-in to Pyodide, the number of Python packages varies as there is no central repository.

  • If a Python package is “pure” (*py3-none-any.whl), then the package can be used as-is.
  • Otherwise, the packages must be compiled for Pyodide under specific Python and Emscripten versions.
    • e.g. *-cp310-cp310-emscripten_3_1_27_wasm32.whl

How to use Pyodide

Install the package:

npm i pyodide

Import the pyodide class:

const { loadPyodide } = require("pyodide");
let pyodide = await loadPyodide();
await pyodide.runPythonAsync("1+1");

What if you don’t know JavaScript? 😅

pyodide aims to be as quick and easy to use as possible for those familiar with JavaScript web development, which raises the question… what if you don’t know Javascript?

Quarto and Quarto Live

Introduction to Quarto

  • Next generation publishing system.
  • Unify and extends the R Markdown ecosystem.
  • Develop and Switch formats without hassle.

Anatomy of a Quarto document

---
title: "quarto demo"
format: 
  html:
    code-fold: true
---

## Meet Quarto

Quarto enables you to weave together content and executable code into a finished document. To learn more about Quarto see <https://quarto.org>.

```{python}
#| label: plot-penguins
#| echo: false
#| message: false
#| warning: false

import seaborn as sns
import matplotlib.pyplot as plt
from palmerpenguins import load_penguins

penguins = load_penguins()
custom_palette = {"Adelie": "darkorange", "Chinstrap": "purple", "Gentoo": "cyan4"}

plt.figure(figsize=(8, 6))
sns.scatterplot(
    data=penguins, 
    x="flipper_length_mm", 
    y="bill_length_mm", 
    hue="species", 
    style="species", 
    palette=custom_palette
)

plt.title("Flipper and Bill Length")
plt.suptitle("Dimensions for Penguins at Palmer Station LTER", fontsize=10)
plt.xlabel("Flipper Length (mm)")
plt.ylabel("Bill Length (mm)")
plt.legend(title="Penguin Species")

sns.set_style("whitegrid")

plt.show()
```

Quarto extensions

Quarto extensions let you customize and extend Quarto’s functionality beyond its built-in features.

  • Enable New Output Formats
  • Create custom templates for reports, presentations, or interactive documents, Custom Layouts & Themes
  • Design unique styling and layouts for HTML, PDF, or Word outputs. Custom Shortcodes & Filters
  • Modify how Quarto processes content, such as adding new syntax or automating formatting

Quarto extensions

For example, there’s a fontawesome extension.

Install the extension:

Terminal
quarto add quarto-ext/fontawesome

Quarto extensions

Add to your Quarto document YAML heading:

example.qmd
---
title: "quarto demo"
format: 
  html:
    code-fold: true
    filters:
      - fontawesome
---

Use the extension in the doc:

{{< fa brands github >}}

Results in:

Quarto extensions

Quarto extension gallery by Mickaël Canouil

Quarto live

Quarto Live allows you to embed WebAssembly-powered code blocks and exercises in Quarto documents through the quarto-live extension. There’s support for Python via Pyodide.

Features include interactive code blocks, exercises with hints and solutions, and rich output like plots. And most importantly, web development skills are not needed to use Pyodide in your content.

Introduction to Quarto Live

Quarto Live embeds WebAssembly powered code blocks and exercises for Python into Quarto documents with HTML-based output formats.

After a short loading period, a Run code button will appear. Python code can be edited and executed in an editor like this one.

How to install Quarto Live

Install the extension:

Terminal
quarto add r-wasm/quarto-live

Add to your Quarto document YAML header:

example.qmd
---
format: live-html
---

How to install Quarto Live

Install the extension:

Terminal
quarto add r-wasm/quarto-live

Add to your Quarto document YAML header:

example.qmd
---
format: live-html
engine: python3
---

{{< include ./_extensions/r-wasm/live/_python3.qmd >}}

How to use Quarto Live

  • Place code in a pyodide code chunk for Python
example.qmd
```{pyodide}
for x in range(1, 6):
    print(x ** 2)
```

{quarto-pyodide}: In Action

{quarto-pyodide} Extension in Action

Use cases

Shiny and Shinylive

Introduction to Shiny

Shiny allows you to create web apps, no web development skills required.

Components

Each Shiny app consists of:

  1. Inputs
  2. Outputs
  3. Instructions on how to build outputs from inputs

Reactivity

When an input changes, Shiny reacts by rebuilding the outputs that depend on it, and only those outputs.

Spreadsheet Analogy

  1. Inputs
  2. Outputs
  3. Instructions on how to build outputs from inputs

Spreadsheet Analogy

Updates:

  1. When an input changes
  2. Only the parts of the app that depend on the input

Key features of reactivity

  • Easy enough to use for prototypes
  • Efficient enough to handle complexity
  • Scales to build production quality apps

Inputs

Notice:

  • Inputs all begin with ui.Input_.
  • Every input requires:
    1. An id for the value to collect
    2. A label to display
    3. Input specific options

Outputs

Notice:

  • Outputs are created by a @render. decorator
  • Pass the decorator a set of instructions for building the output (in the form of a function that returns the contents of the output)

Python has a convenient syntax for decorators. So these would do the same thing:

decorator(function, args)


@decorator(args)
function

To make an app reactive…

Use an input value to build an output.

To access an input’s value, call input.<id>() where <id> is the string you passed to the input, e.g:

ui.input_slider(
    id="n", 
    label="Choose n", 
    min=0, 
    max=100, 
    value=20
)

Value:

input.n()

Traditional Shiny architecture

The traditional way of deploying Shiny involves in a separate server and client. A server is a machine, connected to the Internet, that runs 24/7, ready to run your Shiny app. The server runs Python and Shiny, and clients connect via the web browser.

Each client keeps an open websocket connection as long as they are using the application.

Shiny Express

Shiny Express is designed to make it significantly easier to get started with Shiny, and to write simple apps with a minimum of boilerplate.

Here’s what a simple “Hello, World” app looks like in Shiny Express:

from shiny.express import input, render, ui

ui.input_text("name", "What's your name?", value="World")

@render.text
def greeting():
    return f"Hello, {input.name()}!"

Shiny Express

from shiny import render, ui
from shiny.express import input

ui.panel_title("Hello Shiny!")
ui.input_slider("n", "N", 0, 100, 20)

@render.text
def txt():
    return f"n*2 is {input.n() * 2}"

Shiny Express

Ui and server do not have to be separate in Shiny Express

from shiny import render, ui
from shiny.express import input

ui.panel_title("Hello Shiny!")
ui.input_slider("n", "N", 0, 100, 20)

@render.text
def txt():
    return f"n*2 is {input.n() * 2}"

ui.panel_title("Hello again Shiny!")
ui.input_slider("z", "Z", 50, 100, 70)

@render.text
def txt2():
    return f"Z*3 is {input.z() * 3}"

Shiny Express

The ui.input_text() function creates a text input, and the @render.text decorator makes the output of the greeting() function appear on the page.

After installing Shiny with pip install shiny, you can save this code as app.py and start the app with shiny run app.py.

Or, skip the install and try the app in the online editor.

Shiny Express Dashboards

Shiny Express Dashboards

from shiny import reactive
from shiny.express import input, render, ui
from shinywidgets import render_plotly

ui.page_opts(title="Penguins dashboard", fillable=True)

with ui.sidebar():
    ui.input_selectize(
        "var", "Select variable",
        ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "year"]
    )
    ui.input_numeric("bins", "Number of bins", 30)

@reactive.calc
def df():
    from palmerpenguins import load_penguins
    return load_penguins()[input.var()]

with ui.card(full_screen=True):
    @render_plotly
    def hist():
        import plotly.express as px
        p = px.histogram(df(), nbins=input.bins())
        p.layout.update(showlegend=False)
        return p

Shiny Express Dashboards

While Shiny Express is easy to pick up, it’s powered by Shiny’s proven reactive programming framework, extensive (and growing) suite of components, and deeply customizable approach to UI—just in a more approachable syntax that minimizes the fuss. It’s suitable for writing everything from throwaway prototypes, to realtime dashboards, to cutting edge model demos, to production-quality business workflow apps.

Shiny Express in Quarto Dashboards

Shiny Express syntax is now supported within Quarto Dashboards! This makes it even easier to create interactive data dashboards with Shiny and Quarto.

---
title: "Palmer Penguins"
format: dashboard
server: shiny
---

```{python}
#| context: setup
import seaborn as sns
from shiny.express import render, ui, input
penguins = sns.load_dataset("penguins")
```

# {.sidebar}

```{python}
species = list(penguins["species"].value_counts().index)
ui.input_checkbox_group(
    "species", "Species:",
    species, selected = species
)
```

# Plots

```{python}
@render.plot
def depth():
    data = penguins[penguins["species"].isin(input.species())]
    return sns.displot(
        data, x = "bill_depth_mm",
        hue = "species", kind = "kde",
        fill = True
    )
```

Shiny Express in Quarto Dashboards

Hosting Shiny apps

There are options for hosting Shiny apps, i.e., hosting their servers, you could have a Server on your laptop. But say you need scalability or additional security for your app, you could pay Posit money to have them host your servers for you with Posit Connect.

There are also cloud-based solutions like shinyapps.io and Posit Connect. There’s a spectrum in terms of number of users, for some, it might just be a few people and for others, we’re talking thousands.

Hosting Shiny apps

Some web services offer scalability, features, and cost:

But they can’t run traditional Shiny apps!

This is static web hosting. And it’s great in terms of reach and affordabilty. You don’t have to think about servers anymore, you just give them your app files. But, there’s no way to run Shiny apps or dynamic Python code.

Shinylive uses Pyodide to enable fully in-browser Shiny apps without a backend. It offers an online editor, conversion of existing Shiny apps, and embedding Shiny apps in Quarto documents.

Introduction to Shinylive

With Shinylive, anybody can create their own serverless Shiny apps to run in the browser.

#| standalone: true
#| viewerHeight: 500

library(shiny)
library(bslib)

ui <- page_sidebar(
  title = "Simple Shiny App",
  theme = bs_theme(bootswatch = "flatly"),
  
  sidebar = sidebar(
    numericInput("number", "Enter a number:", value = 5, min = 1, max = 100),
    selectInput("color", "Choose color:", 
                choices = c("red", "blue", "green", "purple"))
  ),
  
  card(
    card_header("Result"),
    card_body(
      plotOutput("plot")
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    plot(1:input$number, 
         col = input$color,
         pch = 19,
         cex = 2,
         main = "Simple Plot",
         xlab = "Index",
         ylab = "Value")
  })
}

shinyApp(ui, server)

Shinylive architecture

Source

All you need to run Shinylive is one of these static web servers. When an application is deployed with Shinylive, Python and Shiny run in the web browser: the browser is effectively both the client and server for the application.

How to use Shinylive

  1. Shinylive online editor
  2. Convert a Shiny app
  3. Embed a Shiny app with Quarto

How to use Shinylive

Link

There’s an online Shinylive editor for Python. The editor is on the left, so you can change the app in real time. Build it live! You can share apps via the share button, with a URL that you can sent to someone else.

How to use Shinylive

To convert a Shiny app (see Python docs), install the package:

Terminal
pip install shinylive
shinylive export myapp site

Converting the app takes an app in the my app directory and produces an output directory site and then you can load the files to a static web hosting service.

Start a web server for the directory:

Terminal
cd site
python -m http.server 8000

If you want to preview the app, you can run this and it’ll open it up in a web browser.

How to use Shinylive

To embed a Shiny app with Quarto, install the Quarto extension:

Terminal
quarto add quarto-ext/shinylive

Add to your Quarto document YAML heading:

example.qmd
filters:
  - shinylive

Insert a code block with {shinylive-python}

example.qmd
```{shinylive-python}
#| standalone: true

/* App code here */

```

Example: Shiny for Python docs

Example: Claus Wilke’s Color Picker App

Example: Shiny Assistant

Benefits of web-based Python

  • Share interactive reports and dashboards easily
  • No installation required for the user
  • Works on any device (tablets, Chromebooks)

Pyodide shines in accessibility. Your content can be hosted on any static web hosting service and you can share with just a URL. Users can run code without installing software. It can be run on tablets or mobile. In terms of ease of deployment, you don’t need a supporting computational python server. You can host your code or app on static web hosting services like GitHub Pages.

Limitations and considerations

  • Under active development and things might change
  • Some packages are not yet supported in WebAssembly
    • Some packages may depend on network access and may not work
  • Security considerations (everything runs client-side)
  • Performance limitations for large datasets and packages or intensive tasks

No secrets with a Shinylive app. All the source code goes to client, all the data goes to the client, don’t include credentials, don’t include sensitive data.

Best practices

  • Keep computations lightweight
  • Structure code efficiently
  • Manage dependencies
  • Choose the right tool for the job

People Behind

References

Huge thanks the following people who have generated and shared most of the content of this lecture:


Thanks for your attention and don’t hesitate to ask if you have any questions!
@damien_dupre
@damien-dupre
https://damien-dupre.github.io
damien.dupre@dcu.ie