Introduction
The examples presented below are meant to be executed and explored so you can understand the basic mechanic of trame.
State management
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
state = server.state
# -----------------------------------------------------------------------------
# State setup
# -----------------------------------------------------------------------------
# Creating new entries to the shared state
state.a = 1
state["b"] = 2
# Force state.d to be client side only
state.client_only("b")
# state.trame__client_only += ["b"]
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
# UI helper to extent layout
def create_ui_for_state_var(name):
with html.Div(style="margin: 20px; padding: 20px; border: solid 1px #333;"):
html.Div(
f"Variable \"{name}\" {{{{ {name} }}}} == get({{{{ get('{name}') }}}})",
style="padding: 10px;",
)
with html.Div(style="padding: 10px;"):
html.Button(f"{name}+", click=f"{name} += 1")
html.Button(f"{name}-", click=f"{name} -= 1")
html.Button(f"{name}=5", click=f"{name} = 5")
html.Button(f"set({name}+)", click=f"set('{name}', {name} + 1)")
html.Button(f"set({name}-)", click=f"set('{name}', {name} - 1)")
html.Button(f"set({name}=5)", click=f"set('{name}', 5)")
# Start with some UI to control a
with DivLayout(server) as layout:
create_ui_for_state_var("a")
create_ui_for_state_var("b")
# -----------------------------------------------------------------------------
# State Listener
# -----------------------------------------------------------------------------
@state.change("a", "b")
def state_change(a, b, **kwargs):
print(f"State updated a={a} b={b}")
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
state = server.state
# -----------------------------------------------------------------------------
# State setup
# -----------------------------------------------------------------------------
# Creating new entries to the shared state
state.a = 1
state["b"] = 2
# Force state.d to be client side only
state.client_only("b")
# state.trame__client_only += ["b"]
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
# UI helper to extent layout
def create_ui_for_state_var(name):
with html.Div(style="margin: 20px; padding: 20px; border: solid 1px #333;"):
html.Div(
f"Variable \"{name}\" {{{{ {name} }}}} == get({{{{ get('{name}') }}}})",
style="padding: 10px;",
)
with html.Div(style="padding: 10px;"):
html.Button(f"{name}+", click=f"{name} += 1")
html.Button(f"{name}-", click=f"{name} -= 1")
html.Button(f"{name}=5", click=f"{name} = 5")
html.Button(f"set({name}+)", click=f"set('{name}', {name} + 1)")
html.Button(f"set({name}-)", click=f"set('{name}', {name} - 1)")
html.Button(f"set({name}=5)", click=f"set('{name}', 5)")
# Start with some UI to control a
with DivLayout(server) as layout:
create_ui_for_state_var("a")
create_ui_for_state_var("b")
# -----------------------------------------------------------------------------
# State Listener
# -----------------------------------------------------------------------------
@state.change("a", "b")
def state_change(a, b, **kwargs):
print(f"State updated a={a} b={b}")
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
import time
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
count = 1
def make_server_busy():
time.sleep(1)
def update_title():
global count
server.state.trame__title = f"T({count})"
count += 1
def update_favicon():
global count
server.state.trame__favicon = f"https://picsum.photos/id/{count}/32/32"
count += 10
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
with DivLayout(server) as layout:
html.Div("trame__busy: {{ trame__busy }}")
html.Button("Make server busy", click=make_server_busy)
html.Button("Update title", click=update_title)
html.Button("Update favicon", click=update_favicon)
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
import time
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
count = 1
def make_server_busy():
time.sleep(1)
def update_title():
global count
server.state.trame__title = f"T({count})"
count += 1
def update_favicon():
global count
server.state.trame__favicon = f"https://picsum.photos/id/{count}/32/32"
count += 10
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
with DivLayout(server) as layout:
html.Div("trame__busy: {{ trame__busy }}")
html.Button("Make server busy", click=make_server_busy)
html.Button("Update title", click=update_title)
html.Button("Update favicon", click=update_favicon)
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
Events management
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
state, ctrl = server.state, server.controller
# -----------------------------------------------------------------------------
# State setup
# -----------------------------------------------------------------------------
state.a = 1
# -----------------------------------------------------------------------------
# Methods call
# -----------------------------------------------------------------------------
@ctrl.set("alias_1")
def method_1(*args, **kwargs):
print(f"Server: method_1 {args=} {kwargs=}")
state.a += 1
@ctrl.add("alias_2")
def method_2(*args, **kwargs):
print(f"Server: method_2 {args=} {kwargs=}")
state.a += 2
@ctrl.add("alias_2")
def method_3(*args, **kwargs):
print(f"Server: method_3 {args=} {kwargs=}")
state.a += 3
def method_4(*args, **kwargs):
print(f"Server: method_4 {args=} {kwargs=}")
state.a += 10
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
with DivLayout(server):
html.Div(
"State a={{ a }}", style="padding: 20px; margin: 20px; border: solid 1px #333;"
)
html.Button(f"Method 1", click=method_1)
html.Button(f"Method 2", click=(method_2, "['hello', 'world']"))
html.Button(f"Method 3", click=(method_3, "[1, 2]", "{ x: 3, y: 4 }"))
html.Button(f"alias_1", click=(ctrl.alias_1, "[2]", "{ z: 4 }"))
html.Button(f"alias_2", click=(ctrl.alias_2, "[3]", "{ z: 5 }"))
html.Button(f"alias_3", click=(ctrl.alias_3, "[4]", "{ z: 6 }"))
html.Button(f"a+", click="a+=1")
# Can be defined after usage
ctrl.alias_3 = method_4
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
state, ctrl = server.state, server.controller
# -----------------------------------------------------------------------------
# State setup
# -----------------------------------------------------------------------------
state.a = 1
# -----------------------------------------------------------------------------
# Methods call
# -----------------------------------------------------------------------------
@ctrl.set("alias_1")
def method_1(*args, **kwargs):
print(f"Server: method_1 {args=} {kwargs=}")
state.a += 1
@ctrl.add("alias_2")
def method_2(*args, **kwargs):
print(f"Server: method_2 {args=} {kwargs=}")
state.a += 2
@ctrl.add("alias_2")
def method_3(*args, **kwargs):
print(f"Server: method_3 {args=} {kwargs=}")
state.a += 3
def method_4(*args, **kwargs):
print(f"Server: method_4 {args=} {kwargs=}")
state.a += 10
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
with DivLayout(server):
html.Div(
"State a={{ a }}", style="padding: 20px; margin: 20px; border: solid 1px #333;"
)
html.Button(f"Method 1", click=method_1)
html.Button(f"Method 2", click=(method_2, "['hello', 'world']"))
html.Button(f"Method 3", click=(method_3, "[1, 2]", "{ x: 3, y: 4 }"))
html.Button(f"alias_1", click=(ctrl.alias_1, "[2]", "{ z: 4 }"))
html.Button(f"alias_2", click=(ctrl.alias_2, "[3]", "{ z: 5 }"))
html.Button(f"alias_3", click=(ctrl.alias_3, "[4]", "{ z: 6 }"))
html.Button(f"a+", click="a+=1")
# Can be defined after usage
ctrl.alias_3 = method_4
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
User Interface
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
line_count = 1
def update_first_line():
global line_count
with server.ui.first_line:
server.ui.first_line.clear()
html.Div(f"First line: { line_count }")
line_count += 1
# Start with some UI to control a
with DivLayout(server) as layout:
server.ui.first_line(layout) # Insert place holder
def add_line():
global line_count
with layout:
html.Div(f"Line: { line_count }")
line_count += 1
html.Button(f"Add line", click=add_line)
html.Button(f"Update first line", click=update_first_line)
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
line_count = 1
def update_first_line():
global line_count
with server.ui.first_line:
server.ui.first_line.clear()
html.Div(f"First line: { line_count }")
line_count += 1
# Start with some UI to control a
with DivLayout(server) as layout:
server.ui.first_line(layout) # Insert place holder
def add_line():
global line_count
with layout:
html.Div(f"Line: { line_count }")
line_count += 1
html.Button(f"Add line", click=add_line)
html.Button(f"Update first line", click=update_first_line)
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
with DivLayout(server):
html.A("Open UI(a)", href="/?ui=a", target="_blank")
html.Br()
html.A("Open UI(b)", href="/?ui=b", target="_blank")
with DivLayout(server, "a"):
html.Div("UI for A")
with DivLayout(server, "b"):
html.Div("UI for B")
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------
server = get_server()
# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------
with DivLayout(server):
html.A("Open UI(a)", href="/?ui=a", target="_blank")
html.Br()
html.A("Open UI(b)", href="/?ui=b", target="_blank")
with DivLayout(server, "a"):
html.Div("UI for A")
with DivLayout(server, "b"):
html.Div("UI for B")
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
Life Cycle
from trame.app import get_server
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------
server = get_server()
ctrl = server.controller
# Need a UI
with DivLayout(server):
pass
# -----------------------------------------------------------------------------
# Life Cycle events
# -----------------------------------------------------------------------------
@ctrl.add("on_server_ready")
def server_ready(**state):
print("on_server_ready")
@ctrl.add("on_client_connected")
def client_connected():
print("on_client_connected")
@ctrl.add("on_client_unmounted")
def client_unmounted():
print("on_client_unmounted")
@ctrl.add("on_client_exited")
def client_exited():
print("on_client_exited")
@ctrl.add("on_server_exited")
def server_exited(**state):
print("on_server_exited")
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start(timeout=1)
from trame.app import get_server
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------
server = get_server()
ctrl = server.controller
# Need a UI
with DivLayout(server):
pass
# -----------------------------------------------------------------------------
# Life Cycle events
# -----------------------------------------------------------------------------
@ctrl.add("on_server_ready")
def server_ready(**state):
print("on_server_ready")
@ctrl.add("on_client_connected")
def client_connected():
print("on_client_connected")
@ctrl.add("on_client_unmounted")
def client_unmounted():
print("on_client_unmounted")
@ctrl.add("on_client_exited")
def client_exited():
print("on_client_exited")
@ctrl.add("on_server_exited")
def server_exited(**state):
print("on_server_exited")
# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start(timeout=1)
Hot Reload
Trame as opposed to many frameworks out there is stateful which make things more complicated on how we can dynamically update it at runtime.
For that reason, the "hot reload" is at the execution and not on file save.
Basically, with trame, you need to interact with the application in order to re-excute some new code rather than saving your file and getting the new app ready to go. In fact you can have pieces of your application that will properly execute the edited code, while other will be stuck with the original version.
Once hot-reload is enabled by either using the TRAME_HOT_RELOAD
environment variable or by using the extra --hot-reload
arg, only the methods executed on the controller or via @state.change
properly re-evaluate the new code version. If you want to enable such behavior on your own function, you can use the @trame.decorators.hot_reload
decorator.
But in the following example, you can use watchdog
to execute the UI update on file change.
from trame.app import get_server
from trame.decorators import hot_reload
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------
server = get_server()
state, ctrl = server.state, server.controller
# Can be enabled at runtime as well with
# 1. export TRAME_HOT_RELOAD=1
# 2. --hot-reload
server.hot_reload = True
# -----------------------------------------------------------------------------
# Dynamically modify any `ChangeMe` to see the new code execute while
# interacting with the app.
# -----------------------------------------------------------------------------
@ctrl.set("number_reset")
def reset_number():
print("reset_number::ChangeMe")
state.number = 6
state.size = 1
do_someting()
@state.change("number")
def update_number(number, **kwargs):
print("update_number::ChangeMe", number)
do_someting()
@hot_reload
def do_someting():
print("do_someting::ChangeMe")
@ctrl.set("update_ui")
def update_ui():
with DivLayout(server):
html.Div("Some content - ChangeMe")
html.Input(type="range", v_model_number=("number", 6))
html.Input(type="range", v_model_number=("size", 2))
html.Button("Reset", click=ctrl.number_reset)
html.Button("Update", click=ctrl.update_ui)
html.Div(
"{{ number }} x {{ i }} = {{ number * i }}",
v_for="i in size",
key="i",
)
# Need to run before start
update_ui()
# -----------------------------------------------------------------------------
# Automatic UI update on file change using watchdog (pip install watchdog)
# -----------------------------------------------------------------------------
try:
import asyncio
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
current_event_loop = asyncio.get_event_loop()
def update_ui():
with server.state:
ctrl.update_ui()
class UpdateUIOnChange(FileSystemEventHandler):
def on_modified(self, event):
current_event_loop.call_soon_threadsafe(update_ui)
observer = Observer()
observer.schedule(
UpdateUIOnChange(), str(Path(__file__).parent.absolute()), recursive=False
)
observer.start()
except:
print("Watchdog not installed so skipping the auto monitoring")
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
from trame.app import get_server
from trame.decorators import hot_reload
from trame.widgets import html
from trame.ui.html import DivLayout
# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------
server = get_server()
state, ctrl = server.state, server.controller
# Can be enabled at runtime as well with
# 1. export TRAME_HOT_RELOAD=1
# 2. --hot-reload
server.hot_reload = True
# -----------------------------------------------------------------------------
# Dynamically modify any `ChangeMe` to see the new code execute while
# interacting with the app.
# -----------------------------------------------------------------------------
@ctrl.set("number_reset")
def reset_number():
print("reset_number::ChangeMe")
state.number = 6
state.size = 1
do_someting()
@state.change("number")
def update_number(number, **kwargs):
print("update_number::ChangeMe", number)
do_someting()
@hot_reload
def do_someting():
print("do_someting::ChangeMe")
@ctrl.set("update_ui")
def update_ui():
with DivLayout(server):
html.Div("Some content - ChangeMe")
html.Input(type="range", v_model_number=("number", 6))
html.Input(type="range", v_model_number=("size", 2))
html.Button("Reset", click=ctrl.number_reset)
html.Button("Update", click=ctrl.update_ui)
html.Div(
"{{ number }} x {{ i }} = {{ number * i }}",
v_for="i in size",
key="i",
)
# Need to run before start
update_ui()
# -----------------------------------------------------------------------------
# Automatic UI update on file change using watchdog (pip install watchdog)
# -----------------------------------------------------------------------------
try:
import asyncio
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
current_event_loop = asyncio.get_event_loop()
def update_ui():
with server.state:
ctrl.update_ui()
class UpdateUIOnChange(FileSystemEventHandler):
def on_modified(self, event):
current_event_loop.call_soon_threadsafe(update_ui)
observer = Observer()
observer.schedule(
UpdateUIOnChange(), str(Path(__file__).parent.absolute()), recursive=False
)
observer.start()
except:
print("Watchdog not installed so skipping the auto monitoring")
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
Class for trame application
from trame.app import get_server
from trame.decorators import TrameApp, change, controller, trigger, life_cycle
from trame.ui.html import DivLayout
from trame.widgets import html
@TrameApp()
class App:
def __init__(self, name=None):
self.server = get_server(name)
self.ui()
@property
def state(self):
return self.server.state
@property
def ctrl(self):
return self.server.controller
@trigger("exec")
def method_call(self, msg):
print("method_called", msg)
@controller.set("hello")
def method_on_ctrl(self, *args):
print("method_on_ctrl", args)
@change("resolution")
def one_slider(self, resolution, **kwargs):
print("Slider value 1", resolution)
@life_cycle.server_ready
def on_ready(self, *args, **kwargs):
print("on_ready")
def ui(self):
with DivLayout(self.server) as layout:
html.Input(
type="range",
min=3,
max=60,
step=1,
v_model_number=("resolution", 6),
)
html.Button("trigger", click="trigger('exec', ['trigger'])")
html.Button("method", click=(self.method_call, "['method']"))
html.Button("ctrl", click=self.ctrl.hello)
if __name__ == "__main__":
app = App()
app.server.start()
from trame.app import get_server
from trame.decorators import TrameApp, change, controller, trigger, life_cycle
from trame.ui.html import DivLayout
from trame.widgets import html
@TrameApp()
class App:
def __init__(self, name=None):
self.server = get_server(name)
self.ui()
@property
def state(self):
return self.server.state
@property
def ctrl(self):
return self.server.controller
@trigger("exec")
def method_call(self, msg):
print("method_called", msg)
@controller.set("hello")
def method_on_ctrl(self, *args):
print("method_on_ctrl", args)
@change("resolution")
def one_slider(self, resolution, **kwargs):
print("Slider value 1", resolution)
@life_cycle.server_ready
def on_ready(self, *args, **kwargs):
print("on_ready")
def ui(self):
with DivLayout(self.server) as layout:
html.Input(
type="range",
min=3,
max=60,
step=1,
v_model_number=("resolution", 6),
)
html.Button("trigger", click="trigger('exec', ['trigger'])")
html.Button("method", click=(self.method_call, "['method']"))
html.Button("ctrl", click=self.ctrl.hello)
if __name__ == "__main__":
app = App()
app.server.start()