Dataclass for trame UI binding
Project description
trame-dataclass
trame-dataclass lets you define typed Python classes whose fields sync automatically between the server and the browser. Each field is declared with a descriptor (Sync, ServerOnly, or ClientOnly) that controls the sync direction. The resulting objects plug into trame’s reactivity system and Vue.js templates without any manual state-key bookkeeping.
License
This library is OpenSource and follows the Apache Software License.
Installation
pip install trame-dataclass
Requires Python 3.10+.
Field descriptors
The three descriptors control how a field is shared between server and client:
Descriptor |
Server → Client |
Client → Server |
@watch fires |
Typical use |
|---|---|---|---|---|
Sync |
Yes |
Yes |
Yes |
Shared mutable state |
ServerOnly |
No |
No |
Yes |
Private server data (e.g. heavy objects, file handles) |
ClientOnly |
Yes (on init) |
No |
No |
Local UI state the server seeds but never reads back |
All three accept (type, default=None) as their first two arguments. The default can be a callable (called fresh for each instance).
Reactivity
Two complementary ways to react to field changes:
Decorator @watch annotation — defined inside the class; fires on the server whenever any of the named fields change:
@watch("age")
def _update_derived(self, age):
self.derived_value = 80 - age
Method .watch() call — registers a callback externally at runtime and returns an unwatch callable:
unwatch = self._data.watch(["age"], self.print_age)
Watchers receive one argument per field name listed and run asynchronously by default. Pass sync=True for synchronous execution or eager=True to fire immediately on registration.
Exposing instances to the UI
provide_as(name) makes a dataclass instance available as a named reactive variable inside a UI scope:
with self._data.provide_as("user"):
html.Input(type="text", v_model="user.name")
For dynamic binding — where the active instance is determined at runtime by a state variable — use dataclass.Provider directly:
with dataclass.Provider(name="dynamic_user", instance=("active_user", None)):
html.Pre("{{ JSON.stringify(dynamic_user, null, 2) }}")
instance accepts a trame expression tuple; the second element is the default when the state variable is unset.
Composed dataclasses
Fields can hold other StateDataModel instances (or lists/dicts of them) using the has_dataclass=True flag. Encoding and decoding between Python objects and client-side IDs is handled automatically:
class Person(StateDataModel):
first_name = Sync(str, "John")
last_name = Sync(str, "Doe")
class AddressBook(StateDataModel):
contacts = Sync(list[Person], list, has_dataclass=True)
Deep reactivity
By default, replacing a list or dict field triggers an update. For mutable in-place edits from the client (e.g. data.values.push(1) in JavaScript), enable deep reactivity so Vue tracks nested changes:
values = Sync(list[int], list, client_deep_reactive=True)
Embedded GUI
A StateDataModel subclass can carry its own UI definition by overriding generate_gui. The returned widget HTML is then rendered on demand in the application via dataclass.Gui:
class Person(StateDataModel):
first_name = Sync(str, "John")
last_name = Sync(str, "Doe")
@classmethod
def generate_gui(cls, trame_server=None) -> str:
with v3.VCard(
trame_server=trame_server,
v_if="self_available && self._id",
) as root:
v3.VTextField(v_model="self.first_name", label="First Name")
v3.VTextField(v_model="self.last_name", label="Last Name")
return root.html
# In your application UI:
dataclass.Gui(instance=("selected_id",))
dataclass.Gui resolves the instance ID at runtime and renders the matching generate_gui output, making it easy to build detail panels for dynamically selected objects.
When the GUI is a fixed layout that does not require Python widget builders, a TEMPLATE class attribute is a simpler alternative. Set it to a raw Vue template string and skip generate_gui entirely — dataclass.Gui picks it up automatically:
class Person(StateDataModel):
first_name = Sync(str, "John")
last_name = Sync(str, "Doe")
TEMPLATE = """
<VCard class="ma-4" v-if="self_available && self._id">
<VContainer>
<VRow>
<VCol>
<VTextField v-model="self.first_name" label="First Name" />
</VCol>
<VCol>
<VTextField v-model="self.last_name" label="Last Name" />
</VCol>
</VRow>
</VContainer>
</VCard>
"""
# In your application UI (identical to the generate_gui approach):
dataclass.Gui(instance=("selected?.[0]",))
Inside both TEMPLATE and generate_gui, the dataclass instance is available as self and self_available is a boolean that is true only when a valid instance is bound. Use v-if="self_available && self._id" to guard content that should only render when an instance is selected.
Need |
TEMPLATE |
generate_gui |
|---|---|---|
Pure HTML / Vue markup |
Simpler |
Works |
Python widget builders (e.g. Vuetify helpers) |
Not applicable |
Required |
Dynamic widget construction at class definition time |
Not applicable |
Required |
Usage example
from typing import Any
from trame.app import TrameApp
from trame.app.dataclass import ClientOnly, ServerOnly, StateDataModel, Sync, watch
from trame.ui.html import DivLayout
from trame.widgets import dataclass, html
class SimpleStructure(StateDataModel):
name = Sync(str, "John Doe") # server <=> client
age = Sync(int, 1) # server <=> client
derived_value = Sync(int) # server <=> client
something = ServerOnly(Any) # server only
local_edit = ClientOnly(int) # server seeds, client owns
@watch("age")
def _update_derived(self, age):
self.derived_value = 80 - age
@watch("local_edit")
def _never_called(self, local_edit):
# ClientOnly — client edits never reach the server
print("local_edit changed to", local_edit)
class GettingStarted(TrameApp):
def __init__(self, server=None):
super().__init__(server)
self._data = SimpleStructure(self.server)
self._data.watch(["age"], self.print_age)
self._build_ui()
def print_age(self, age):
print(f"Age changed to {age=}")
def toggle_active_user(self):
if self.state.active_user:
self.state.active_user = None
else:
self.state.active_user = self._data._id
def _modify_data(self):
self._data.age += 1
def _build_ui(self):
with DivLayout(self.server) as self.ui:
# Edit user on server
html.Button("Server change", click=self._modify_data)
# Provide data class instance to the UI as a variable
with self._data.provide_as("user"):
html.Button("Edit local", click="user.local_edit = Date.now()")
html.Pre("{{ JSON.stringify(user, null, 2) }}")
html.Hr()
html.Div(
"Hello {{ user.name }} - derived value = {{ user.derived_value }}"
)
html.Hr()
html.Span("Your name:")
html.Input(type="text", v_model="user.name")
html.Hr()
html.Span("Your age:")
html.Input(
type="range", min=0, max=120, step=1, v_model_number="user.age"
)
html.Hr()
# Adjust dynamic user
html.Button(
"Toggle user ({{ active_user || 'None' }})",
click=self.toggle_active_user,
)
# Dynamically provide a dataclass to the UI
with dataclass.Provider(
name="dynamic_user",
instance=("active_user", None),
):
html.Pre("{{ JSON.stringify(dynamic_user, null, 2) }}")
def main():
app = GettingStarted()
app.server.start()
if __name__ == "__main__":
main()
More examples
The examples/ directory contains runnable scripts covering:
getting_started/ — basic usage, deep reactivity, tree structures, custom encoders
gui/ — embedded GUI with generate_gui and dataclass.Gui
validation/ — type checking, composite dataclasses, performance tests
Scripts with a /// script header can be run directly with uv run:
uv run examples/getting_started/readme.py
Development setup
We recommend using uv for setting up and managing a virtual environment for your development.
# Create venv and install all dependencies
uv sync --all-extras --dev
# Activate environment
source .venv/bin/activate
# Install commit analysis
pre-commit install
pre-commit install --hook-type commit-msg
Build and install the Vue components
cd vue-components
npm i
npm run build
cd -
For running tests and checks, you can run nox.
# run all
nox
# lint
nox -s lint
# tests
nox -s tests
Professional Support
Training: Learn how to confidently use trame from the expert developers at Kitware.
Support: Our experts can assist your team as you build your web application and establish in-house expertise.
Custom Development: Leverage Kitware’s 25+ years of experience to quickly build your web application.
Commit message convention
Semantic release rely on conventional commits to generate new releases and changelog.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trame_dataclass-2.1.2.tar.gz.
File metadata
- Download URL: trame_dataclass-2.1.2.tar.gz
- Upload date:
- Size: 21.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1233638c00a4265e354c58d10877c84c3bb7bf880ab108b97c4b2ee5dac8677
|
|
| MD5 |
f5c7d2b03919e5c6192047a6fa5b8cb7
|
|
| BLAKE2b-256 |
14e804d507cf9a7cb6121aa2eaadf06fb689a4837fc3ca17eb8736a900bf6b0e
|
Provenance
The following attestation bundles were made for trame_dataclass-2.1.2.tar.gz:
Publisher:
test_and_release.yml on Kitware/trame-dataclass
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trame_dataclass-2.1.2.tar.gz -
Subject digest:
f1233638c00a4265e354c58d10877c84c3bb7bf880ab108b97c4b2ee5dac8677 - Sigstore transparency entry: 2015023454
- Sigstore integration time:
-
Permalink:
Kitware/trame-dataclass@161736363bf2b68a6b7d4c85cdff6f77cf613fcb -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Kitware
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
test_and_release.yml@161736363bf2b68a6b7d4c85cdff6f77cf613fcb -
Trigger Event:
push
-
Statement type:
File details
Details for the file trame_dataclass-2.1.2-py3-none-any.whl.
File metadata
- Download URL: trame_dataclass-2.1.2-py3-none-any.whl
- Upload date:
- Size: 28.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
524d073576ed274b2e72c85486ca7fd7b83d4723fdcbb1ed10108058b27917dd
|
|
| MD5 |
c7fe5efb4ba52b2fa72747f13933acee
|
|
| BLAKE2b-256 |
c4724a3479b7c4b0c8180a127c2f34199b60eac66a333e82ac9263f84826327a
|
Provenance
The following attestation bundles were made for trame_dataclass-2.1.2-py3-none-any.whl:
Publisher:
test_and_release.yml on Kitware/trame-dataclass
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trame_dataclass-2.1.2-py3-none-any.whl -
Subject digest:
524d073576ed274b2e72c85486ca7fd7b83d4723fdcbb1ed10108058b27917dd - Sigstore transparency entry: 2015023571
- Sigstore integration time:
-
Permalink:
Kitware/trame-dataclass@161736363bf2b68a6b7d4c85cdff6f77cf613fcb -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Kitware
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
test_and_release.yml@161736363bf2b68a6b7d4c85cdff6f77cf613fcb -
Trigger Event:
push
-
Statement type: