GitHub

Example: Showcase

Reactive state, derived values and a server-backed search on one page.

  • 70 lines of Python
  • 1 page
  • 1 server function
  • 929 B page JS (gzip)
This example needs its server (database, sessions or server functions). Run it locally:
Terminal
pyweb dev examples/showcase/app.pyweb

#Source

examples/showcase/app.pyweb
"""One page, every layer: reactive state, derived values, server search."""

from pyweb import App, server

app = App(title="PyWeb showcase", stylesheets=["/static/style.css"])

PRODUCTS = [
    {"name": "Keyboard", "price": 89},
    {"name": "Monitor", "price": 249},
    {"name": "Mouse", "price": 39},
    {"name": "Headphones", "price": 129},
    {"name": "Webcam", "price": 69},
]


@server
def search_products(q: str) -> list:
    q = q.strip().lower()
    return [p for p in PRODUCTS if q in p["name"].lower()]


@app.page("/")
def Home():
    count = 0
    price = 100
    quantity = 2
    total = price * quantity
    q = ""
    results = search_products("")

    def increment():
        count += 1

    def reset():
        count = 0

    def search():
        results = search_products(q)

    <main class="wrap">
        <nav class="nav">
            <span class="logo">PyWeb</span>
            <span class="tag">Python from browser to database</span>
        </nav>
        <section class="hero">
            <h1>One language. Every layer.</h1>
            <p>Plain Python variables become reactive state. Server functions become typed RPC.</p>
        </section>
        <section class="card">
            <h2>Reactive counter</h2>
            <button id="inc" onclick={increment} class="btn">Count: {count}</button>
            <button onclick={reset} class="btn ghost" disabled={count == 0}>Reset</button>
            <p class="hint">count is a signal: only this button's text node updates.</p>
        </section>
        <section class="card">
            <h2>Derived values</h2>
            <label>Quantity <input id="qty" type="number" bind={quantity} /></label>
            <p id="total">Total: ${total}</p>
            <p class="hint">total = price * quantity recomputes when quantity changes.</p>
        </section>
        <section class="card">
            <h2>Server search</h2>
            <input id="q" bind={q} oninput={search} placeholder="Search products…" />
            <ul id="results">
                for p in results:
                    <li>{p["name"]} <span class="hint">${p["price"]}</span></li>
            </ul>
            <p class="hint">Each keystroke calls search_products() on the server over RPC.</p>
        </section>
    </main>

#What runs where

Output of pyweb inspect: every page variable, handler and server function, where it runs and why.

Output
page Home  route=/
  browser  count          reactive state: assigned in increment(); literal initial value
  browser  price          constant
  browser  quantity       reactive state: bound to an input (line 57); literal initial value
  browser  total          derived from quantity; recomputed when they change
  browser  q              reactive state: bound to an input (line 63); literal initial value
  browser  results        reactive state: assigned in search(); initial value computed on the server and sent
  browser  increment      event handler (compiled to JavaScript)
  browser  reset          event handler (compiled to JavaScript)
  browser  search         event handler (compiled to JavaScript)
rpc POST /__pyweb/rpc/search_products  (q: str) -> list

#Generated JavaScript

The browser modules the compiler wrote (before minification). They import the shared runtime.

JavaScript
// Home.js
import { h as $h, t as $t, dyn as $dyn, list as $list, when as $when, signal as $signal, computed as $computed, mount as $mount, onMount as $onMount, py as $py, rpc as $rpc, subscribe as $subscribe, slot as $slot, onCleanup as $onCleanup, live as $live } from "./runtime.js";
function Home($s) {
  const count = $signal("count" in $s ? $s["count"] : 0);
  const price = 100;
  const quantity = $signal("quantity" in $s ? $s["quantity"] : 2);
  const total = $computed(() => $py.mul(price, quantity()));
  const q = $signal("q" in $s ? $s["q"] : "");
  const results = $signal("results" in $s ? $s["results"] : null);
  function increment() {
    count($py.add(count(), 1));
  }
  function reset() {
    count(0);
  }
  async function search() {
    results((await $rpc("search_products", {"q": q()})));
  }
  return [$h("main", {"class": "wrap"}, () => [
      $h("nav", {"class": "nav"}, () => [
        $h("span", {"class": "logo"}, () => [$t("PyWeb")]),
        $h("span", {"class": "tag"}, () => [$t("Python from browser to database")])
      ]),
      $h("section", {"class": "hero"}, () => [
        $h("h1", null, () => [$t("One language. Every layer.")]),
        $h("p", null, () => [$t("Plain Python variables become reactive state. Server functions become typed RPC.")])
      ]),
      $h("section", {"class": "card"}, () => [
        $h("h2", null, () => [$t("Reactive counter")]),
        $h("button", {"id": "inc", "onclick": increment, "class": "btn"}, () => [
          $t("Count: "),
          $dyn(() => count())
        ]),
        $h("button", {"onclick": reset, "class": "btn ghost", "disabled": () => (count() === 0)}, () => [$t("Reset")]),
        $h("p", {"class": "hint"}, () => [$t("count is a signal: only this button's text node updates.")])
      ]),
      $h("section", {"class": "card"}, () => [
        $h("h2", null, () => [$t("Derived values")]),
        $h("label", null, () => [
          $t("Quantity "),
          $h("input", {"id": "qty", "type": "number", "$bind": quantity})
        ]),
        $h("p", {"id": "total"}, () => [
          $t("Total: $"),
          $dyn(() => total())
        ]),
        $h("p", {"class": "hint"}, () => [$t("total = price * quantity recomputes when quantity changes.")])
      ]),
      $h("section", {"class": "card"}, () => [
        $h("h2", null, () => [$t("Server search")]),
        $h("input", {"id": "q", "$bind": q, "oninput": search, "placeholder": "Search products\u2026"}),
        $h("ul", {"id": "results"}, () => [$list(() => results(), (p) => [$h("li", null, () => [
              $t($py.text($py.at(p, "name"))),
              $t(" "),
              $h("span", {"class": "hint"}, () => [
                $t("$"),
                $t($py.text($py.at(p, "price")))
              ])
            ])])]),
        $h("p", {"class": "hint"}, () => [$t("Each keystroke calls search_products() on the server over RPC.")])
      ])
    ])];
}
$mount("Home", Home);
Edit this page on GitHub