Skip to content

Commit 5bcb4a2

Browse files
jcheng5cpsievert
andauthored
Move @render_widget to output_transformer infrastructure (#110)
Co-authored-by: Carson Sievert <[email protected]>
1 parent 121da87 commit 5bcb4a2

File tree

3 files changed

+23
-52
lines changed

3 files changed

+23
-52
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [UNRELEASED]
99

10+
* `@render_widget` now builds on `shiny`'s `render.transformer` infrastructure, and as a result, it works more seamlessly in `shiny.express` mode. (#110)
1011
* Closed #104: Officially support for Python 3.7.
1112

1213
## [0.2.1] - 2023-05-15

Diff for: setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ setup_requires =
3434
install_requires =
3535
ipywidgets>=7.6.5
3636
jupyter_core
37-
shiny>=0.3.0
37+
shiny>=0.5.1.9003
3838
python-dateutil>=2.8.2
3939
# Needed because of https://github.com/python/importlib_metadata/issues/411
4040
importlib-metadata>=4.8.3,<5; python_version < "3.8"

Diff for: shinywidgets/_shinywidgets.py

+21-51
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131
from shiny.http_staticfiles import StaticFiles
3232
from shiny.module import resolve_id
3333
from shiny.render import RenderFunction, RenderFunctionAsync
34+
from shiny.render.transformer import (
35+
TransformerMetadata,
36+
ValueFn,
37+
output_transformer,
38+
resolve_value_fn,
39+
)
3440
from shiny.session import get_current_session, require_active_session
3541

3642
from ._as_widget import as_widget
@@ -182,71 +188,35 @@ def _restore_state():
182188

183189
# --------------------------------------------------------------------------------------------
184190
# Implement @render_widget()
185-
# TODO: shiny should probably make this simpler
186191
# --------------------------------------------------------------------------------------------
187192

188-
IPyWidgetRenderFunc = Callable[[], Widget]
189-
IPyWidgetRenderFuncAsync = Callable[[], Awaitable[Widget]]
190-
191-
192-
class IPyWidget(RenderFunction[Widget, object]):
193-
def __init__(self, fn: IPyWidgetRenderFunc) -> None:
194-
super().__init__(fn)
195-
self._fn: IPyWidgetRenderFuncAsync = wrap_async(fn)
196-
197-
def __call__(self) -> object:
198-
return run_coro_sync(self.run())
199-
200-
async def run(self) -> object:
201-
x = await self._fn()
202-
if x is None:
203-
return None
204-
widget = as_widget(x)
205-
return {"model_id": widget.model_id} # type: ignore
206193

207-
208-
class IPyWidgetAsync(IPyWidget, RenderFunctionAsync[Widget, object]):
209-
def __init__(self, fn: IPyWidgetRenderFuncAsync) -> None:
210-
if not inspect.iscoroutinefunction(fn):
211-
raise TypeError("IPyWidgetAsync requires an async function")
212-
super().__init__(cast(IPyWidgetRenderFunc, fn))
213-
214-
async def __call__(self) -> object:
215-
return await self.run()
194+
@output_transformer(default_ui=output_widget)
195+
async def WidgetTransformer(
196+
_meta: TransformerMetadata,
197+
_fn: ValueFn[object | None],
198+
) -> dict[str, Any] | None:
199+
value = await resolve_value_fn(_fn)
200+
if value is None:
201+
return None
202+
widget = as_widget(value)
203+
return {"model_id": widget.model_id} # type: ignore
216204

217205

218206
@overload
219-
def render_widget(
220-
fn: Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]
221-
) -> IPyWidget:
207+
def render_widget(fn: WidgetTransformer.ValueFn) -> WidgetTransformer.OutputRenderer:
222208
...
223209

224210

225211
@overload
226-
def render_widget() -> (
227-
Callable[[Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]], IPyWidget]
228-
):
212+
def render_widget() -> WidgetTransformer.OutputRendererDecorator:
229213
...
230214

231215

232216
def render_widget(
233-
fn: Optional[Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]] = None
234-
) -> Union[
235-
IPyWidget,
236-
Callable[[Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]], IPyWidget],
237-
]:
238-
def wrapper(fn: Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]) -> IPyWidget:
239-
if inspect.iscoroutinefunction(fn):
240-
fn = cast(IPyWidgetRenderFuncAsync, fn)
241-
return IPyWidgetAsync(fn)
242-
else:
243-
fn = cast(IPyWidgetRenderFunc, fn)
244-
return IPyWidget(fn)
245-
246-
if fn is None:
247-
return wrapper
248-
else:
249-
return wrapper(fn)
217+
fn: WidgetTransformer.ValueFn | None = None,
218+
) -> WidgetTransformer.OutputRenderer | WidgetTransformer.OutputRendererDecorator:
219+
return WidgetTransformer(fn)
250220

251221

252222
def reactive_read(widget: Widget, names: Union[str, Sequence[str]]) -> Any:

0 commit comments

Comments
 (0)