From def2faa4e4dd3195616408cc683a7a90f6f7976f Mon Sep 17 00:00:00 2001 From: Joe Cheng Date: Thu, 26 Oct 2023 14:50:40 -0700 Subject: [PATCH 1/5] Move @render_widget to output_transformer infrastructure This also adds the ability to use render_widget in self-rendering contexts like quarto --- shinywidgets/_shinywidgets.py | 71 +++++++++++------------------------ 1 file changed, 21 insertions(+), 50 deletions(-) diff --git a/shinywidgets/_shinywidgets.py b/shinywidgets/_shinywidgets.py index 09cf6d7..f44c1ee 100644 --- a/shinywidgets/_shinywidgets.py +++ b/shinywidgets/_shinywidgets.py @@ -31,6 +31,12 @@ from shiny.http_staticfiles import StaticFiles from shiny.module import resolve_id from shiny.render import RenderFunction, RenderFunctionAsync +from shiny.render.transformer import ( + TransformerMetadata, + ValueFn, + output_transformer, + resolve_value_fn, +) from shiny.session import get_current_session, require_active_session from ._as_widget import as_widget @@ -185,68 +191,33 @@ def _restore_state(): # TODO: shiny should probably make this simpler # -------------------------------------------------------------------------------------------- -IPyWidgetRenderFunc = Callable[[], Widget] -IPyWidgetRenderFuncAsync = Callable[[], Awaitable[Widget]] - - -class IPyWidget(RenderFunction[Widget, object]): - def __init__(self, fn: IPyWidgetRenderFunc) -> None: - super().__init__(fn) - self._fn: IPyWidgetRenderFuncAsync = wrap_async(fn) - - def __call__(self) -> object: - return run_coro_sync(self.run()) - - async def run(self) -> object: - x = await self._fn() - if x is None: - return None - widget = as_widget(x) - return {"model_id": widget.model_id} # type: ignore - -class IPyWidgetAsync(IPyWidget, RenderFunctionAsync[Widget, object]): - def __init__(self, fn: IPyWidgetRenderFuncAsync) -> None: - if not inspect.iscoroutinefunction(fn): - raise TypeError("IPyWidgetAsync requires an async function") - super().__init__(cast(IPyWidgetRenderFunc, fn)) - - async def __call__(self) -> object: - return await self.run() +@output_transformer(default_ui=output_widget) +async def WidgetTransformer( + _meta: TransformerMetadata, + _fn: ValueFn[object | None], +) -> dict[str, Any] | None: + value = await resolve_value_fn(_fn) + if value is None: + return None + widget = as_widget(value) + return {"model_id": widget.model_id} # type: ignore @overload -def render_widget( - fn: Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync] -) -> IPyWidget: +def render_widget(fn: WidgetTransformer.ValueFn) -> WidgetTransformer.OutputRenderer: ... @overload -def render_widget() -> ( - Callable[[Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]], IPyWidget] -): +def render_widget() -> WidgetTransformer.OutputRendererDecorator: ... def render_widget( - fn: Optional[Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]] = None -) -> Union[ - IPyWidget, - Callable[[Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]], IPyWidget], -]: - def wrapper(fn: Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]) -> IPyWidget: - if inspect.iscoroutinefunction(fn): - fn = cast(IPyWidgetRenderFuncAsync, fn) - return IPyWidgetAsync(fn) - else: - fn = cast(IPyWidgetRenderFunc, fn) - return IPyWidget(fn) - - if fn is None: - return wrapper - else: - return wrapper(fn) + fn: WidgetTransformer.ValueFn | None = None, +) -> WidgetTransformer.OutputRenderer | WidgetTransformer.OutputRendererDecorator: + return WidgetTransformer(fn) def reactive_read(widget: Widget, names: Union[str, Sequence[str]]) -> Any: From f5f89f618dbde71dc6ce9f1f466fa2e24ea05cdd Mon Sep 17 00:00:00 2001 From: Carson Sievert Date: Thu, 26 Oct 2023 18:01:18 -0500 Subject: [PATCH 2/5] Remove comment --- shinywidgets/_shinywidgets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shinywidgets/_shinywidgets.py b/shinywidgets/_shinywidgets.py index f44c1ee..66fb6d5 100644 --- a/shinywidgets/_shinywidgets.py +++ b/shinywidgets/_shinywidgets.py @@ -188,7 +188,6 @@ def _restore_state(): # -------------------------------------------------------------------------------------------- # Implement @render_widget() -# TODO: shiny should probably make this simpler # -------------------------------------------------------------------------------------------- From 641268191a25abba4d23588806972486a442d74d Mon Sep 17 00:00:00 2001 From: Carson Date: Thu, 26 Oct 2023 18:10:13 -0500 Subject: [PATCH 3/5] Require dev version of shiny --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1eb52a8..588d65d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,7 +34,7 @@ setup_requires = install_requires = ipywidgets>=7.6.5 jupyter_core - shiny>=0.3.0 + shiny>=0.5.1.9003 python-dateutil>=2.8.2 # Needed because of https://github.com/python/importlib_metadata/issues/411 importlib-metadata>=4.8.3,<5; python_version < "3.8" From 2e71c1e71cb484fb3bb8a9576c3e471790c88aa1 Mon Sep 17 00:00:00 2001 From: Carson Date: Thu, 26 Oct 2023 18:13:23 -0500 Subject: [PATCH 4/5] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa5bdbd..4e506f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [UNRELEASED] +* Moved `@render_widget` to output_transformer infrastructure, and as a result, it works more seamlessly in `shiny.express` mode. (#110) * Closed #104: Officially support for Python 3.7. ## [0.2.1] - 2023-05-15 From 935c5aa8b4dfef12c3417d913466c37b941b14bf Mon Sep 17 00:00:00 2001 From: Carson Sievert Date: Thu, 26 Oct 2023 18:17:20 -0500 Subject: [PATCH 5/5] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e506f3..492d489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [UNRELEASED] -* Moved `@render_widget` to output_transformer infrastructure, and as a result, it works more seamlessly in `shiny.express` mode. (#110) +* `@render_widget` now builds on `shiny`'s `render.transformer` infrastructure, and as a result, it works more seamlessly in `shiny.express` mode. (#110) * Closed #104: Officially support for Python 3.7. ## [0.2.1] - 2023-05-15