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
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "trame",
# ]
# ///
from trame.app import TrameApp
from trame.decorators import change
from trame.ui.html import DivLayout
from trame.widgets import html


# 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)")


class StateUsage(TrameApp):
    def __init__(self, server=None):
        super().__init__(server)
        self.state_setup()
        self._build_ui()

    def state_setup(self):
        # Creating new entries to the shared state
        self.state.a = 1
        self.state["b"] = 2

        # Force state.d to be client side only
        self.state.client_only("b")
        # self.state.trame__client_only += ["b"]

    def _build_ui(self):
        with DivLayout(self.server) as self.ui:
            create_ui_for_state_var("a")
            create_ui_for_state_var("b")

    @change("a", "b")
    def state_change(self, a, b, **_):
        """State listener"""
        print(f"State updated a={a} b={b}")


def main():
    app = StateUsage()
    app.server.start()


if __name__ == "__main__":
    main()
py
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "trame",
# ]
# ///
import time

from trame.app import TrameApp
from trame.ui.html import DivLayout
from trame.widgets import html


class ReservedState(TrameApp):
    def __init__(self, server=None):
        super().__init__(server)
        self._count = 1
        self._build_ui()

    def make_server_busy(self):
        time.sleep(1)

    def update_title(self):
        self.state.trame__title = f"T({self._count})"
        self._count += 1

    def update_favicon(self):
        self.state.trame__favicon = f"https://picsum.photos/id/{self._count}/32/32"
        self._count += 10

    def _build_ui(self):
        with DivLayout(self.server) as self.ui:
            html.Div("trame__busy: {{ trame__busy }}")
            html.Button("Make server busy", click=self.make_server_busy)
            html.Button("Update title", click=self.update_title)
            html.Button("Update favicon", click=self.update_favicon)


def main():
    app = ReservedState()
    app.server.start()


if __name__ == "__main__":
    main()

Events management

py
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "trame",
# ]
# ///
from trame.app import TrameApp
from trame.decorators import controller
from trame.ui.html import DivLayout
from trame.widgets import html


class Events(TrameApp):
    def __init__(self, server=None):
        super().__init__(server)

        # setup state
        self.state.a = 1

        # build ui
        self._build_ui()

        # Can be defined after usage
        self.ctrl.alias_3 = self.method_4

    def _build_ui(self):
        with DivLayout(self.server) as self.ui:
            html.Div(
                "State a={{ a }}",
                style="padding: 20px; margin: 20px; border: solid 1px #333;",
            )
            html.Button("Method 1", click=self.method_1)
            html.Button("Method 2", click=(self.method_2, "['hello', 'world']"))
            html.Button("Method 3", click=(self.method_3, "[1, 2]", "{ x: 3, y: 4 }"))
            html.Button("alias_1", click=(self.ctrl.alias_1, "[2]", "{ z: 4 }"))
            html.Button("alias_2", click=(self.ctrl.alias_2, "[3]", "{ z: 5 }"))
            html.Button("alias_3", click=(self.ctrl.alias_3, "[4]", "{ z: 6 }"))
            html.Button("a+", click="a+=1")

    @controller.set("alias_1")
    def method_1(self, *args, **kwargs):
        print(f"Server: method_1 {args=} {kwargs=}")
        self.state.a += 1

    @controller.add("alias_2")
    def method_2(self, *args, **kwargs):
        print(f"Server: method_2 {args=} {kwargs=}")
        self.state.a += 2

    @controller.add("alias_2")
    def method_3(self, *args, **kwargs):
        print(f"Server: method_3 {args=} {kwargs=}")
        self.state.a += 3

    def method_4(self, *args, **kwargs):
        print(f"Server: method_4 {args=} {kwargs=}")
        self.state.a += 10


def main():
    app = Events()
    app.server.start()


if __name__ == "__main__":
    main()

User Interface

py
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "trame",
# ]
# ///
from trame.app import TrameApp
from trame.ui.html import DivLayout
from trame.widgets import html


class DynamicLayout(TrameApp):
    def __init__(self, server=None):
        super().__init__(server)
        self.line_count = 1
        self._build_ui()

    def add_line(self):
        with self.ui:
            html.Div(f"Line: {self.line_count}")
        self.line_count += 1

    def update_first_line(self):
        with self.server.ui.first_line:
            self.server.ui.first_line.clear()
            html.Div(f"First line: {self.line_count}")
            self.line_count += 1

    def _build_ui(self):
        with DivLayout(self.server) as self.ui:
            self.server.ui.first_line(self.ui)  # Insert place holder

            html.Button("Add line", click=self.add_line)
            html.Button("Update first line", click=self.update_first_line)


def main():
    app = DynamicLayout()
    app.server.start()


if __name__ == "__main__":
    main()
py
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "trame",
# ]
# ///
from trame.app import TrameApp
from trame.ui.html import DivLayout
from trame.widgets import html


class MultiLayout(TrameApp):
    def __init__(self, server=None):
        super().__init__(server)
        self._build_ui()

    def _build_ui(self):
        with DivLayout(self.server) as self.ui:
            html.A("Open UI(a)", href="/?ui=a", target="_blank")
            html.Br()
            html.A("Open UI(b)", href="/?ui=b", target="_blank")

        with DivLayout(self.server, "a"):
            html.Div("UI for A")

        with DivLayout(self.server, "b"):
            html.Div("UI for B")


def main():
    app = MultiLayout()
    app.server.start()


if __name__ == "__main__":
    main()

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)

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-execute 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.ui.html import DivLayout
from trame.widgets import html

# -----------------------------------------------------------------------------
# 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 2")
    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.events import FileSystemEventHandler
    from watchdog.observers import Observer

    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 ImportError:
    print("Watchdog not installed so skipping the auto monitoring")

# -----------------------------------------------------------------------------

if __name__ == "__main__":
    server.start()

Class for trame application

py
from trame.app import TrameApp
from trame.decorators import change, controller, life_cycle, trigger
from trame.ui.html import DivLayout
from trame.widgets import html


class App(TrameApp):
    def __init__(self, name=None):
        super().__init__(server=name)
        self._build_ui()

    @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 _build_ui(self):
        with DivLayout(self.server) as self.ui:
            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()

Then with such a class that inherit trame.app.TrameApp you can instantiate it and return it to display its user interface.