Getting Started
Let’s get you up and running with a real-time web app in about five minutes.
PyView requires Python 3.11 or later. For t-string templates and LiveComponents, you’ll need Python 3.14+.
Quick Start with Cookiecutter
The fastest way to start a new project is with cookiecutter:
cookiecutter gh:ogrodnek/pyview-cookiecutterThis gives you a complete project with:
- A working counter example to play with immediately
- Poetry for dependency management
- Just command runner for common tasks
- Docker setup for deployment
Alternative: Prefer uv and want a nice component library? Try pyview-cookiecutter-uv, which uses uv for dependency management and includes WebAwesome components.
Install and Run
cd myapppoetry installjustVisit http://localhost:8000 and click the buttons. The count updates instantly without page reloads—that’s LiveView in action.
Try adding ?c=10 to the URL to see how handle_params can initialize state from query parameters.
What Just Happened?
- Initial page load: The server renders HTML and sends it to the browser
- WebSocket connection: The JavaScript client connects back to the server
- User clicks a button:
phx-click="increment"sends an event over WebSocket - Server handles event: The event handler updates the context
- Diff sent to client: Only the changed
{{count}}value is sent back - DOM updates: The page updates without a full reload
This is the LiveView pattern: server-rendered HTML with real-time updates over WebSocket.
Project Structure
Here’s what the cookiecutter generated:
myapp/├── src/myapp/│ ├── app.py # Application entry point│ └── views/│ ├── __init__.py│ └── count/│ ├── __init__.py│ ├── count.py # LiveView class│ └── count.html # Template├── tests/├── pyproject.toml # Poetry config├── justfile # Command runner└── Dockerfile # Production deploymentCreating a New View
Let’s add a temperature converter. The just add-view command scaffolds a new view:
just add-view temperatureThis creates src/myapp/views/temperature/ with starter files and automatically exports the view. Open temperature.py and update it:
from pyview import LiveView, LiveViewSocketfrom pyview.events import event, BaseEventHandlerfrom dataclasses import dataclassfrom typing import Optional
@dataclassclass TempContext: celsius: float = 0 fahrenheit: float = 32
class TemperatureLiveView(BaseEventHandler, LiveView[TempContext]): async def mount(self, socket: LiveViewSocket[TempContext], session): socket.context = TempContext()
@event("update_celsius") async def handle_celsius(self, socket: LiveViewSocket[TempContext], value: Optional[float] = None): if value is None: return socket.context.celsius = value socket.context.fahrenheit = round(value * 9/5 + 32, 1)
@event("update_fahrenheit") async def handle_fahrenheit(self, socket: LiveViewSocket[TempContext], value: Optional[float] = None): if value is None: return socket.context.fahrenheit = value socket.context.celsius = round((value - 32) * 5/9, 1)Now update temperature.html:
<div> <h1>Temperature Converter</h1> <div> <label>Celsius</label> <input type="number" value="{{celsius}}" phx-keyup="update_celsius" /> </div> <div> <label>Fahrenheit</label> <input type="number" value="{{fahrenheit}}" phx-keyup="update_fahrenheit" /> </div></div>Register the route in app.py:
from .views import CountLiveViewfrom .views.temperature import TemperatureLiveView
routes = [ ("/", CountLiveView), ("/temperature", TemperatureLiveView),]Restart the server and visit http://localhost:8000/temperature. Type in either field and watch the other update in real-time.
Single-File Apps
For quick experiments, you can put everything in one file using the playground builder:
#!/usr/bin/env -S uv run# /// script# requires-python = ">=3.14"# dependencies = ["pyview-web", "uvicorn"]# ///
from pyview import LiveView, playgroundfrom pyview.template import TemplateViewfrom typing import TypedDictimport uvicorn
class CountContext(TypedDict): count: int
class Counter(TemplateView, LiveView[CountContext]): async def mount(self, socket, session): socket.context = {"count": 0}
async def handle_event(self, event, payload, socket): if event == "increment": socket.context["count"] += 1
def template(self, assigns, meta): return t"""<div> <h1>Count: {assigns['count']}</h1> <button phx-click="increment">+</button> </div>"""
app = playground().with_live_view(Counter).with_title("Counter").build()
if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)Run it with uv run counter.py—no virtual environment setup needed.
See Single-File Apps for more on this approach.
Environment Variables
For production, set a secret key for session security:
export PYVIEW_SECRET="your-secret-key-here"Generate one with:
openssl rand -base64 32Next Steps
- LiveView Lifecycle — Understand mount, handle_event, and handle_params
- Socket and Context — Managing state and real-time features
- Event Handling — Buttons, forms, and user interactions
- Templating — Choose between HTML templates and t-strings
- Routing — URL patterns and path parameters