| name | pulse |
| description | Full-stack Python framework for interactive web apps. Server-driven React UI with WebSocket sync. This skill should be used when building Pulse applications, components, state management, data fetching, routing, forms, or real-time features. |
Pulse Framework
Full-stack Python web framework. Server renders React components over WebSocket. All logic runs in Python—no JavaScript required.
Import Convention
Canonical Pulse style: import pulse as ps, then access everything from ps. The only modules with exports not available through pulse are pulse.js (React/JS placeholders for transpilation) and pulse.transpiler.
Quick Start
import pulse as ps
class Counter(ps.State):
count: int = 0
def increment(self): self.count += 1
@ps.component
def App():
with ps.init():
state = Counter()
return ps.div(
ps.button("Click", onClick=state.increment),
ps.span(f"Count: {state.count}"),
)
app = ps.App([ps.Route("/", App)])
pulse run app.py → dev server on :8000
Quick Reference
| Task | API | Section |
|---|
| Create component | @ps.component | Core Concepts |
| Preserve state | with ps.init(): state = MyState() | Hooks |
| One-time setup | ps.setup(fn, *args) | Hooks |
| Inline state | ps.state(lambda: State(), key=...) | Hooks |
| Effects | @ps.effect decorator | Hooks |
| DOM refs | ps.ref() | Refs |
| Route info | ps.route() | Navigation |
| Navigate | ps.navigate(path, force=False), ps.Link | Navigation |
| Forms | ps.Form(on_submit=handler) | Forms |
| Data fetching | @ps.query on State method | Data Fetching |
| Mutations | @ps.mutation on State method | Data Fetching |
| Global state | @ps.global_state decorator | State |
| Lists | ps.For(items, lambda item, i: ...) | Rendering |
| Conditionals | ps.If(cond, then=..., else_=...) | Rendering |
| Session data | ps.session() | Runtime |
| WebSocket ID | ps.websocket_id() | Runtime |
| NPM packages | ps.require("package") | JS Interop |
Core Concepts
Components
Functions decorated with @ps.component. Return VDOM elements. Called on every render.
@ps.component
def Greeting(name: str):
return ps.h1(f"Hello, {name}!")
Rules:
- Always use
@ps.component decorator
- Props are function parameters
- Use
key= kwarg for list reconciliation
- Components can nest other components
HTML Elements
All standard HTML/SVG tags available as ps.<tag>():
ps.div(*children, className="", style=ps.CSSProperties(...), onClick=handler, ...)
ps.input(value=val, onChange=handler, type="text", placeholder="...")
ps.button(*children, onClick=handler, disabled=False, ...)
ps.a(*children, href="...", target="_blank", ...)
ps.img(src="...", alt="...", width=100, ...)
ps.form(*children, onSubmit=handler, ...)
Syntax variants:
ps.div(ps.h1("Title"), ps.p("Body"))
ps.div(className="container")[ps.h1("Title"), ps.p("Body")]
ps.div(className="wrap")[ps.span("text")]
Common props: className, style, id, key, onClick, onChange, onSubmit, disabled, placeholder, value, checked, href, src, alt
State
Class inheriting from ps.State. Attributes become reactive—changes trigger re-renders.
class TodoState(ps.State):
items: list[str] = []
draft: str = ""
def add(self):
if self.draft.strip():
self.items.append(self.draft)
self.draft = ""
def remove(self, idx: int):
self.items.pop(idx)
@ps.computed
def count(self) -> int:
return len(self.items)
@ps.effect
def log_changes(self):
print(f"Items: {self.count}")
Initialize in components with ps.init():
@ps.component
def TodoApp():
with ps.init():
state = TodoState()
return ps.div(...)
Rules:
- Define fields with type annotations and defaults
- Methods mutate state directly (
self.field = value)
@ps.computed for derived values (cached, auto-updates)
@ps.effect for side effects (runs when dependencies change)
on_dispose() method for cleanup when component unmounts
See references/state.md for computed properties, effects on State, and global state patterns.
Refs
Server-side handles for imperative DOM operations. Commands sent over WebSocket.
@ps.component
def AutoFocus():
input_ref = ps.ref(on_mount=lambda: input_ref.focus())
return ps.input(ref=input_ref, placeholder="Auto-focused")
Fire-and-forget: focus(), blur(), click(), submit(), reset(), scroll_to(), scroll_into_view(), select()
Request-response (async): measure(), get_value(), set_value(), get_prop(), set_prop(), get_attr(), set_attr(), get_text(), set_text(), set_style()
Lifecycle: wait_mounted(), on_mount, on_unmount, mounted property
See references/refs.md for full API.
Hooks
ps.init(*, key=None) — Preserve state across renders
@ps.component
def MyComponent():
with ps.init():
state = MyState()
other = OtherState()
@ps.component
def UserView(user_id: str):
with ps.init(key=user_id):
state = UserState(user_id)
return ps.div(state.name)
ps.setup(fn, *args) — One-time initialization with args
def create_api(user_id: int):
return {"user_id": user_id}
@ps.component
def UserView(user_id: int):
meta = ps.setup(create_api, user_id)
return ps.div(f"User: {meta['user_id']}")
ps.state(arg, *, key=None) — Inline state instances
@ps.component
def Counter():
counter = ps.state(CounterState())
return ps.button(f"Count: {counter.count}", onClick=counter.increment)
@ps.component
def UserList(user_ids: list[str]):
items = []
for uid in user_ids:
user = ps.state(lambda uid=uid: UserState(uid), key=uid)
items.append(ps.li(user.name, key=uid))
return ps.ul(*items)
note = ps.state(lambda: NoteState(), key=f"note-v{version}")
@ps.effect — Inline effects (auto-registered in components)
@ps.component
def Timer():
with ps.init():
state = TimerState()
@ps.effect
def log_elapsed():
print(f"Elapsed: {state.elapsed}")
return ps.div(str(state.elapsed))
@ps.component
def ItemTracker(items: list[str]):
for item in items:
@ps.effect(key=item)
def track(item=item):
print(f"Tracking: {item}")
return ps.div(...)
if state.show_timer:
@ps.effect(immediate=True)
def timer_effect():
return lambda: print("Timer stopped")
See references/reactive.md for Effect options (interval, lazy, on_error) and references/hooks.md for inline effect patterns.
Runtime hooks
ps.route()
ps.pulse_route()
ps.session()
ps.session_id()
ps.websocket_id()
ps.navigate(path)
ps.redirect(path)
ps.not_found()
Events
Event handlers receive serialized event dict. Common patterns:
ps.button("Click", onClick=lambda: do_action())
ps.button("Click", onClick=state.method)
ps.input(
value=state.text,
onChange=lambda e: setattr(state, "text", e["target"]["value"]),
)
ps.form(onSubmit=lambda e: handle_submit(e))[...]
ps.input(onKeyDown=lambda e: submit() if e["key"] == "Enter" else None)
ps.div(onClick=lambda e: print(e["clientX"], e["clientY"]))
Event types: MouseEvent, KeyboardEvent, ChangeEvent, FormEvent, FocusEvent, DragEvent, TouchEvent, WheelEvent
Async handlers supported:
async def handle_click():
await api.fetch_data()
state.data = result
ps.button("Load", onClick=handle_click)
Lists & Conditionals
ps.For — Iterate with keys
ps.ul(
ps.For(
items,
lambda item, idx: ps.li(item.name, key=str(item.id)),
)
)
ps.If — Conditional render
ps.If(
user is not None,
then=ps.div(f"Welcome, {user.name}"),
else_=ps.div("Please login"),
)
Inline conditionals
has_items and ps.ul(...)
ps.div(state.loading and "Loading..." or ps.span(state.data))
Routing
app = ps.App([
ps.Layout(
AppLayout,
children=[
ps.Route("/", HomePage),
ps.Route("/users", UsersPage),
ps.Route("/users/:id", UserDetail),
ps.Route("/docs/*", DocsPage),
],
),
])
Layout component:
@ps.component
def AppLayout():
return ps.div(
ps.nav(ps.Link("Home", to="/"), ps.Link("Users", to="/users")),
ps.main(ps.Outlet()),
)
Access route info:
@ps.component
def UserDetail():
route = ps.route()
user_id = route["pathParams"]["id"]
return ps.div(f"User: {user_id}")
Navigation:
ps.Link("Go", to="/path")
ps.navigate("/path")
ps.redirect("/login")
See references/routing.md for layouts, nested routes, and path parameters.
Forms
Declarative form
ps.Form(
ps.input(name="email", type="email"),
ps.input(name="password", type="password"),
ps.button("Submit", type="submit"),
on_submit=handle_submit,
)
def handle_submit(data: ps.FormData):
email = data["email"]
password = data["password"]
Controlled inputs with state
class LoginState(ps.State):
email: str = ""
password: str = ""
async def submit(self):
await api.login(self.email, self.password)
@ps.component
def LoginForm():
with ps.init():
state = LoginState()
return ps.form(onSubmit=lambda e: state.submit())[
ps.input(
value=state.email,
onChange=lambda e: setattr(state, "email", e["target"]["value"]),
),
ps.input(
value=state.password,
type="password",
onChange=lambda e: setattr(state, "password", e["target"]["value"]),
),
ps.button("Login"),
]
See references/forms.md for ManualForm, file uploads, and validation patterns. See references/dom.md for all form elements.
Data Fetching
@ps.query — Cached async data
class UserState(ps.State):
user_id: int = 1
@ps.query
async def user(self) -> dict:
return await api.get_user(self.user_id)
@user.key
def _user_key(self):
return ("user", self.user_id)
@ps.component
def UserProfile():
with ps.init():
state = UserState()
if state.user.is_loading:
return ps.div("Loading...")
if state.user.is_error:
return ps.div(f"Error: {state.user.error}")
user = state.user.data
return ps.div(user["name"])
Query options: stale_time, gc_time, retries, retry_delay, keep_previous_data
Query methods: .refetch(), .invalidate(), .set_data(val), .wait()
Query properties: .data, .error, .status, .is_loading, .is_fetching, .is_success, .is_error
@ps.mutation — Non-cached operations
@ps.mutation
async def update_user(self, name: str) -> dict:
result = await api.update_user(self.user_id, name)
self.user.invalidate()
return result
@update_user.on_success
def _on_success(self, result):
print("Updated:", result)
@update_user.on_error
def _on_error(self, error):
print("Failed:", error)
See references/queries.md for infinite queries, query callbacks, optimistic updates, and caching.
Global State
Share state across components:
@ps.global_state
class AppSettings(ps.State):
theme: str = "light"
def toggle(self):
self.theme = "dark" if self.theme == "light" else "light"
@ps.component
def Header():
settings = AppSettings()
return ps.button(f"Theme: {settings.theme}", onClick=settings.toggle)
App Configuration
app = ps.App(
routes=[...],
middleware=[LoggingMiddleware()],
session_store=ps.CookieSessionStore(secret_key="..."),
server_address="https://app.example.com",
mode="single-server",
)
Commands
uv run pulse run app.py
uv run pulse run app.py --port 3000
uv run pulse run app.py --interrupt
make all
Common Patterns
Counter
class Counter(ps.State):
count: int = 0
def inc(self): self.count += 1
def dec(self): self.count -= 1
@ps.component
def CounterApp():
with ps.init():
s = Counter()
return ps.div(
ps.button("-", onClick=s.dec),
ps.span(s.count),
ps.button("+", onClick=s.inc),
)
Todo List
class Todos(ps.State):
items: list[str] = []
draft: str = ""
def add(self):
if self.draft.strip():
self.items.append(self.draft)
self.draft = ""
def remove(self, idx: int):
self.items.pop(idx)
@ps.component
def TodoApp():
with ps.init():
state = Todos()
return ps.div(
ps.input(
value=state.draft,
onChange=lambda e: setattr(state, "draft", e["target"]["value"]),
placeholder="Add item...",
),
ps.button("Add", onClick=state.add),
ps.ul(
ps.For(
state.items,
lambda item, idx: ps.li(
item,
ps.button("x", onClick=lambda i=idx: state.remove(i)),
key=f"{idx}-{item}",
),
)
),
)
Data Table with Loading
class TableState(ps.State):
page: int = 1
@ps.query
async def data(self) -> list[dict]:
return await api.fetch_page(self.page)
@data.key
def _key(self): return ("data", self.page)
@ps.component
def DataTable():
with ps.init():
state = TableState()
if state.data.is_loading:
return ps.div("Loading...")
return ps.div(
ps.table(
ps.thead(ps.tr(ps.th("Name"), ps.th("Email"))),
ps.tbody(
ps.For(
state.data.data or [],
lambda row, _: ps.tr(
ps.td(row["name"]),
ps.td(row["email"]),
key=str(row["id"]),
),
)
),
),
ps.div(
ps.button("Prev", onClick=lambda: setattr(state, "page", max(1, state.page - 1))),
ps.span(f"Page {state.page}"),
ps.button("Next", onClick=lambda: setattr(state, "page", state.page + 1)),
),
)
Rules & Best Practices
- Always use
@ps.component for component functions
- Always use
ps.init() to preserve State across renders
- Define State fields with type annotations and defaults
- Use
key= on list items for proper reconciliation
- Mutate state directly in methods (
self.field = value)
- Use
@ps.query for data fetching with caching
- Use
ps.Link for internal navigation, not ps.a
- Access route params via
ps.route()["pathParams"]
- Clean up in
on_dispose() method or effect return
- Run
make all before committing
Working with JavaScript
NPM Dependencies
Use ps.require() to declare npm package dependencies:
ps.require("recharts")
ps.require("lodash", version="^4.17.0")
from pulse.transpiler import Import
LineChart = Import("LineChart", "recharts")
Calling Own FastAPI Endpoints
Use ps.call_api() to call your app's FastAPI endpoints from components:
result = await ps.call_api("/api/users", method="GET")
result = await ps.call_api("/api/login", method="POST", body={"email": email})
Executing JavaScript
Use run_js() for imperative JS execution:
from pulse import run_js
from pulse.transpiler import javascript
@javascript
def show_alert(msg: str):
window.alert(msg)
run_js(show_alert("Hello!"))
result = await run_js(get_window_width(), result=True)
Production Configuration
Key ps.App parameters for production:
app = ps.App(
routes=[...],
mode="single-server",
server_address="https://myapp.com",
codegen=ps.CodegenConfig(router="@tanstack/react-router"),
session_store=ps.CookieSessionStore(secret_key="..."),
)
See references/middleware.md for session store options.
Additional References
For advanced topics, see references/ folder:
Core:
state.md — Computed properties, global state, State effects
hooks.md — Inline effects, setup patterns, lifecycle
refs.md — DOM refs, imperative operations, lifecycle
reactive.md — Signal, Computed, Effect, ReactiveDict/List/Set
routing.md — Layouts, nested routes, path parameters
forms.md — ManualForm, file uploads, validation
dom.md — Full HTML elements and events reference
Data & Communication:
queries.md — Query options, infinite queries, mutations
channels.md — Real-time bidirectional communication
sessions.md — Session management, stores, authentication
App Setup:
app.md — App configuration, middleware, production setup
middleware.md — Request middleware, auth patterns
context.md — Runtime context, hooks, call_api
JavaScript Integration:
js-interop.md — React components, JavaScript execution
transpiler.md — Python-to-JS transpiler, Import, syntax
Utilities:
helpers.md — Utilities: ps.later, ps.repeat, CSSProperties
errors.md — Exception handling, debugging
examples.md — Complete working examples