Routing
PyView uses Starlette’s routing under the hood—no need to learn a new system if you’re already familiar with it. You get flexible URL patterns with automatic type conversion for path parameters.
Basic Route Registration
Section titled “Basic Route Registration”Register LiveViews with add_live_view():
from pyview import PyViewfrom views.counter import CounterLiveViewfrom views.users import UserListLiveView, UserDetailLiveView
app = PyView()
app.add_live_view("/", CounterLiveView)app.add_live_view("/users", UserListLiveView)app.add_live_view("/users/{id}", UserDetailLiveView)Path Parameters
Section titled “Path Parameters”Use curly braces to capture URL segments. Starlette’s converters handle type conversion automatically:
# Matches /users/123, /users/456, etc.app.add_live_view("/users/{id:int}", UserDetailLiveView)
# Matches /posts/hello-world, /posts/my-first-post, etc.app.add_live_view("/posts/{slug:str}", PostLiveView)
# Matches /files/documents/report.pdf (captures full path)app.add_live_view("/files/{path:path}", FileLiveView)Accessing Path Parameters
Section titled “Accessing Path Parameters”Path parameters are automatically merged with query parameters and passed to handle_params(). Use typed parameters for clean access:
class UserDetailLiveView(LiveView[UserContext]): async def mount(self, socket: LiveViewSocket[UserContext], session): socket.context = {"user": None}
async def handle_params(self, socket: LiveViewSocket[UserContext], id: int): # 'id' is already converted to int from the URL /users/123 user = await load_user(id) socket.context["user"] = userPath parameters take precedence over query parameters if there’s a name conflict.
Multiple Path Parameters
Section titled “Multiple Path Parameters”# Route: /orgs/{org}/projects/{project_id}app.add_live_view("/orgs/{org}/projects/{project_id}", ProjectLiveView)
class ProjectLiveView(LiveView[ProjectContext]): async def handle_params(self, socket, org: str, project_id: int): project = await load_project(org, project_id) socket.context["project"] = projectRoute Priority
Section titled “Route Priority”When multiple routes could match a URL, static routes take precedence over parameterized routes:
app.add_live_view("/users/new", NewUserLiveView) # Static - matched firstapp.add_live_view("/users/{id}", UserDetailLiveView) # ParameterizedA request to /users/new goes to NewUserLiveView, while /users/123 goes to UserDetailLiveView.
Trailing Slashes
Section titled “Trailing Slashes”PyView handles trailing slashes gracefully. Both /users and /users/ will match a route registered as /users.
Combining with Query Parameters
Section titled “Combining with Query Parameters”Path and query parameters work together seamlessly:
# URL: /users/123?tab=settings&mode=edit# Route: /users/{id:int}
class UserDetailLiveView(LiveView[UserContext]): async def handle_params(self, socket, id: int, tab: str = "profile", mode: str = "view"): # id = 123 (from path) # tab = "settings" (from query) # mode = "edit" (from query) socket.context.update({ "user": await load_user(id), "active_tab": tab, "edit_mode": mode == "edit" })Static Files
Section titled “Static Files”Mount static files separately using Starlette’s StaticFiles:
from starlette.staticfiles import StaticFiles
app = PyView()
# Serve files from ./static at /static URLapp.mount("/static", StaticFiles(directory="static"), name="static")
# Or from package resourcesapp.mount( "/static", StaticFiles(packages=[("myapp", "static")]), name="static")Route Organization
Section titled “Route Organization”For larger applications, organize routes in a list:
from pyview import PyViewfrom views import ( HomeLiveView, UserListLiveView, UserDetailLiveView, SettingsLiveView,)
routes = [ ("/", HomeLiveView), ("/users", UserListLiveView), ("/users/{id:int}", UserDetailLiveView), ("/settings", SettingsLiveView),]
app = PyView()
for path, view in routes: app.add_live_view(path, view)Non-LiveView Routes
Section titled “Non-LiveView Routes”You can add regular Starlette routes alongside LiveViews for APIs, webhooks, or static pages:
from starlette.routing import Routefrom starlette.responses import JSONResponse
async def health_check(request): return JSONResponse({"status": "ok"})
app = PyView()app.routes.append(Route("/health", health_check))app.add_live_view("/", HomeLiveView)Custom Root Template
Section titled “Custom Root Template”Customize the HTML shell that wraps your LiveViews:
from pyview import PyView, defaultRootTemplatefrom markupsafe import Markup
css = Markup("""<link rel="stylesheet" href="/static/styles.css"><script src="https://cdn.tailwindcss.com"></script>""")
app = PyView()app.rootTemplate = defaultRootTemplate(css=css)See Socket and Context - Navigation for navigating between routes programmatically.