Skip to content

Introduction

The examples presented below are meant to be executed and explored so you can understand the basic mechanic of trame.

State management

py
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()
py
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

py
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

py
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()
py
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

py
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.

py
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

py
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()