From 5a1921dcfacb185301829f29a9a1ad84144f751e Mon Sep 17 00:00:00 2001 From: David Weedon Date: Tue, 17 Feb 2026 11:18:07 -0600 Subject: [PATCH] Support Basic roots in WidgetTemplate.build --- chatkit/widgets.py | 19 ++++++++++--------- tests/test_widgets.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/chatkit/widgets.py b/chatkit/widgets.py index 80492d1..e8323c9 100644 --- a/chatkit/widgets.py +++ b/chatkit/widgets.py @@ -1098,8 +1098,14 @@ class DynamicWidgetComponent(WidgetComponentBase): ] +class BasicRoot(DynamicWidgetComponent): + """Layout root capable of nesting components or other roots.""" + + type: Literal["Basic"] = Field(default="Basic", frozen=True) # pyright: ignore + + StrictWidgetRoot = Annotated[ - Card | ListView, + Card | ListView | BasicRoot, Field(discriminator="type"), ] @@ -1107,13 +1113,7 @@ class DynamicWidgetComponent(WidgetComponentBase): class DynamicWidgetRoot(DynamicWidgetComponent): """Dynamic root widget restricted to root types.""" - type: Literal["Card", "ListView"] # pyright: ignore - - -class BasicRoot(DynamicWidgetComponent): - """Layout root capable of nesting components or other roots.""" - - type: Literal["Basic"] = Field(default="Basic", frozen=True) # pyright: ignore + type: Literal["Card", "ListView", "Basic"] # pyright: ignore WidgetComponent = StrictWidgetComponent | DynamicWidgetComponent @@ -1178,8 +1178,9 @@ def build( widget_dict = json.loads(rendered) return DynamicWidgetRoot.model_validate(widget_dict) + @deprecated("WidgetTemplate.build_basic is deprecated. Use WidgetTemplate.build instead.") def build_basic(self, data: dict[str, Any] | BaseModel | None = None) -> BasicRoot: - """Separate method for building basic root widgets until BasicRoot is supported for streamed widgets.""" + """Deprecated alias for building Basic root widgets.""" rendered = self.template.render(**self._normalize_data(data)) widget_dict = json.loads(rendered) return BasicRoot.model_validate(widget_dict) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index f10d53f..18c56a2 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -244,18 +244,45 @@ def test_widget_template_from_file( assert widget.model_dump(exclude_none=True) == expected_widget_dict -def test_widget_template_with_basic_root(): +def test_widget_template_build_with_basic_root(): template = WidgetTemplate.from_file("assets/widgets/basic_root.widget") with open("tests/assets/widgets/basic_root.json", "r") as file: expected_widget_dict = json.load(file) - widget = template.build_basic( + widget = template.build( { "name": "Harry Potter", "bio": "The boy who lived", }, ) + assert isinstance(widget, DynamicWidgetRoot) + assert widget.type == "Basic" + assert widget.model_dump(exclude_none=True) == expected_widget_dict + + widget_item = WidgetItem( + thread_id="1", widget=widget, id="1", created_at=datetime.now() + ) + assert widget_item.widget.type == "Basic" + + +def test_widget_template_build_basic_is_deprecated(): + template = WidgetTemplate.from_file("assets/widgets/basic_root.widget") + + with open("tests/assets/widgets/basic_root.json", "r") as file: + expected_widget_dict = json.load(file) + + with pytest.warns( + DeprecationWarning, + match="WidgetTemplate.build_basic is deprecated. Use WidgetTemplate.build instead.", + ): + widget = template.build_basic( + { + "name": "Harry Potter", + "bio": "The boy who lived", + }, + ) + assert isinstance(widget, BasicRoot) assert widget.model_dump(exclude_none=True) == expected_widget_dict