Skip to content

Commit 7482d72

Browse files
committed
Add dynamic Pydantic rules example
1 parent c80c42d commit 7482d72

File tree

5 files changed

+104
-3
lines changed

5 files changed

+104
-3
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ This repository contains examples of how to build application functionalities in
66

77
1. [Creating a form for a Pydantic model](examples/pydantic_form)
88
2. [Create a JSON editor for a Pydantic model](examples/pydantic_monaco)
9-
3. [Working with Plotly](examples/plotly)
10-
4. [Working with Matplotlib](examples/matplotlib)
11-
5. [Synchronizing changes between tabs](examples/multitab)
9+
3. [Changing Pydantic rules based on user input](examples/dynamic_pydantic_rules)
10+
4. [Complex Pydantic rules](examples/complex_pydantic_rules)
11+
5. [Working with Plotly](examples/plotly)
12+
6. [Working with Matplotlib](examples/matplotlib)
13+
7. [Synchronizing changes between tabs](examples/multitab)
1214

1315
## Running Examples
1416

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Runs the dynamic Pydantic rules example."""
2+
3+
from examples.dynamic_pydantic_rules.view import App
4+
5+
6+
def main() -> None:
7+
app = App()
8+
app.server.start(open_browser=False)
9+
10+
11+
if __name__ == "__main__":
12+
main()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Model implementation for dynamic Pydantic rules example."""
2+
3+
from pydantic import BaseModel, Field, model_validator
4+
from typing_extensions import Self
5+
6+
7+
class FormData(BaseModel):
8+
"""Pydantic model for the form data."""
9+
10+
min: float = Field(default=0.0, title="Minimum")
11+
max: float = Field(default=10.0, title="Maximum")
12+
value: float = Field(default=5.0, title="Value")
13+
14+
@model_validator(mode="after")
15+
def validate_value_in_range(self) -> Self:
16+
if self.value < self.min or self.value > self.max:
17+
raise ValueError("Value must be between Minimum and Maximum")
18+
19+
return self
20+
21+
22+
class Model:
23+
"""Model implementation for dynamic Pydantic rules example."""
24+
25+
def __init__(self) -> None:
26+
self.form = FormData()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""View for dynamic Pydantic rules example."""
2+
3+
from nova.mvvm.trame_binding import TrameBinding
4+
from nova.trame import ThemedApp
5+
from nova.trame.view.components import InputField
6+
from nova.trame.view.layouts import GridLayout
7+
from trame.widgets import vuetify3 as vuetify
8+
9+
from .model import Model
10+
from .view_model import ViewModel
11+
12+
13+
class App(ThemedApp):
14+
"""View for dynamic Pydantic rules example."""
15+
16+
def __init__(self) -> None:
17+
super().__init__()
18+
19+
self.create_vm()
20+
# If you forget to call connect, then the application will crash when you attempt to update the view.
21+
self.view_model.form_data_bind.connect("data")
22+
# Generally, we want to initialize the view state before creating the UI for ease of use. If initialization
23+
# is expensive, then you can defer it. In this case, you must handle the view state potentially being
24+
# uninitialized in the UI via v_if statements.
25+
self.view_model.update_form_data()
26+
27+
self.create_ui()
28+
29+
def create_ui(self) -> None:
30+
with super().create_ui() as layout:
31+
with layout.content:
32+
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
33+
with GridLayout(columns=3, gap="0.5em"):
34+
InputField(v_model="data.min")
35+
InputField(v_model="data.max")
36+
InputField(v_model="data.value")
37+
38+
def create_vm(self) -> None:
39+
binding = TrameBinding(self.state)
40+
41+
model = Model()
42+
self.view_model = ViewModel(model, binding)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""View model implementation for dynamic Pydantic rules example."""
2+
3+
from nova.mvvm.interface import BindingInterface
4+
5+
from .model import Model
6+
7+
8+
class ViewModel:
9+
"""View model implementation for dynamic Pydantic rules example."""
10+
11+
def __init__(self, model: Model, binding: BindingInterface) -> None:
12+
self.model = model
13+
14+
# self.on_update is called any time the view updates the binding.
15+
self.form_data_bind = binding.new_bind(self.model.form)
16+
17+
def update_form_data(self) -> None:
18+
# This will fail if you haven't called connect on the binding!
19+
self.form_data_bind.update_in_view(self.model.form)

0 commit comments

Comments
 (0)