From 1a8f1ff0dca71a3da26ed7b19aa6f2dda38dce6d Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Thu, 11 Jan 2024 21:31:19 -0600 Subject: [PATCH 01/25] Add render.code --- shiny/render/__init__.py | 2 + shiny/render/_render.py | 95 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/shiny/render/__init__.py b/shiny/render/__init__.py index ce47f4f64..a1876c33f 100644 --- a/shiny/render/__init__.py +++ b/shiny/render/__init__.py @@ -15,6 +15,7 @@ display, ) from ._render import ( + code, image, plot, table, @@ -28,6 +29,7 @@ "data_frame", "display", "text", + "code", "plot", "image", "table", diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 5d03a2a6d..eb618c92d 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -9,6 +9,7 @@ # Can use `dict` in python >= 3.9 from typing import ( TYPE_CHECKING, + Any, Callable, Literal, Optional, @@ -46,6 +47,7 @@ __all__ = ( "text", + "code", "plot", "image", "table", @@ -61,6 +63,17 @@ class text(Renderer[str]): """ Reactively render text. + When used in Shiny Express applications, this defaults to displaying the text as + normal text on the web page. When used in Shiny Core applications, this should be + paired with :func:`~shiny.ui.output_text` in the UI. + + + Parameters + ---------- + inline + Used in Shiny Express only. If ``True``, the result is displayed inline. (This + argument is passed to :func:`~shiny.ui.output_text`.) + Returns ------- : @@ -74,14 +87,92 @@ class text(Renderer[str]): See Also -------- + ~shiny.render.code ~shiny.ui.output_text """ - def default_ui(self, id: str, placeholder: bool | MISSING_TYPE = MISSING) -> Tag: + def default_ui( + self, + id: str, + *, + inline: bool | MISSING_TYPE = MISSING, + ) -> Tag: + kwargs: dict[str, Any] = {} + set_kwargs_value(kwargs, "inline", inline, self.inline) + + return _ui.output_text(id, **kwargs) + + def __init__( + self, + _fn: Optional[ValueFn[str]] = None, + *, + inline: bool = False, + ) -> None: + super().__init__(_fn) + self.inline = inline + + async def transform(self, value: str) -> Jsonifiable: + return str(value) + + +# ====================================================================================== +# RenderCode +# ====================================================================================== + + +class code(Renderer[str]): + """ + Reactively render text as code (monospaced). + + When used in Shiny Express applications, this defaults to displaying the text in a + monospace font in a code block. When used in Shiny Core applications, this should be + paired with :func:`~shiny.ui.output_text_verbatim` in the UI. + + Parameters + ---------- + placeholder + Used in Shiny Express only. If the output is empty or ``None``, should an empty + rectangle be displayed to serve as a placeholder? This does not affect behavior + when the output is nonempty. (This argument is passed to + :func:`~shiny.ui.output_text_verbatim`.) + + + Returns + ------- + : + A decorator for a function that returns a string. + + Tip + ---- + The name of the decorated function (or ``@output(id=...)``) should match the ``id`` + of a :func:`~shiny.ui.output_text_verbatim` container (see + :func:`~shiny.ui.output_text_verbatim` for example usage). + + See Also + -------- + ~shiny.render.text + ~shiny.ui.output_text_verbatim + """ + + def default_ui( + self, + id: str, + *, + placeholder: bool | MISSING_TYPE = MISSING, + ) -> Tag: kwargs: dict[str, bool] = {} - set_kwargs_value(kwargs, "placeholder", placeholder, None) + set_kwargs_value(kwargs, "placeholder", placeholder, self.placeholder) return _ui.output_text_verbatim(id, **kwargs) + def __init__( + self, + _fn: Optional[ValueFn[str]] = None, + *, + placeholder: bool = False, + ) -> None: + super().__init__(_fn) + self.placeholder = placeholder + async def transform(self, value: str) -> Jsonifiable: return str(value) From 8a530b01c22917e9aa55b62fae50e9d521b97b88 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Fri, 12 Jan 2024 16:02:39 -0600 Subject: [PATCH 02/25] Add output_code --- docs/_quartodoc.yml | 1 + shiny/render/_render.py | 16 ++++++++-------- shiny/ui/__init__.py | 2 ++ shiny/ui/_output.py | 42 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/docs/_quartodoc.yml b/docs/_quartodoc.yml index 967abf63c..921a453f9 100644 --- a/docs/_quartodoc.yml +++ b/docs/_quartodoc.yml @@ -158,6 +158,7 @@ quartodoc: - ui.output_table - ui.output_data_frame - ui.output_text + - ui.output_code - ui.output_text_verbatim - ui.output_ui - render.plot diff --git a/shiny/render/_render.py b/shiny/render/_render.py index eb618c92d..30f978402 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -126,7 +126,7 @@ class code(Renderer[str]): When used in Shiny Express applications, this defaults to displaying the text in a monospace font in a code block. When used in Shiny Core applications, this should be - paired with :func:`~shiny.ui.output_text_verbatim` in the UI. + paired with :func:`~shiny.ui.output_code` in the UI. Parameters ---------- @@ -134,7 +134,7 @@ class code(Renderer[str]): Used in Shiny Express only. If the output is empty or ``None``, should an empty rectangle be displayed to serve as a placeholder? This does not affect behavior when the output is nonempty. (This argument is passed to - :func:`~shiny.ui.output_text_verbatim`.) + :func:`~shiny.ui.output_code`.) Returns @@ -145,13 +145,13 @@ class code(Renderer[str]): Tip ---- The name of the decorated function (or ``@output(id=...)``) should match the ``id`` - of a :func:`~shiny.ui.output_text_verbatim` container (see - :func:`~shiny.ui.output_text_verbatim` for example usage). + of a :func:`~shiny.ui.output_code` container (see :func:`~shiny.ui.output_code` for + example usage). See Also -------- - ~shiny.render.text - ~shiny.ui.output_text_verbatim + ~shiny.render.code + ~shiny.ui.output_code """ def default_ui( @@ -162,13 +162,13 @@ def default_ui( ) -> Tag: kwargs: dict[str, bool] = {} set_kwargs_value(kwargs, "placeholder", placeholder, self.placeholder) - return _ui.output_text_verbatim(id, **kwargs) + return _ui.output_code(id, **kwargs) def __init__( self, _fn: Optional[ValueFn[str]] = None, *, - placeholder: bool = False, + placeholder: bool = True, ) -> None: super().__init__(_fn) self.placeholder = placeholder diff --git a/shiny/ui/__init__.py b/shiny/ui/__init__.py index 49a725aab..70cc4108d 100644 --- a/shiny/ui/__init__.py +++ b/shiny/ui/__init__.py @@ -110,6 +110,7 @@ output_plot, output_image, output_text, + output_code, output_text_verbatim, output_table, output_ui, @@ -296,6 +297,7 @@ "output_plot", "output_image", "output_text", + "output_code", "output_text_verbatim", "output_table", "output_ui", diff --git a/shiny/ui/_output.py b/shiny/ui/_output.py index 7accac6a1..aab99a35a 100644 --- a/shiny/ui/_output.py +++ b/shiny/ui/_output.py @@ -4,6 +4,7 @@ "output_plot", "output_image", "output_text", + "output_code", "output_text_verbatim", "output_table", "output_ui", @@ -270,6 +271,47 @@ def output_text( return container(id=resolve_id(id), class_="shiny-text-output") +def output_code(id: str, placeholder: bool = True) -> Tag: + """ + Create a output container for code (monospaced text). + + This is similar to :func:`~shiny.ui.output_text`, except that it displays the text + in a fixed-width container with a gray-ish background color and border. + + Parameters + ---------- + id + An output id. + placeholder + If the output is empty or ``None``, should an empty rectangle be displayed to + serve as a placeholder? (This does not affect behavior when the output is + nonempty.) + + Returns + ------- + : + A UI element + + Note + ---- + This function is currently the same as :func:`~shiny.ui.output_text_verbatim`, but + this may change in future versions of Shiny. + + See Also + -------- + * :func:`~shiny.render.text` + * :func:`~shiny.ui.output_text` + * :func:`~shiny.ui.output_text_verbatim` + + Example + ------- + See :func:`~shiny.ui.output_text` + """ + + cls = "shiny-text-output" + (" noplaceholder" if not placeholder else "") + return tags.pre(id=resolve_id(id), class_=cls) + + def output_text_verbatim(id: str, placeholder: bool = False) -> Tag: """ Create a output container for some text. From f7d0a982c742bb0e1da3dea5a27e1048d9efb3eb Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Fri, 12 Jan 2024 16:30:38 -0600 Subject: [PATCH 03/25] Update tests --- tests/pytest/test_display_decorator.py | 4 ++-- tests/pytest/test_express_ui.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/pytest/test_display_decorator.py b/tests/pytest/test_display_decorator.py index bfdb51b7e..aa8f330dc 100644 --- a/tests/pytest/test_display_decorator.py +++ b/tests/pytest/test_display_decorator.py @@ -165,7 +165,7 @@ def annotated(x: int, y: int) -> int: def test_implicit_output(): @display_body() def has_implicit_outputs(): - @render.text + @render.code def foo(): return "hello" @@ -173,7 +173,7 @@ def foo(): has_implicit_outputs() assert len(d) == 1 d0 = cast(Tagifiable, d[0]) - assert d0.tagify() == ui.output_text_verbatim("foo") + assert d0.tagify() == ui.output_code("foo") def test_no_nested_transform_unless_explicit(): diff --git a/tests/pytest/test_express_ui.py b/tests/pytest/test_express_ui.py index 2f5a7cc0d..4f97c9d5e 100644 --- a/tests/pytest/test_express_ui.py +++ b/tests/pytest/test_express_ui.py @@ -50,7 +50,7 @@ def text1(): assert ( ui.TagList(text1.tagify()).get_html_string() - == ui.output_text_verbatim("text1").get_html_string() + == ui.output_text("text1").get_html_string() ) @suspend_display @@ -61,22 +61,22 @@ def text2(): assert ui.TagList(text2.tagify()).get_html_string() == "" @ui_kwargs(placeholder=True) - @render.text - def text3(): + @render.code + def code1(): return "text" assert ( - ui.TagList(text3.tagify()).get_html_string() - == ui.output_text_verbatim("text3", placeholder=True).get_html_string() + ui.TagList(code1.tagify()).get_html_string() + == ui.output_code("code1", placeholder=True).get_html_string() ) @ui_kwargs(width=100) - @render.text - def text4(): + @render.code + def code2(): return "text" with pytest.raises(TypeError, match="width"): - text4.tagify() + code2.tagify() def test_suspend_display(): From 426dca367a2fb3aaa958bc9b25434d97912eb655 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Fri, 12 Jan 2024 16:20:53 -0600 Subject: [PATCH 04/25] Remove output_* functions from shiny.express.ui --- shiny/express/ui/__init__.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/shiny/express/ui/__init__.py b/shiny/express/ui/__init__.py index 730380281..533371a84 100644 --- a/shiny/express/ui/__init__.py +++ b/shiny/express/ui/__init__.py @@ -38,8 +38,6 @@ SliderStepArg, SliderValueArg, ValueBoxTheme, - download_button, - download_link, brush_opts, click_opts, dblclick_opts, @@ -95,14 +93,7 @@ notification_show, notification_remove, nav_spacer, - output_plot, - output_image, - output_text, - output_text_verbatim, - output_table, - output_ui, Progress, - output_data_frame, value_box_theme, ) @@ -178,8 +169,6 @@ "SliderStepArg", "SliderValueArg", "ValueBoxTheme", - "download_button", - "download_link", "brush_opts", "click_opts", "dblclick_opts", @@ -235,14 +224,7 @@ "notification_show", "notification_remove", "nav_spacer", - "output_plot", - "output_image", - "output_text", - "output_text_verbatim", - "output_table", - "output_ui", "Progress", - "output_data_frame", "value_box_theme", # Imports from ._cm_components "sidebar", @@ -301,6 +283,17 @@ "showcase_bottom", "showcase_left_center", "showcase_top_right", + # Outputs automatically placed by render functions + "download_button", + "download_link", + "output_plot", + "output_image", + "output_text", + "output_code", + "output_text_verbatim", + "output_table", + "output_ui", + "output_data_frame", ), # Items from shiny.express.ui that don't have a counterpart in shiny.ui "shiny.express.ui": ("page_opts",), From 4df0453d9fad645cbbed5106c72c5937c1512fbb Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Fri, 12 Jan 2024 20:02:09 -0600 Subject: [PATCH 05/25] Add more output args to render functions --- shiny/render/_render.py | 210 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 195 insertions(+), 15 deletions(-) diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 30f978402..58060bed9 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -9,7 +9,6 @@ # Can use `dict` in python >= 3.9 from typing import ( TYPE_CHECKING, - Any, Callable, Literal, Optional, @@ -19,7 +18,7 @@ runtime_checkable, ) -from htmltools import Tag, TagAttrValue, TagChild +from htmltools import Tag, TagAttrValue, TagChild, TagFunction if TYPE_CHECKING: from ..session._utils import RenderedDeps @@ -32,6 +31,7 @@ from ..session import get_current_session, require_active_session from ..session._session import DownloadHandler, DownloadInfo from ..types import MISSING, MISSING_TYPE, ImgData +from ..ui._plot_output_opts import BrushOpts, ClickOpts, DblClickOpts, HoverOpts from ._try_render_plot import ( PlotSizeInfo, try_render_matplotlib, @@ -94,22 +94,23 @@ class text(Renderer[str]): def default_ui( self, id: str, - *, - inline: bool | MISSING_TYPE = MISSING, ) -> Tag: - kwargs: dict[str, Any] = {} - set_kwargs_value(kwargs, "inline", inline, self.inline) - - return _ui.output_text(id, **kwargs) + return _ui.output_text( + id, + inline=self.inline, + container=self.container, + ) def __init__( self, _fn: Optional[ValueFn[str]] = None, *, inline: bool = False, + container: Optional[TagFunction] = None, ) -> None: super().__init__(_fn) self.inline = inline + self.container = container async def transform(self, value: str) -> Jsonifiable: return str(value) @@ -207,6 +208,43 @@ class plot(Renderer[object]): determined by the size of the corresponding :func:`~shiny.ui.output_plot`. (You should not need to use this argument in most Shiny apps--set the desired height on :func:`~shiny.ui.output_plot` instead.) + inline + (Express only) If ``True``, the result is displayed inline. + click + (Express only) This can be a boolean or an object created by + :func:`~shiny.ui.click_opts`. The default is `False`, but if you use `True` (or + equivalently, `click_opts()`), the plot will send coordinates to the server + whenever it is clicked, and the value will be accessible via `input.xx_click()`, + where `xx` is replaced with the ID of this plot. The input value will be a + dictionary with `x` and `y` elements indicating the mouse position. + dblclick + (Express only) This is just like the `click` parameter, but for double-click + events. + hover + (Express only) Similar to the `click` argument, this can be a boolean or an + object created by :func:`~shiny.ui.hover_opts`. The default is `False`, but if + you use `True` (or equivalently, `hover_opts()`), the plot will send coordinates + to the server whenever it is clicked, and the value will be accessible via + `input.xx_hover()`, where `xx` is replaced with the ID of this plot. The input + value will be a dictionary with `x` and `y` elements indicating the mouse + position. To control the hover time or hover delay type, use + :func:`~shiny.ui.hover_opts`. + brush + (Express only) Similar to the `click` argument, this can be a boolean or an + object created by :func:`~shiny.ui.brush_opts`. The default is `False`, but if + you use `True` (or equivalently, `brush_opts()`), the plot will allow the user + to "brush" in the plotting area, and will send information about the brushed + area to the server, and the value will be accessible via `input.plot_brush()`. + Brushing means that the user will be able to draw a rectangle in the plotting + area and drag it around. The value will be a named list with `xmin`, `xmax`, + `ymin`, and `ymax` elements indicating the brush area. To control the brush + behavior, use :func:`~shiny.ui.brush_opts`. Multiple + `output_image`/`output_plot` calls may share the same `id` value; brushing one + image or plot will cause any other brushes with the same `id` to disappear. + fill + (Express only) Whether or not to allow the image output to grow/shrink to fit a + fillable container with an opinionated height (e.g., + :func:`~shiny.ui.page_fillable`). **kwargs Additional keyword arguments passed to the relevant method for saving the image (e.g., for matplotlib, arguments to ``savefig()``; for PIL and plotnine, @@ -254,9 +292,16 @@ def default_ui( set_kwargs_value(kwargs, "height", height, self.height) return _ui.output_plot( id, + inline=self.inline, + click=self.click, + dblclick=self.dblclick, + hover=self.hover, + brush=self.brush, + fill=self.fill, # (possibly) contains `width` and `height` keys! **kwargs, # pyright: ignore[reportGeneralTypeIssues] ) + # TODO: Deal with kwargs and width/height def __init__( self, @@ -265,6 +310,12 @@ def __init__( alt: Optional[str] = None, width: float | None | MISSING_TYPE = MISSING, height: float | None | MISSING_TYPE = MISSING, + inline: bool = False, + click: bool | ClickOpts = False, + dblclick: bool | DblClickOpts = False, + hover: bool | HoverOpts = False, + brush: bool | BrushOpts = False, + fill: bool | MISSING_TYPE = MISSING, **kwargs: object, ) -> None: super().__init__(_fn) @@ -273,6 +324,13 @@ def __init__( self.height = height self.kwargs = kwargs + self.inline = inline + self.click = click + self.dblclick = dblclick + self.hover = hover + self.brush = brush + self.fill = fill + async def render(self) -> dict[str, Jsonifiable] | Jsonifiable | None: is_userfn_async = self.fn.is_async() name = self.output_id @@ -393,6 +451,43 @@ class image(Renderer[ImgData]): ---------- delete_file If ``True``, the image file will be deleted after rendering. + inline + (Express only) If ``True``, the result is displayed inline. + click + (Express only) This can be a boolean or an object created by + :func:`~shiny.ui.click_opts`. The default is `False`, but if you use `True` (or + equivalently, `click_opts()`), the plot will send coordinates to the server + whenever it is clicked, and the value will be accessible via `input.xx_click()`, + where `xx` is replaced with the ID of this plot. The input value will be a + dictionary with `x` and `y` elements indicating the mouse position. + dblclick + (Express only) This is just like the `click` parameter, but for double-click + events. + hover + (Express only) Similar to the `click` argument, this can be a boolean or an + object created by :func:`~shiny.ui.hover_opts`. The default is `False`, but if + you use `True` (or equivalently, `hover_opts()`), the plot will send coordinates + to the server whenever it is clicked, and the value will be accessible via + `input.xx_hover()`, where `xx` is replaced with the ID of this plot. The input + value will be a dictionary with `x` and `y` elements indicating the mouse + position. To control the hover time or hover delay type, use + :func:`~shiny.ui.hover_opts`. + brush + (Express only) Similar to the `click` argument, this can be a boolean or an + object created by :func:`~shiny.ui.brush_opts`. The default is `False`, but if + you use `True` (or equivalently, `brush_opts()`), the plot will allow the user + to "brush" in the plotting area, and will send information about the brushed + area to the server, and the value will be accessible via `input.plot_brush()`. + Brushing means that the user will be able to draw a rectangle in the plotting + area and drag it around. The value will be a named list with `xmin`, `xmax`, + `ymin`, and `ymax` elements indicating the brush area. To control the brush + behavior, use :func:`~shiny.ui.brush_opts`. Multiple + `output_image`/`output_plot` calls may share the same `id` value; brushing one + image or plot will cause any other brushes with the same `id` to disappear. + fill + (Express only) Whether or not to allow the image output to grow/shrink to fit a + fillable container with an opinionated height (e.g., + :func:`~shiny.ui.page_fillable`). Returns ------- @@ -415,17 +510,38 @@ class image(Renderer[ImgData]): def default_ui(self, id: str, **kwargs: object): return _ui.output_image( id, + inline=self.inline, + click=self.click, + dblclick=self.dblclick, + hover=self.hover, + brush=self.brush, + fill=self.fill, **kwargs, # pyright: ignore[reportGeneralTypeIssues] ) + # TODO: Figure out where these **kwargs come from. + # TODO: Deal with kwargs and width/height def __init__( self, _fn: Optional[ValueFn[ImgData]] = None, *, delete_file: bool = False, + inline: bool = False, + click: bool | ClickOpts = False, + dblclick: bool | DblClickOpts = False, + hover: bool | HoverOpts = False, + brush: bool | BrushOpts = False, + fill: bool = False, ) -> None: super().__init__(_fn) - self.delete_file: bool = delete_file + + self.delete_file = delete_file + self.inline = inline + self.click = click + self.dblclick = dblclick + self.hover = hover + self.brush = brush + self.fill = fill async def transform(self, value: ImgData) -> dict[str, Jsonifiable] | None: src: str = value.get("src") @@ -521,6 +637,8 @@ def __init__( self.border: int = border self.kwargs: dict[str, object] = kwargs + # TODO: deal with kwargs collision with output_table + async def transform(self, value: TableResult) -> dict[str, Jsonifiable]: import pandas import pandas.io.formats.style @@ -562,6 +680,22 @@ class ui(Renderer[TagChild]): """ Reactively render HTML content. + Parameters + ---------- + inline + (Express only) If ``True``, the result is displayed inline. + container + (Express only) A Callable that returns the output container. + fill + (Express only) Whether or not to allow the UI output to grow/shrink to fit a + fillable container with an opinionated height (e.g., + :func:`~shiny.ui.page_fillable`). + fillable + (Express only) Whether or not the UI output area should be considered a fillable + (i.e., flexbox) container. + **kwargs + (Express only) Attributes to be applied to the output container. + Returns ------- : @@ -580,7 +714,35 @@ class ui(Renderer[TagChild]): """ def default_ui(self, id: str) -> Tag: - return _ui.output_ui(id) + return _ui.output_ui( + id, + inline=self.inline, + container=self.container, + fill=self.fill, + fillable=self.fillable, + **self.kwargs, + ) + + def __init__( + self, + fn: Optional[ValueFn[TagChild]] = None, + *, + inline: bool = False, + container: Optional[TagFunction] = None, + fill: bool = False, + fillable: bool = False, + **kwargs: TagAttrValue, + ) -> None: + super().__init__() + + self.inline = inline + self.container = container + self.fill = fill + self.fillable = fillable + self.kwargs = kwargs + + if fn is not None: + self(fn) async def transform(self, value: TagChild) -> Jsonifiable: session = require_active_session(None) @@ -600,12 +762,18 @@ class download(Renderer[str]): ---------- filename The filename of the download. - label - A label for the button, when used in Express mode. Defaults to "Download". media_type The media type of the download. encoding The encoding of the download. + label + (Express only) A label for the button. Defaults to "Download". + icon + (Express only) An icon to display on the button. + width + (Express only) The width of the button. + **kwargs + (Express only) Additional attributes for the button. Returns ------- @@ -618,23 +786,35 @@ class download(Renderer[str]): """ def default_ui(self, id: str) -> Tag: - return _ui.download_button(id, label=self.label) + return _ui.download_button( + id, + label=self.label, + icon=self.icon, + width=self.width, + **self.kwargs, + ) def __init__( self, fn: Optional[DownloadHandler] = None, *, filename: Optional[str | Callable[[], str]] = None, - label: TagChild = "Download", media_type: None | str | Callable[[], str] = None, encoding: str = "utf-8", + label: TagChild = "Download", + icon: TagChild = None, + width: Optional[str] = None, + **kwargs: TagAttrValue, ) -> None: super().__init__() - self.label = label self.filename = filename self.media_type = media_type self.encoding = encoding + self.label = label + self.icon = icon + self.width = width + self.kwargs = kwargs if fn is not None: self(fn) From e577bf3349cce420cc414a08a1e7e7914f5ad202 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Fri, 12 Jan 2024 20:09:25 -0600 Subject: [PATCH 06/25] Remove ui_kwargs function --- shiny/express/__init__.py | 2 -- shiny/express/_output.py | 38 +------------------------------- tests/pytest/test_express_ui.py | 13 ++--------- tests/pytest/test_plot_sizing.py | 1 - 4 files changed, 3 insertions(+), 51 deletions(-) diff --git a/shiny/express/__init__.py b/shiny/express/__init__.py index 1c430267f..96821610f 100644 --- a/shiny/express/__init__.py +++ b/shiny/express/__init__.py @@ -7,7 +7,6 @@ from . import ui from ._is_express import is_express_app from ._output import ( # noqa: F401 - ui_kwargs, suspend_display, output_args, # pyright: ignore[reportUnusedImport] ) @@ -19,7 +18,6 @@ "output", "session", "is_express_app", - "ui_kwargs", "suspend_display", "wrap_express_app", "ui", diff --git a/shiny/express/_output.py b/shiny/express/_output.py index 6ab1362d8..d807ed932 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -11,49 +11,13 @@ from ..render.transformer import OutputRenderer from ..render.transformer._transformer import OT -__all__ = ( - "ui_kwargs", - "suspend_display", -) +__all__ = ("suspend_display",) P = ParamSpec("P") R = TypeVar("R") CallableT = TypeVar("CallableT", bound=Callable[..., object]) -# TODO-barret-future; quartodoc entry? -def ui_kwargs( - **kwargs: object, -) -> Callable[[RendererBaseT], RendererBaseT]: - """ - Sets default UI arguments for a Shiny rendering function. - - Each Shiny render function (like :func:`~shiny.render.plot`) can display itself when - declared within a Shiny inline-style application. In the case of - :func:`~shiny.render.plot`, the :func:`~shiny.ui.output_plot` function is called - implicitly to display the plot. Use the `@ui_kwargs` decorator to specify - arguments to be passed to `output_plot` (or whatever the corresponding UI function - is) when the render function displays itself. - - Parameters - ---------- - **kwargs - Keyword arguments to be passed to the UI function. - - Returns - ------- - : - A decorator that sets the default UI arguments for a Shiny rendering function. - """ - - def wrapper(renderer: RendererBaseT) -> RendererBaseT: - # renderer._default_ui_args = args - renderer._default_ui_kwargs = kwargs - return renderer - - return wrapper - - def output_args( *args: object, **kwargs: object, diff --git a/tests/pytest/test_express_ui.py b/tests/pytest/test_express_ui.py index 4f97c9d5e..510e1d54c 100644 --- a/tests/pytest/test_express_ui.py +++ b/tests/pytest/test_express_ui.py @@ -6,7 +6,7 @@ import pytest from shiny import render, ui -from shiny.express import suspend_display, ui_kwargs +from shiny.express import suspend_display from shiny.express._run import run_express @@ -60,8 +60,7 @@ def text2(): assert ui.TagList(text2.tagify()).get_html_string() == "" - @ui_kwargs(placeholder=True) - @render.code + @render.code(placeholder=True) def code1(): return "text" @@ -70,14 +69,6 @@ def code1(): == ui.output_code("code1", placeholder=True).get_html_string() ) - @ui_kwargs(width=100) - @render.code - def code2(): - return "text" - - with pytest.raises(TypeError, match="width"): - code2.tagify() - def test_suspend_display(): old_displayhook = sys.displayhook diff --git a/tests/pytest/test_plot_sizing.py b/tests/pytest/test_plot_sizing.py index da6a354fc..59a242873 100644 --- a/tests/pytest/test_plot_sizing.py +++ b/tests/pytest/test_plot_sizing.py @@ -1,5 +1,4 @@ from shiny import render, ui -from shiny.express import ui_kwargs from shiny.types import MISSING From b4ed7ff15907846ea6e88e57223e4cca2a95c9c2 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Fri, 12 Jan 2024 20:13:24 -0600 Subject: [PATCH 07/25] Deprecate output_args() --- shiny/express/_output.py | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/shiny/express/_output.py b/shiny/express/_output.py index d807ed932..04a93de6c 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -6,6 +6,7 @@ from typing import Callable, Generator, TypeVar, overload from .. import ui +from .._deprecated import warn_deprecated from .._typing_extensions import ParamSpec from ..render.renderer import RendererBase, RendererBaseT from ..render.transformer import OutputRenderer @@ -23,28 +24,11 @@ def output_args( **kwargs: object, ) -> Callable[[OutputRenderer[OT]], OutputRenderer[OT]]: """ - Sets default UI arguments for a Shiny rendering function. - - Each Shiny render function (like :func:`~shiny.render.plot`) can display itself when - declared within a Shiny inline-style application. In the case of - :func:`~shiny.render.plot`, the :func:`~shiny.ui.output_plot` function is called - implicitly to display the plot. Use the `@output_args` decorator to specify - arguments to be passed to `output_plot` (or whatever the corresponding UI function - is) when the render function displays itself. - - - Parameters - ---------- - *args - Positional arguments to be passed to the UI function. - **kwargs - Keyword arguments to be passed to the UI function. - - Returns - ------- - : - A decorator that sets the default UI arguments for a Shiny rendering function. + Deprecated. Sets default UI arguments for a Shiny rendering function. """ + warn_deprecated( + "`shiny.express.output_args()` is deprecated and will be removed in a future version of shiny." + ) def wrapper(renderer: OutputRenderer[OT]) -> OutputRenderer[OT]: if not isinstance(renderer, OutputRenderer): From ec0419ae5c0d108e496b534e862921273c290976 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Fri, 12 Jan 2024 20:25:06 -0600 Subject: [PATCH 08/25] Clean up default_ui methods --- shiny/render/_render.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 58060bed9..d1050654d 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -91,10 +91,7 @@ class text(Renderer[str]): ~shiny.ui.output_text """ - def default_ui( - self, - id: str, - ) -> Tag: + def default_ui(self, id: str) -> Tag: return _ui.output_text( id, inline=self.inline, @@ -155,15 +152,8 @@ class code(Renderer[str]): ~shiny.ui.output_code """ - def default_ui( - self, - id: str, - *, - placeholder: bool | MISSING_TYPE = MISSING, - ) -> Tag: - kwargs: dict[str, bool] = {} - set_kwargs_value(kwargs, "placeholder", placeholder, self.placeholder) - return _ui.output_code(id, **kwargs) + def default_ui(self, id: str) -> Tag: + return _ui.output_code(id, placeholder=self.placeholder) def __init__( self, @@ -507,7 +497,7 @@ class image(Renderer[ImgData]): ~shiny.render.plot """ - def default_ui(self, id: str, **kwargs: object): + def default_ui(self, id: str): return _ui.output_image( id, inline=self.inline, @@ -516,10 +506,8 @@ def default_ui(self, id: str, **kwargs: object): hover=self.hover, brush=self.brush, fill=self.fill, - **kwargs, # pyright: ignore[reportGeneralTypeIssues] ) - # TODO: Figure out where these **kwargs come from. - # TODO: Deal with kwargs and width/height + # TODO: Deal with width/height def __init__( self, @@ -621,6 +609,7 @@ class table(Renderer[TableResult]): def default_ui(self, id: str, **kwargs: TagAttrValue) -> Tag: return _ui.output_table(id, **kwargs) + # TODO: Deal with kwargs def __init__( self, From 8c77bb6400dfdc4bf7d2ac726f068e1b1e0d43e9 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 11:01:30 -0600 Subject: [PATCH 09/25] More consistent output arg handling --- shiny/render/_render.py | 42 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/shiny/render/_render.py b/shiny/render/_render.py index d1050654d..b51ce5d94 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -269,17 +269,14 @@ class plot(Renderer[object]): ~shiny.ui.output_plot ~shiny.render.image """ - def default_ui( - self, - id: str, - *, - width: str | float | int | MISSING_TYPE = MISSING, - height: str | float | int | MISSING_TYPE = MISSING, - **kwargs: object, - ) -> Tag: - # Only set the arg if it is available. (Prevents duplicating default values) - set_kwargs_value(kwargs, "width", width, self.width) - set_kwargs_value(kwargs, "height", height, self.height) + def default_ui(self, id: str) -> Tag: + kwargs: dict[str, object] = {} + # Only set the arg if it is available + if not isinstance(self.width, MISSING_TYPE): + kwargs["width"] = self.width + if not isinstance(self.height, MISSING_TYPE): + kwargs["height"] = self.height + return _ui.output_plot( id, inline=self.inline, @@ -288,10 +285,9 @@ def default_ui( hover=self.hover, brush=self.brush, fill=self.fill, - # (possibly) contains `width` and `height` keys! **kwargs, # pyright: ignore[reportGeneralTypeIssues] ) - # TODO: Deal with kwargs and width/height + # TODO: Deal with output width/height separately from render width/height? def __init__( self, @@ -441,6 +437,14 @@ class image(Renderer[ImgData]): ---------- delete_file If ``True``, the image file will be deleted after rendering. + width + (Express only) The CSS width, e.g. '400px', or '100%'. Note that this only + affects the output HTML element on the web page, not the actual image data sent + to the browser. + height + (Express only) The CSS height, e.g. '100%' or '600px'. Note that this only + affects the output HTML element on the web page, not the actual image data sent + to the browser. inline (Express only) If ``True``, the result is displayed inline. click @@ -492,14 +496,14 @@ class image(Renderer[ImgData]): See Also -------- - ~shiny.ui.output_image - ~shiny.types.ImgData - ~shiny.render.plot + ~shiny.ui.output_image ~shiny.types.ImgData ~shiny.render.plot """ def default_ui(self, id: str): return _ui.output_image( id, + width=self.width, + height=self.height, inline=self.inline, click=self.click, dblclick=self.dblclick, @@ -507,13 +511,15 @@ def default_ui(self, id: str): brush=self.brush, fill=self.fill, ) - # TODO: Deal with width/height + # TODO: Make width/height handling consistent with render_plot def __init__( self, _fn: Optional[ValueFn[ImgData]] = None, *, delete_file: bool = False, + width: str | float | int = "100%", + height: str | float | int = "400px", inline: bool = False, click: bool | ClickOpts = False, dblclick: bool | DblClickOpts = False, @@ -524,6 +530,8 @@ def __init__( super().__init__(_fn) self.delete_file = delete_file + self.width = width + self.height = height self.inline = inline self.click = click self.dblclick = dblclick From c87ef09a13186c72225b527598d2aba14b94deaa Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 11:12:11 -0600 Subject: [PATCH 10/25] Disable ui_kwargs tests --- tests/pytest/test_plot_sizing.py | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/pytest/test_plot_sizing.py b/tests/pytest/test_plot_sizing.py index 59a242873..0340922ac 100644 --- a/tests/pytest/test_plot_sizing.py +++ b/tests/pytest/test_plot_sizing.py @@ -26,38 +26,38 @@ def foo(): assert rendered == str(ui.output_plot("foo")) -def test_decorator_ui_kwargs(): - """@ui_kwargs is respected""" +# def test_decorator_ui_kwargs(): +# """@ui_kwargs is respected""" - @ui_kwargs(width="640px", height="480px") - @render.plot() - def foo(): - ... +# @ui_kwargs(width="640px", height="480px") +# @render.plot() +# def foo(): +# ... - rendered = str(foo.tagify()) - assert rendered == str(ui.output_plot("foo", width="640px", height="480px")) +# rendered = str(foo.tagify()) +# assert rendered == str(ui.output_plot("foo", width="640px", height="480px")) -def test_decorator_ui_kwargs_priority(): - """@ui_kwargs should override render.plot width/height""" +# def test_decorator_ui_kwargs_priority(): +# """@ui_kwargs should override render.plot width/height""" - @ui_kwargs(width="640px", height=480) - @render.plot(width=1280, height=960) - def foo(): - ... +# @ui_kwargs(width="640px", height=480) +# @render.plot(width=1280, height=960) +# def foo(): +# ... - rendered = str(foo.tagify()) - # Note "640px" => 640 and 480 => "480px" - assert rendered == str(ui.output_plot("foo", width=640, height="480px")) +# rendered = str(foo.tagify()) +# # Note "640px" => 640 and 480 => "480px" +# assert rendered == str(ui.output_plot("foo", width=640, height="480px")) -def test_decorator_ui_kwargs_MISSING(): - """Not saying we support this, but test how MISSING interacts""" +# def test_decorator_ui_kwargs_MISSING(): +# """Not saying we support this, but test how MISSING interacts""" - @ui_kwargs(width=MISSING) - @render.plot(width=1280, height=MISSING) - def foo(): - ... +# @ui_kwargs(width=MISSING) +# @render.plot(width=1280, height=MISSING) +# def foo(): +# ... - rendered = str(foo.tagify()) - assert rendered == str(ui.output_plot("foo", width="1280px")) +# rendered = str(foo.tagify()) +# assert rendered == str(ui.output_plot("foo", width="1280px")) From 749eb5576c746f0d8b2dba67460b2d281b311b6a Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 11:47:54 -0600 Subject: [PATCH 11/25] Rename default_ui to auto_output_ui --- shiny/api-examples/Renderer/app.py | 4 +- shiny/express/_output.py | 6 +-- shiny/render/_dataframe.py | 2 +- shiny/render/_display.py | 2 +- shiny/render/_render.py | 20 ++++----- shiny/render/renderer/_renderer.py | 22 +++------- shiny/render/transformer/_transformer.py | 42 +++++++++---------- .../custom_component/custom_component.py | 2 +- .../custom_component/custom_component.py | 2 +- 9 files changed, 43 insertions(+), 59 deletions(-) diff --git a/shiny/api-examples/Renderer/app.py b/shiny/api-examples/Renderer/app.py index 332a470f0..e1a8cb513 100644 --- a/shiny/api-examples/Renderer/app.py +++ b/shiny/api-examples/Renderer/app.py @@ -28,7 +28,7 @@ class render_capitalize(Renderer[str]): Whether to render a placeholder value. (Defaults to `True`) """ - def default_ui(self, id: str): + def auto_output_ui(self, id: str): """ Express UI for the renderer """ @@ -94,7 +94,7 @@ class render_upper(Renderer[str]): Note: This renderer is equivalent to `render_capitalize(to="upper")`. """ - def default_ui(self, id: str): + def auto_output_ui(self, id: str): """ Express UI for the renderer """ diff --git a/shiny/express/_output.py b/shiny/express/_output.py index 04a93de6c..6d5b2c1a8 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -37,8 +37,8 @@ def wrapper(renderer: OutputRenderer[OT]) -> OutputRenderer[OT]: "\nIf you are trying to set default UI arguments for a `Renderer`, use" " `@ui_kwargs` instead." ) - renderer._default_ui_args = args - renderer._default_ui_kwargs = kwargs + renderer._auto_output_ui_args = args + renderer._auto_output_ui_kwargs = kwargs return renderer @@ -100,7 +100,7 @@ def suspend_display( # display yourself" if isinstance(fn, RendererBase): # By setting the class value, the `self` arg will be auto added. - fn.default_ui = null_ui + fn.auto_output_ui = null_ui return fn return suspend_display_ctxmgr()(fn) diff --git a/shiny/render/_dataframe.py b/shiny/render/_dataframe.py index 246aab89f..6ecdc55d5 100644 --- a/shiny/render/_dataframe.py +++ b/shiny/render/_dataframe.py @@ -256,7 +256,7 @@ class data_frame(Renderer[DataFrameResult]): objects you can return from the rendering function to specify options. """ - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: return ui.output_data_frame(id=id) async def transform(self, value: DataFrameResult) -> Jsonifiable: diff --git a/shiny/render/_display.py b/shiny/render/_display.py index 593cdda4d..da597a9f1 100644 --- a/shiny/render/_display.py +++ b/shiny/render/_display.py @@ -18,7 +18,7 @@ class display(Renderer[None]): - def default_ui( + def auto_output_ui( self, id: str, *, diff --git a/shiny/render/_render.py b/shiny/render/_render.py index b51ce5d94..5ab38d7f2 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -39,11 +39,7 @@ try_render_plotnine, ) from .renderer import Jsonifiable, Renderer, ValueFn -from .renderer._utils import ( - imgdata_to_jsonifiable, - rendered_deps_to_jsonifiable, - set_kwargs_value, -) +from .renderer._utils import imgdata_to_jsonifiable, rendered_deps_to_jsonifiable __all__ = ( "text", @@ -91,7 +87,7 @@ class text(Renderer[str]): ~shiny.ui.output_text """ - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: return _ui.output_text( id, inline=self.inline, @@ -152,7 +148,7 @@ class code(Renderer[str]): ~shiny.ui.output_code """ - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: return _ui.output_code(id, placeholder=self.placeholder) def __init__( @@ -269,7 +265,7 @@ class plot(Renderer[object]): ~shiny.ui.output_plot ~shiny.render.image """ - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: kwargs: dict[str, object] = {} # Only set the arg if it is available if not isinstance(self.width, MISSING_TYPE): @@ -499,7 +495,7 @@ class image(Renderer[ImgData]): ~shiny.ui.output_image ~shiny.types.ImgData ~shiny.render.plot """ - def default_ui(self, id: str): + def auto_output_ui(self, id: str): return _ui.output_image( id, width=self.width, @@ -615,7 +611,7 @@ class table(Renderer[TableResult]): ~shiny.ui.output_table for the corresponding UI component to this render function. """ - def default_ui(self, id: str, **kwargs: TagAttrValue) -> Tag: + def auto_output_ui(self, id: str, **kwargs: TagAttrValue) -> Tag: return _ui.output_table(id, **kwargs) # TODO: Deal with kwargs @@ -710,7 +706,7 @@ class ui(Renderer[TagChild]): ~shiny.ui.output_ui """ - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: return _ui.output_ui( id, inline=self.inline, @@ -782,7 +778,7 @@ class download(Renderer[str]): ~shiny.ui.download_button """ - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: return _ui.download_button( id, label=self.label, diff --git a/shiny/render/renderer/_renderer.py b/shiny/render/renderer/_renderer.py index 0be14fc23..5a6cff00f 100644 --- a/shiny/render/renderer/_renderer.py +++ b/shiny/render/renderer/_renderer.py @@ -102,8 +102,8 @@ class RendererBase(ABC): # Q: Could we do this with typing without putting `P` in the Generic? # A: No. Even if we had a `P` in the Generic, the calling decorator would not have access to it. # Idea: Possibly use a chained method of `.ui_kwargs()`? https://github.com/posit-dev/py-shiny/issues/971 - _default_ui_kwargs: dict[str, Any] = dict() - # _default_ui_args: tuple[Any, ...] = tuple() + _auto_output_ui_kwargs: dict[str, Any] = dict() + # _auto_output_ui_args: tuple[Any, ...] = tuple() __name__: str """ @@ -135,12 +135,7 @@ def _set_output_metadata( """ self.output_id = output_name - def default_ui( - self, - id: str, - # *args: object, - # **kwargs: object, - ) -> DefaultUIFnResultOrNone: + def auto_output_ui(self, id: str) -> DefaultUIFnResultOrNone: return None @abstractmethod @@ -155,13 +150,13 @@ def __init__(self) -> None: # Tagify-like methods # ###### def _repr_html_(self) -> str | None: - rendered_ui = self._render_default_ui() + rendered_ui = self.auto_output_ui(self.__name__) if rendered_ui is None: return None return TagList(rendered_ui)._repr_html_() def tagify(self) -> DefaultUIFnResult: - rendered_ui = self._render_default_ui() + rendered_ui = self.auto_output_ui(self.__name__) if rendered_ui is None: raise TypeError( "No default UI exists for this type of render function: ", @@ -169,13 +164,6 @@ def tagify(self) -> DefaultUIFnResult: ) return rendered_ui - def _render_default_ui(self) -> DefaultUIFnResultOrNone: - return self.default_ui( - self.__name__, - # Pass the `@ui_kwargs(foo="bar")` kwargs through to the default_ui function. - **self._default_ui_kwargs, - ) - # ###### # Auto registering output # ###### diff --git a/shiny/render/transformer/_transformer.py b/shiny/render/transformer/_transformer.py index bb7315d18..7a118bf56 100644 --- a/shiny/render/transformer/_transformer.py +++ b/shiny/render/transformer/_transformer.py @@ -220,8 +220,8 @@ def __init__( value_fn: ValueFn[IT], transform_fn: TransformFn[IT, P, OT], params: TransformerParams[P], - default_ui: Optional[DefaultUIFn] = None, - default_ui_passthrough_args: Optional[tuple[str, ...]] = None, + auto_output_ui: Optional[DefaultUIFn] = None, + auto_output_ui_passthrough_args: Optional[tuple[str, ...]] = None, ) -> None: """ Parameters @@ -233,7 +233,7 @@ def __init__( `OT`. The `params` will used as variadic keyword arguments. params App-provided parameters for the transform function (`transform_fn`). - default_ui + auto_output_ui Optional function that takes an `output_id` string and returns a Shiny UI object that can be used to display the output. This allows render functions to respond to `_repr_html_` method calls in environments like Jupyter. @@ -266,11 +266,11 @@ def __init__( self._transformer = transform_fn self._params = params - self._default_ui = default_ui - self._default_ui_passthrough_args = default_ui_passthrough_args + self._auto_output_ui = auto_output_ui + self._auto_output_ui_passthrough_args = auto_output_ui_passthrough_args - self._default_ui_args: tuple[object, ...] = tuple() - self._default_ui_kwargs: dict[str, object] = dict() + self._auto_output_ui_args: tuple[object, ...] = tuple() + self._auto_output_ui_kwargs: dict[str, object] = dict() # Allow for App authors to not require `@output` self._auto_register() @@ -318,24 +318,24 @@ async def _run(self) -> OT: # # Shims for Renderer class ############################# - def default_ui( + def auto_output_ui( self, id: str, **kwargs: object, ) -> DefaultUIFnResultOrNone: - if self._default_ui is None: + if self._auto_output_ui is None: return None - if self._default_ui_passthrough_args is not None: + if self._auto_output_ui_passthrough_args is not None: kwargs.update( { k: v for k, v in self._params.kwargs.items() - if k in self._default_ui_passthrough_args and v is not MISSING + if k in self._auto_output_ui_passthrough_args and v is not MISSING } ) - return self._default_ui(id, *self._default_ui_args, **kwargs) + return self._auto_output_ui(id, *self._auto_output_ui_args, **kwargs) async def render(self) -> Jsonifiable: ret = await self._run() @@ -507,8 +507,8 @@ def __init__( @overload def output_transformer( *, - default_ui: Optional[DefaultUIFn] = None, - default_ui_passthrough_args: Optional[tuple[str, ...]] = None, + auto_output_ui: Optional[DefaultUIFn] = None, + auto_output_ui_passthrough_args: Optional[tuple[str, ...]] = None, ) -> Callable[[TransformFn[IT, P, OT]], OutputTransformer[IT, OT, P]]: ... @@ -524,8 +524,8 @@ def output_transformer( def output_transformer( transform_fn: TransformFn[IT, P, OT] | None = None, *, - default_ui: Optional[DefaultUIFn] = None, - default_ui_passthrough_args: Optional[tuple[str, ...]] = None, + auto_output_ui: Optional[DefaultUIFn] = None, + auto_output_ui_passthrough_args: Optional[tuple[str, ...]] = None, ) -> ( OutputTransformer[IT, OT, P] | Callable[[TransformFn[IT, P, OT]], OutputTransformer[IT, OT, P]] @@ -588,7 +588,7 @@ def output_transformer( Asynchronous function used to determine the app-supplied output value function return type (`IT`), the transformed type (`OT`), and the keyword arguments (`P`) app authors can supply to the renderer decorator. - default_ui + auto_output_ui Optional function that takes an `output_id` string and returns a Shiny UI object that can be used to display the output. This allows render functions to respond to `_repr_html_` method calls in environments like Jupyter. @@ -602,8 +602,8 @@ def output_transformer( called with parentheses. """ - # If default_ui_passthrough_args was used, modify the default_ui function so it is - # ready to mix in extra arguments from the decorator. + # If auto_output_ui_passthrough_args was used, modify the auto_output_ui function so + # it is ready to mix in extra arguments from the decorator. def output_transformer_impl( transform_fn: TransformFn[IT, P, OT], ) -> OutputTransformer[IT, OT, P]: @@ -620,8 +620,8 @@ def as_value_fn( value_fn=fn, transform_fn=transform_fn, params=params, - default_ui=default_ui, - default_ui_passthrough_args=default_ui_passthrough_args, + auto_output_ui=auto_output_ui, + auto_output_ui_passthrough_args=auto_output_ui_passthrough_args, ) if value_fn is None: diff --git a/shiny/templates/package-templates/js-output/custom_component/custom_component.py b/shiny/templates/package-templates/js-output/custom_component/custom_component.py index 2dd5f13a8..1bf568912 100644 --- a/shiny/templates/package-templates/js-output/custom_component/custom_component.py +++ b/shiny/templates/package-templates/js-output/custom_component/custom_component.py @@ -27,7 +27,7 @@ class render_custom_component(Renderer[int]): """ # The UI used within Shiny Express mode - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: return custom_component(id, height=self.height) # The init method is used to set up the renderer's parameters. diff --git a/shiny/templates/package-templates/js-react/custom_component/custom_component.py b/shiny/templates/package-templates/js-react/custom_component/custom_component.py index a8dc26c99..f68f90a12 100644 --- a/shiny/templates/package-templates/js-react/custom_component/custom_component.py +++ b/shiny/templates/package-templates/js-react/custom_component/custom_component.py @@ -39,7 +39,7 @@ class render_custom_component(Renderer[str]): """ # The UI used within Shiny Express mode - def default_ui(self, id: str) -> Tag: + def auto_output_ui(self, id: str) -> Tag: return output_custom_component(id) # # There are no parameters being supplied to the `output_custom_component` rendering function. From fea77c3206654d07169e383be11e9b01e9462fd7 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 16:54:47 -0600 Subject: [PATCH 12/25] Export express.ui.render --- shiny/express/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shiny/express/__init__.py b/shiny/express/__init__.py index 96821610f..8add0557a 100644 --- a/shiny/express/__init__.py +++ b/shiny/express/__init__.py @@ -4,6 +4,7 @@ # console. from ..session import Inputs as _Inputs, Outputs as _Outputs, Session as _Session from ..session import _utils as _session_utils +from .. import render from . import ui from ._is_express import is_express_app from ._output import ( # noqa: F401 @@ -13,7 +14,9 @@ from ._run import wrap_express_app from .display_decorator import display_body + __all__ = ( + "render", "input", "output", "session", From b796514a5bab1668d255512e169546d4219d9ea8 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 17:41:17 -0600 Subject: [PATCH 13/25] Revert adding all output args to render functions --- shiny/express/_output.py | 24 +++- shiny/render/_render.py | 251 ++++++++------------------------------- 2 files changed, 70 insertions(+), 205 deletions(-) diff --git a/shiny/express/_output.py b/shiny/express/_output.py index 6d5b2c1a8..76526e572 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -24,11 +24,27 @@ def output_args( **kwargs: object, ) -> Callable[[OutputRenderer[OT]], OutputRenderer[OT]]: """ - Deprecated. Sets default UI arguments for a Shiny rendering function. + Sets default UI arguments for a Shiny rendering function. + + Each Shiny render function (like :func:`~shiny.render.plot`) can display itself when + declared within a Shiny inline-style application. In the case of + :func:`~shiny.render.plot`, the :func:`~shiny.ui.output_plot` function is called + implicitly to display the plot. Use the `@output_args` decorator to specify + arguments to be passed to `output_plot` (or whatever the corresponding UI function + is) when the render function displays itself. + + Parameters + ---------- + *args + Positional arguments to be passed to the UI function. + **kwargs + Keyword arguments to be passed to the UI function. + + Returns + ------- + : + A decorator that sets the default UI arguments for a Shiny rendering function. """ - warn_deprecated( - "`shiny.express.output_args()` is deprecated and will be removed in a future version of shiny." - ) def wrapper(renderer: OutputRenderer[OT]) -> OutputRenderer[OT]: if not isinstance(renderer, OutputRenderer): diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 5ab38d7f2..af9b6b961 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -9,6 +9,7 @@ # Can use `dict` in python >= 3.9 from typing import ( TYPE_CHECKING, + Any, Callable, Literal, Optional, @@ -39,7 +40,11 @@ try_render_plotnine, ) from .renderer import Jsonifiable, Renderer, ValueFn -from .renderer._utils import imgdata_to_jsonifiable, rendered_deps_to_jsonifiable +from .renderer._utils import ( + imgdata_to_jsonifiable, + rendered_deps_to_jsonifiable, + set_kwargs_value, +) __all__ = ( "text", @@ -67,8 +72,8 @@ class text(Renderer[str]): Parameters ---------- inline - Used in Shiny Express only. If ``True``, the result is displayed inline. (This - argument is passed to :func:`~shiny.ui.output_text`.) + (Express only). If ``True``, the result is displayed inline. (This argument is + passed to :func:`~shiny.ui.output_text`.) Returns ------- @@ -83,27 +88,28 @@ class text(Renderer[str]): See Also -------- - ~shiny.render.code - ~shiny.ui.output_text + ~shiny.render.code ~shiny.ui.output_text """ - def auto_output_ui(self, id: str) -> Tag: - return _ui.output_text( - id, - inline=self.inline, - container=self.container, - ) + def auto_output_ui( + self, + id: str, + *, + inline: bool | MISSING_TYPE = MISSING, + ) -> Tag: + kwargs: dict[str, Any] = {} + set_kwargs_value(kwargs, "inline", inline, self.inline) + + return _ui.output_text(id, **kwargs) def __init__( self, _fn: Optional[ValueFn[str]] = None, *, inline: bool = False, - container: Optional[TagFunction] = None, ) -> None: super().__init__(_fn) self.inline = inline - self.container = container async def transform(self, value: str) -> Jsonifiable: return str(value) @@ -125,10 +131,9 @@ class code(Renderer[str]): Parameters ---------- placeholder - Used in Shiny Express only. If the output is empty or ``None``, should an empty - rectangle be displayed to serve as a placeholder? This does not affect behavior - when the output is nonempty. (This argument is passed to - :func:`~shiny.ui.output_code`.) + (Express only) If the output is empty or ``None``, should an empty rectangle be + displayed to serve as a placeholder? This does not affect behavior when the + output is nonempty. (This argument is passed to :func:`~shiny.ui.output_code`.) Returns @@ -144,12 +149,18 @@ class code(Renderer[str]): See Also -------- - ~shiny.render.code - ~shiny.ui.output_code + ~shiny.render.code ~shiny.ui.output_code """ - def auto_output_ui(self, id: str) -> Tag: - return _ui.output_code(id, placeholder=self.placeholder) + def auto_output_ui( + self, + id: str, + *, + placeholder: bool | MISSING_TYPE = MISSING, + ) -> Tag: + kwargs: dict[str, bool] = {} + set_kwargs_value(kwargs, "placeholder", placeholder, self.placeholder) + return _ui.output_code(id, **kwargs) def __init__( self, @@ -194,43 +205,6 @@ class plot(Renderer[object]): determined by the size of the corresponding :func:`~shiny.ui.output_plot`. (You should not need to use this argument in most Shiny apps--set the desired height on :func:`~shiny.ui.output_plot` instead.) - inline - (Express only) If ``True``, the result is displayed inline. - click - (Express only) This can be a boolean or an object created by - :func:`~shiny.ui.click_opts`. The default is `False`, but if you use `True` (or - equivalently, `click_opts()`), the plot will send coordinates to the server - whenever it is clicked, and the value will be accessible via `input.xx_click()`, - where `xx` is replaced with the ID of this plot. The input value will be a - dictionary with `x` and `y` elements indicating the mouse position. - dblclick - (Express only) This is just like the `click` parameter, but for double-click - events. - hover - (Express only) Similar to the `click` argument, this can be a boolean or an - object created by :func:`~shiny.ui.hover_opts`. The default is `False`, but if - you use `True` (or equivalently, `hover_opts()`), the plot will send coordinates - to the server whenever it is clicked, and the value will be accessible via - `input.xx_hover()`, where `xx` is replaced with the ID of this plot. The input - value will be a dictionary with `x` and `y` elements indicating the mouse - position. To control the hover time or hover delay type, use - :func:`~shiny.ui.hover_opts`. - brush - (Express only) Similar to the `click` argument, this can be a boolean or an - object created by :func:`~shiny.ui.brush_opts`. The default is `False`, but if - you use `True` (or equivalently, `brush_opts()`), the plot will allow the user - to "brush" in the plotting area, and will send information about the brushed - area to the server, and the value will be accessible via `input.plot_brush()`. - Brushing means that the user will be able to draw a rectangle in the plotting - area and drag it around. The value will be a named list with `xmin`, `xmax`, - `ymin`, and `ymax` elements indicating the brush area. To control the brush - behavior, use :func:`~shiny.ui.brush_opts`. Multiple - `output_image`/`output_plot` calls may share the same `id` value; brushing one - image or plot will cause any other brushes with the same `id` to disappear. - fill - (Express only) Whether or not to allow the image output to grow/shrink to fit a - fillable container with an opinionated height (e.g., - :func:`~shiny.ui.page_fillable`). **kwargs Additional keyword arguments passed to the relevant method for saving the image (e.g., for matplotlib, arguments to ``savefig()``; for PIL and plotnine, @@ -265,22 +239,20 @@ class plot(Renderer[object]): ~shiny.ui.output_plot ~shiny.render.image """ - def auto_output_ui(self, id: str) -> Tag: - kwargs: dict[str, object] = {} - # Only set the arg if it is available - if not isinstance(self.width, MISSING_TYPE): - kwargs["width"] = self.width - if not isinstance(self.height, MISSING_TYPE): - kwargs["height"] = self.height - + def auto_output_ui( + self, + id: str, + *, + width: str | float | int | MISSING_TYPE = MISSING, + height: str | float | int | MISSING_TYPE = MISSING, + **kwargs: object, + ) -> Tag: + # Only set the arg if it is available. (Prevents duplicating default values) + set_kwargs_value(kwargs, "width", width, self.width) + set_kwargs_value(kwargs, "height", height, self.height) return _ui.output_plot( id, - inline=self.inline, - click=self.click, - dblclick=self.dblclick, - hover=self.hover, - brush=self.brush, - fill=self.fill, + # (possibly) contains `width` and `height` keys! **kwargs, # pyright: ignore[reportGeneralTypeIssues] ) # TODO: Deal with output width/height separately from render width/height? @@ -292,12 +264,6 @@ def __init__( alt: Optional[str] = None, width: float | None | MISSING_TYPE = MISSING, height: float | None | MISSING_TYPE = MISSING, - inline: bool = False, - click: bool | ClickOpts = False, - dblclick: bool | DblClickOpts = False, - hover: bool | HoverOpts = False, - brush: bool | BrushOpts = False, - fill: bool | MISSING_TYPE = MISSING, **kwargs: object, ) -> None: super().__init__(_fn) @@ -306,13 +272,6 @@ def __init__( self.height = height self.kwargs = kwargs - self.inline = inline - self.click = click - self.dblclick = dblclick - self.hover = hover - self.brush = brush - self.fill = fill - async def render(self) -> dict[str, Jsonifiable] | Jsonifiable | None: is_userfn_async = self.fn.is_async() name = self.output_id @@ -433,51 +392,6 @@ class image(Renderer[ImgData]): ---------- delete_file If ``True``, the image file will be deleted after rendering. - width - (Express only) The CSS width, e.g. '400px', or '100%'. Note that this only - affects the output HTML element on the web page, not the actual image data sent - to the browser. - height - (Express only) The CSS height, e.g. '100%' or '600px'. Note that this only - affects the output HTML element on the web page, not the actual image data sent - to the browser. - inline - (Express only) If ``True``, the result is displayed inline. - click - (Express only) This can be a boolean or an object created by - :func:`~shiny.ui.click_opts`. The default is `False`, but if you use `True` (or - equivalently, `click_opts()`), the plot will send coordinates to the server - whenever it is clicked, and the value will be accessible via `input.xx_click()`, - where `xx` is replaced with the ID of this plot. The input value will be a - dictionary with `x` and `y` elements indicating the mouse position. - dblclick - (Express only) This is just like the `click` parameter, but for double-click - events. - hover - (Express only) Similar to the `click` argument, this can be a boolean or an - object created by :func:`~shiny.ui.hover_opts`. The default is `False`, but if - you use `True` (or equivalently, `hover_opts()`), the plot will send coordinates - to the server whenever it is clicked, and the value will be accessible via - `input.xx_hover()`, where `xx` is replaced with the ID of this plot. The input - value will be a dictionary with `x` and `y` elements indicating the mouse - position. To control the hover time or hover delay type, use - :func:`~shiny.ui.hover_opts`. - brush - (Express only) Similar to the `click` argument, this can be a boolean or an - object created by :func:`~shiny.ui.brush_opts`. The default is `False`, but if - you use `True` (or equivalently, `brush_opts()`), the plot will allow the user - to "brush" in the plotting area, and will send information about the brushed - area to the server, and the value will be accessible via `input.plot_brush()`. - Brushing means that the user will be able to draw a rectangle in the plotting - area and drag it around. The value will be a named list with `xmin`, `xmax`, - `ymin`, and `ymax` elements indicating the brush area. To control the brush - behavior, use :func:`~shiny.ui.brush_opts`. Multiple - `output_image`/`output_plot` calls may share the same `id` value; brushing one - image or plot will cause any other brushes with the same `id` to disappear. - fill - (Express only) Whether or not to allow the image output to grow/shrink to fit a - fillable container with an opinionated height (e.g., - :func:`~shiny.ui.page_fillable`). Returns ------- @@ -492,20 +406,15 @@ class image(Renderer[ImgData]): See Also -------- - ~shiny.ui.output_image ~shiny.types.ImgData ~shiny.render.plot + ~shiny.ui.output_image + ~shiny.types.ImgData + ~shiny.render.plot """ - def auto_output_ui(self, id: str): + def default_ui(self, id: str, **kwargs: object): return _ui.output_image( id, - width=self.width, - height=self.height, - inline=self.inline, - click=self.click, - dblclick=self.dblclick, - hover=self.hover, - brush=self.brush, - fill=self.fill, + **kwargs, # pyright: ignore[reportGeneralTypeIssues] ) # TODO: Make width/height handling consistent with render_plot @@ -514,26 +423,10 @@ def __init__( _fn: Optional[ValueFn[ImgData]] = None, *, delete_file: bool = False, - width: str | float | int = "100%", - height: str | float | int = "400px", - inline: bool = False, - click: bool | ClickOpts = False, - dblclick: bool | DblClickOpts = False, - hover: bool | HoverOpts = False, - brush: bool | BrushOpts = False, - fill: bool = False, ) -> None: super().__init__(_fn) self.delete_file = delete_file - self.width = width - self.height = height - self.inline = inline - self.click = click - self.dblclick = dblclick - self.hover = hover - self.brush = brush - self.fill = fill async def transform(self, value: ImgData) -> dict[str, Jsonifiable] | None: src: str = value.get("src") @@ -673,22 +566,6 @@ class ui(Renderer[TagChild]): """ Reactively render HTML content. - Parameters - ---------- - inline - (Express only) If ``True``, the result is displayed inline. - container - (Express only) A Callable that returns the output container. - fill - (Express only) Whether or not to allow the UI output to grow/shrink to fit a - fillable container with an opinionated height (e.g., - :func:`~shiny.ui.page_fillable`). - fillable - (Express only) Whether or not the UI output area should be considered a fillable - (i.e., flexbox) container. - **kwargs - (Express only) Attributes to be applied to the output container. - Returns ------- : @@ -707,35 +584,7 @@ class ui(Renderer[TagChild]): """ def auto_output_ui(self, id: str) -> Tag: - return _ui.output_ui( - id, - inline=self.inline, - container=self.container, - fill=self.fill, - fillable=self.fillable, - **self.kwargs, - ) - - def __init__( - self, - fn: Optional[ValueFn[TagChild]] = None, - *, - inline: bool = False, - container: Optional[TagFunction] = None, - fill: bool = False, - fillable: bool = False, - **kwargs: TagAttrValue, - ) -> None: - super().__init__() - - self.inline = inline - self.container = container - self.fill = fill - self.fillable = fillable - self.kwargs = kwargs - - if fn is not None: - self(fn) + return _ui.output_ui(id) async def transform(self, value: TagChild) -> Jsonifiable: session = require_active_session(None) From 348d032c478759c19598f08ed2bc8083b0c65d78 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 17:46:15 -0600 Subject: [PATCH 14/25] Clean up imports --- shiny/express/_output.py | 1 - shiny/render/_render.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/shiny/express/_output.py b/shiny/express/_output.py index 76526e572..28dbbba7c 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -6,7 +6,6 @@ from typing import Callable, Generator, TypeVar, overload from .. import ui -from .._deprecated import warn_deprecated from .._typing_extensions import ParamSpec from ..render.renderer import RendererBase, RendererBaseT from ..render.transformer import OutputRenderer diff --git a/shiny/render/_render.py b/shiny/render/_render.py index af9b6b961..ea6879b4e 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -19,7 +19,7 @@ runtime_checkable, ) -from htmltools import Tag, TagAttrValue, TagChild, TagFunction +from htmltools import Tag, TagAttrValue, TagChild if TYPE_CHECKING: from ..session._utils import RenderedDeps @@ -32,7 +32,6 @@ from ..session import get_current_session, require_active_session from ..session._session import DownloadHandler, DownloadInfo from ..types import MISSING, MISSING_TYPE, ImgData -from ..ui._plot_output_opts import BrushOpts, ClickOpts, DblClickOpts, HoverOpts from ._try_render_plot import ( PlotSizeInfo, try_render_matplotlib, From 2a2941b99e0db92fb1bd4ce239aaa842a7dc446a Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 17:50:25 -0600 Subject: [PATCH 15/25] Restore argument passing --- shiny/render/renderer/_renderer.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/shiny/render/renderer/_renderer.py b/shiny/render/renderer/_renderer.py index 5a6cff00f..80a425c26 100644 --- a/shiny/render/renderer/_renderer.py +++ b/shiny/render/renderer/_renderer.py @@ -135,7 +135,12 @@ def _set_output_metadata( """ self.output_id = output_name - def auto_output_ui(self, id: str) -> DefaultUIFnResultOrNone: + def auto_output_ui( + self, + id: str, + # *args: object, + # **kwargs: object, + ) -> DefaultUIFnResultOrNone: return None @abstractmethod @@ -150,13 +155,13 @@ def __init__(self) -> None: # Tagify-like methods # ###### def _repr_html_(self) -> str | None: - rendered_ui = self.auto_output_ui(self.__name__) + rendered_ui = self._render_auto_output_ui() if rendered_ui is None: return None return TagList(rendered_ui)._repr_html_() def tagify(self) -> DefaultUIFnResult: - rendered_ui = self.auto_output_ui(self.__name__) + rendered_ui = self._render_auto_output_ui() if rendered_ui is None: raise TypeError( "No default UI exists for this type of render function: ", @@ -164,6 +169,13 @@ def tagify(self) -> DefaultUIFnResult: ) return rendered_ui + def _render_auto_output_ui(self) -> DefaultUIFnResultOrNone: + return self.auto_output_ui( + self.__name__, + # Pass the `@ui_kwargs(foo="bar")` kwargs through to the default_ui function. + **self._auto_output_ui_kwargs, + ) + # ###### # Auto registering output # ###### From 2dc3a5b06f476c5da4616f934298acd801779578 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 20:36:43 -0600 Subject: [PATCH 16/25] Remove unused imports --- tests/pytest/test_express_ui.py | 2 -- tests/pytest/test_plot_sizing.py | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/pytest/test_express_ui.py b/tests/pytest/test_express_ui.py index 510e1d54c..1d568c3f7 100644 --- a/tests/pytest/test_express_ui.py +++ b/tests/pytest/test_express_ui.py @@ -3,8 +3,6 @@ from pathlib import Path from typing import Any -import pytest - from shiny import render, ui from shiny.express import suspend_display from shiny.express._run import run_express diff --git a/tests/pytest/test_plot_sizing.py b/tests/pytest/test_plot_sizing.py index 0340922ac..737899748 100644 --- a/tests/pytest/test_plot_sizing.py +++ b/tests/pytest/test_plot_sizing.py @@ -1,5 +1,6 @@ from shiny import render, ui -from shiny.types import MISSING + +# from shiny.types import MISSING def test_decorator_plot_sizing(): From 6fccc372c8f219525db4ea46012f7abbf7ff75eb Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 22:59:03 -0600 Subject: [PATCH 17/25] Update output_args --- shiny/express/_output.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/shiny/express/_output.py b/shiny/express/_output.py index 28dbbba7c..480be72d8 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -8,8 +8,6 @@ from .. import ui from .._typing_extensions import ParamSpec from ..render.renderer import RendererBase, RendererBaseT -from ..render.transformer import OutputRenderer -from ..render.transformer._transformer import OT __all__ = ("suspend_display",) @@ -18,24 +16,22 @@ CallableT = TypeVar("CallableT", bound=Callable[..., object]) +# TODO-barret-future; quartodoc entry? def output_args( - *args: object, **kwargs: object, -) -> Callable[[OutputRenderer[OT]], OutputRenderer[OT]]: +) -> Callable[[RendererBaseT], RendererBaseT]: """ Sets default UI arguments for a Shiny rendering function. Each Shiny render function (like :func:`~shiny.render.plot`) can display itself when declared within a Shiny inline-style application. In the case of :func:`~shiny.render.plot`, the :func:`~shiny.ui.output_plot` function is called - implicitly to display the plot. Use the `@output_args` decorator to specify - arguments to be passed to `output_plot` (or whatever the corresponding UI function - is) when the render function displays itself. + implicitly to display the plot. Use the `@ui_kwargs` decorator to specify arguments + to be passed to `output_plot` (or whatever the corresponding UI function is) when + the render function displays itself. Parameters ---------- - *args - Positional arguments to be passed to the UI function. **kwargs Keyword arguments to be passed to the UI function. @@ -45,16 +41,9 @@ def output_args( A decorator that sets the default UI arguments for a Shiny rendering function. """ - def wrapper(renderer: OutputRenderer[OT]) -> OutputRenderer[OT]: - if not isinstance(renderer, OutputRenderer): - raise TypeError( - f"Expected an OutputRenderer, but got {type(renderer).__name__}." - "\nIf you are trying to set default UI arguments for a `Renderer`, use" - " `@ui_kwargs` instead." - ) - renderer._auto_output_ui_args = args + def wrapper(renderer: RendererBaseT) -> RendererBaseT: + # renderer._default_ui_args = args renderer._auto_output_ui_kwargs = kwargs - return renderer return wrapper From 2c8ddb106c8d021e15fe763933a2428b013c63ea Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Tue, 16 Jan 2024 23:17:21 -0600 Subject: [PATCH 18/25] Fix typing --- shiny/render/_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shiny/render/_render.py b/shiny/render/_render.py index ea6879b4e..436f3d6ad 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -103,7 +103,7 @@ def auto_output_ui( def __init__( self, - _fn: Optional[ValueFn[str]] = None, + _fn: Optional[ValueFn[str | None]] = None, *, inline: bool = False, ) -> None: @@ -163,7 +163,7 @@ def auto_output_ui( def __init__( self, - _fn: Optional[ValueFn[str]] = None, + _fn: Optional[ValueFn[str | None]] = None, *, placeholder: bool = True, ) -> None: From edcb394ae7a4f1fa65b9bef07aa60d81f12149b8 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 17 Jan 2024 11:02:37 -0600 Subject: [PATCH 19/25] Code review fixes --- shiny/render/_render.py | 38 ++++++++-------------- tests/pytest/test_express_ui.py | 24 +++++++++----- tests/pytest/test_plot_sizing.py | 54 ++++++++++++++++---------------- 3 files changed, 57 insertions(+), 59 deletions(-) diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 436f3d6ad..aba541d94 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -87,7 +87,8 @@ class text(Renderer[str]): See Also -------- - ~shiny.render.code ~shiny.ui.output_text + * ~shiny.render.code + * ~shiny.ui.output_text """ def auto_output_ui( @@ -108,7 +109,7 @@ def __init__( inline: bool = False, ) -> None: super().__init__(_fn) - self.inline = inline + self.inline: bool = inline async def transform(self, value: str) -> Jsonifiable: return str(value) @@ -148,7 +149,8 @@ class code(Renderer[str]): See Also -------- - ~shiny.render.code ~shiny.ui.output_code + * ~shiny.render.code + * ~shiny.ui.output_code """ def auto_output_ui( @@ -235,7 +237,8 @@ class plot(Renderer[object]): See Also -------- - ~shiny.ui.output_plot ~shiny.render.image + * ~shiny.ui.output_plot + * ~shiny.render.image """ def auto_output_ui( @@ -405,9 +408,9 @@ class image(Renderer[ImgData]): See Also -------- - ~shiny.ui.output_image - ~shiny.types.ImgData - ~shiny.render.plot + * ~shiny.ui.output_image + * ~shiny.types.ImgData + * ~shiny.render.plot """ def default_ui(self, id: str, **kwargs: object): @@ -500,7 +503,7 @@ class table(Renderer[TableResult]): See Also -------- - ~shiny.ui.output_table for the corresponding UI component to this render function. + * ~shiny.ui.output_table for the corresponding UI component to this render function. """ def auto_output_ui(self, id: str, **kwargs: TagAttrValue) -> Tag: @@ -579,7 +582,7 @@ class ui(Renderer[TagChild]): See Also -------- - ~shiny.ui.output_ui + * ~shiny.ui.output_ui """ def auto_output_ui(self, id: str) -> Tag: @@ -609,12 +612,6 @@ class download(Renderer[str]): The encoding of the download. label (Express only) A label for the button. Defaults to "Download". - icon - (Express only) An icon to display on the button. - width - (Express only) The width of the button. - **kwargs - (Express only) Additional attributes for the button. Returns ------- @@ -623,16 +620,13 @@ class download(Renderer[str]): See Also -------- - ~shiny.ui.download_button + * ~shiny.ui.download_button """ def auto_output_ui(self, id: str) -> Tag: return _ui.download_button( id, label=self.label, - icon=self.icon, - width=self.width, - **self.kwargs, ) def __init__( @@ -643,9 +637,6 @@ def __init__( media_type: None | str | Callable[[], str] = None, encoding: str = "utf-8", label: TagChild = "Download", - icon: TagChild = None, - width: Optional[str] = None, - **kwargs: TagAttrValue, ) -> None: super().__init__() @@ -653,9 +644,6 @@ def __init__( self.media_type = media_type self.encoding = encoding self.label = label - self.icon = icon - self.width = width - self.kwargs = kwargs if fn is not None: self(fn) diff --git a/tests/pytest/test_express_ui.py b/tests/pytest/test_express_ui.py index 1d568c3f7..15fc8a575 100644 --- a/tests/pytest/test_express_ui.py +++ b/tests/pytest/test_express_ui.py @@ -3,8 +3,10 @@ from pathlib import Path from typing import Any +import pytest + from shiny import render, ui -from shiny.express import suspend_display +from shiny.express import output_args, suspend_display from shiny.express._run import run_express @@ -58,14 +60,22 @@ def text2(): assert ui.TagList(text2.tagify()).get_html_string() == "" - @render.code(placeholder=True) - def code1(): + # @render.code(placeholder=True) + # def code1(): + # return "text" + + # assert ( + # ui.TagList(code1.tagify()).get_html_string() + # == ui.output_code("code1", placeholder=True).get_html_string() + # ) + + @output_args(width=100) + @render.code + def code2(): return "text" - assert ( - ui.TagList(code1.tagify()).get_html_string() - == ui.output_code("code1", placeholder=True).get_html_string() - ) + with pytest.raises(TypeError, match="width"): + code2.tagify() def test_suspend_display(): diff --git a/tests/pytest/test_plot_sizing.py b/tests/pytest/test_plot_sizing.py index 737899748..4390f85f2 100644 --- a/tests/pytest/test_plot_sizing.py +++ b/tests/pytest/test_plot_sizing.py @@ -1,6 +1,6 @@ from shiny import render, ui - -# from shiny.types import MISSING +from shiny.express import output_args +from shiny.types import MISSING def test_decorator_plot_sizing(): @@ -27,38 +27,38 @@ def foo(): assert rendered == str(ui.output_plot("foo")) -# def test_decorator_ui_kwargs(): -# """@ui_kwargs is respected""" +def test_decorator_output_args(): + """@output_args is respected""" -# @ui_kwargs(width="640px", height="480px") -# @render.plot() -# def foo(): -# ... + @output_args(width="640px", height="480px") + @render.plot() + def foo(): + ... -# rendered = str(foo.tagify()) -# assert rendered == str(ui.output_plot("foo", width="640px", height="480px")) + rendered = str(foo.tagify()) + assert rendered == str(ui.output_plot("foo", width="640px", height="480px")) -# def test_decorator_ui_kwargs_priority(): -# """@ui_kwargs should override render.plot width/height""" +def test_decorator_output_args_priority(): + """@output_args should override render.plot width/height""" -# @ui_kwargs(width="640px", height=480) -# @render.plot(width=1280, height=960) -# def foo(): -# ... + @output_args(width="640px", height=480) + @render.plot(width=1280, height=960) + def foo(): + ... -# rendered = str(foo.tagify()) -# # Note "640px" => 640 and 480 => "480px" -# assert rendered == str(ui.output_plot("foo", width=640, height="480px")) + rendered = str(foo.tagify()) + # Note "640px" => 640 and 480 => "480px" + assert rendered == str(ui.output_plot("foo", width=640, height="480px")) -# def test_decorator_ui_kwargs_MISSING(): -# """Not saying we support this, but test how MISSING interacts""" +def test_decorator_output_args_MISSING(): + """Not saying we support this, but test how MISSING interacts""" -# @ui_kwargs(width=MISSING) -# @render.plot(width=1280, height=MISSING) -# def foo(): -# ... + @output_args(width=MISSING) + @render.plot(width=1280, height=MISSING) + def foo(): + ... -# rendered = str(foo.tagify()) -# assert rendered == str(ui.output_plot("foo", width="1280px")) + rendered = str(foo.tagify()) + assert rendered == str(ui.output_plot("foo", width="1280px")) From 0a318e59e0efc6b69308ad09cdfbae7fcb0e09f1 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 17 Jan 2024 11:10:38 -0600 Subject: [PATCH 20/25] Update tests to use render.code --- tests/playwright/shiny/shiny-express/page_fillable/app.py | 2 +- tests/playwright/shiny/shiny-express/page_fluid/app.py | 2 +- tests/playwright/shiny/shiny-express/page_sidebar/app.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/playwright/shiny/shiny-express/page_fillable/app.py b/tests/playwright/shiny/shiny-express/page_fillable/app.py index 94e8b2a99..573e65fdb 100644 --- a/tests/playwright/shiny/shiny-express/page_fillable/app.py +++ b/tests/playwright/shiny/shiny-express/page_fillable/app.py @@ -6,6 +6,6 @@ with ui.card(id="card"): ui.input_slider("a", "A", 1, 100, 50) - @render.text + @render.code def txt(): return input.a() diff --git a/tests/playwright/shiny/shiny-express/page_fluid/app.py b/tests/playwright/shiny/shiny-express/page_fluid/app.py index 54fd50c64..1f5c0dae4 100644 --- a/tests/playwright/shiny/shiny-express/page_fluid/app.py +++ b/tests/playwright/shiny/shiny-express/page_fluid/app.py @@ -6,6 +6,6 @@ with ui.card(id="card"): ui.input_slider("a", "A", 1, 100, 50) - @render.text + @render.code def txt(): return input.a() diff --git a/tests/playwright/shiny/shiny-express/page_sidebar/app.py b/tests/playwright/shiny/shiny-express/page_sidebar/app.py index 8168abf73..455326083 100644 --- a/tests/playwright/shiny/shiny-express/page_sidebar/app.py +++ b/tests/playwright/shiny/shiny-express/page_sidebar/app.py @@ -9,6 +9,6 @@ with ui.card(id="card"): ui.input_slider("a", "A", 1, 100, 50) - @render.text + @render.code def txt(): return input.a() From 3b9f13303faece2864e63ad62e5376a204d8bc5d Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 17 Jan 2024 11:14:11 -0600 Subject: [PATCH 21/25] Update more tests --- tests/playwright/controls.py | 1 + tests/playwright/shiny/shiny-express/suspend_display/app.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/playwright/controls.py b/tests/playwright/controls.py index c27becd07..8fdcc7d76 100644 --- a/tests/playwright/controls.py +++ b/tests/playwright/controls.py @@ -2174,6 +2174,7 @@ def __init__( super().__init__(page, id=id, loc=f"#{id}.shiny-text-output") +# TODO-Karan: Add OutputCode class class OutputTextVerbatim(_OutputTextValue): def __init__(self, page: Page, id: str) -> None: super().__init__(page, id=id, loc=f"pre#{id}.shiny-text-output") diff --git a/tests/playwright/shiny/shiny-express/suspend_display/app.py b/tests/playwright/shiny/shiny-express/suspend_display/app.py index 14baf6df7..4f4b510ef 100644 --- a/tests/playwright/shiny/shiny-express/suspend_display/app.py +++ b/tests/playwright/shiny/shiny-express/suspend_display/app.py @@ -5,7 +5,7 @@ ui.input_slider("s1", "A", 1, 100, 20) @suspend_display - @render.text + @render.code def hidden(): return input.s1() @@ -14,7 +14,7 @@ def hidden(): # from shiny.express import ui_kwargs # @ui_kwargs(placeholder=False) # @ui_kwargs(placeholder=True) - @render.text() + @render.code() def visible(): # from shiny import req From 93b5e5e6e9e3e40ae68ab614c2f836cfa24de9e0 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 17 Jan 2024 11:22:31 -0600 Subject: [PATCH 22/25] Fix argument names --- shiny/express/_output.py | 1 - shiny/render/_render.py | 2 +- shiny/render/renderer/_renderer.py | 2 +- shiny/render/transformer/_transformer.py | 28 ++++++++++++------------ 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/shiny/express/_output.py b/shiny/express/_output.py index 480be72d8..ef614efd7 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -42,7 +42,6 @@ def output_args( """ def wrapper(renderer: RendererBaseT) -> RendererBaseT: - # renderer._default_ui_args = args renderer._auto_output_ui_kwargs = kwargs return renderer diff --git a/shiny/render/_render.py b/shiny/render/_render.py index aba541d94..426e319f2 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -413,7 +413,7 @@ class image(Renderer[ImgData]): * ~shiny.render.plot """ - def default_ui(self, id: str, **kwargs: object): + def auto_output_ui(self, id: str, **kwargs: object): return _ui.output_image( id, **kwargs, # pyright: ignore[reportGeneralTypeIssues] diff --git a/shiny/render/renderer/_renderer.py b/shiny/render/renderer/_renderer.py index 80a425c26..0c0237fe7 100644 --- a/shiny/render/renderer/_renderer.py +++ b/shiny/render/renderer/_renderer.py @@ -172,7 +172,7 @@ def tagify(self) -> DefaultUIFnResult: def _render_auto_output_ui(self) -> DefaultUIFnResultOrNone: return self.auto_output_ui( self.__name__, - # Pass the `@ui_kwargs(foo="bar")` kwargs through to the default_ui function. + # Pass the `@output_args(foo="bar")` kwargs through to the auto_output_ui function. **self._auto_output_ui_kwargs, ) diff --git a/shiny/render/transformer/_transformer.py b/shiny/render/transformer/_transformer.py index 7a118bf56..114c88269 100644 --- a/shiny/render/transformer/_transformer.py +++ b/shiny/render/transformer/_transformer.py @@ -220,8 +220,8 @@ def __init__( value_fn: ValueFn[IT], transform_fn: TransformFn[IT, P, OT], params: TransformerParams[P], - auto_output_ui: Optional[DefaultUIFn] = None, - auto_output_ui_passthrough_args: Optional[tuple[str, ...]] = None, + default_ui: Optional[DefaultUIFn] = None, + default_ui_passthrough_args: Optional[tuple[str, ...]] = None, ) -> None: """ Parameters @@ -233,7 +233,7 @@ def __init__( `OT`. The `params` will used as variadic keyword arguments. params App-provided parameters for the transform function (`transform_fn`). - auto_output_ui + default_ui Optional function that takes an `output_id` string and returns a Shiny UI object that can be used to display the output. This allows render functions to respond to `_repr_html_` method calls in environments like Jupyter. @@ -266,8 +266,8 @@ def __init__( self._transformer = transform_fn self._params = params - self._auto_output_ui = auto_output_ui - self._auto_output_ui_passthrough_args = auto_output_ui_passthrough_args + self._auto_output_ui = default_ui + self._auto_output_ui_passthrough_args = default_ui_passthrough_args self._auto_output_ui_args: tuple[object, ...] = tuple() self._auto_output_ui_kwargs: dict[str, object] = dict() @@ -507,8 +507,8 @@ def __init__( @overload def output_transformer( *, - auto_output_ui: Optional[DefaultUIFn] = None, - auto_output_ui_passthrough_args: Optional[tuple[str, ...]] = None, + default_ui: Optional[DefaultUIFn] = None, + default_ui_passthrough_args: Optional[tuple[str, ...]] = None, ) -> Callable[[TransformFn[IT, P, OT]], OutputTransformer[IT, OT, P]]: ... @@ -524,8 +524,8 @@ def output_transformer( def output_transformer( transform_fn: TransformFn[IT, P, OT] | None = None, *, - auto_output_ui: Optional[DefaultUIFn] = None, - auto_output_ui_passthrough_args: Optional[tuple[str, ...]] = None, + default_ui: Optional[DefaultUIFn] = None, + default_ui_passthrough_args: Optional[tuple[str, ...]] = None, ) -> ( OutputTransformer[IT, OT, P] | Callable[[TransformFn[IT, P, OT]], OutputTransformer[IT, OT, P]] @@ -588,7 +588,7 @@ def output_transformer( Asynchronous function used to determine the app-supplied output value function return type (`IT`), the transformed type (`OT`), and the keyword arguments (`P`) app authors can supply to the renderer decorator. - auto_output_ui + default_ui Optional function that takes an `output_id` string and returns a Shiny UI object that can be used to display the output. This allows render functions to respond to `_repr_html_` method calls in environments like Jupyter. @@ -602,8 +602,8 @@ def output_transformer( called with parentheses. """ - # If auto_output_ui_passthrough_args was used, modify the auto_output_ui function so - # it is ready to mix in extra arguments from the decorator. + # If default_ui_passthrough_args was used, modify the default_ui function so it is + # ready to mix in extra arguments from the decorator. def output_transformer_impl( transform_fn: TransformFn[IT, P, OT], ) -> OutputTransformer[IT, OT, P]: @@ -620,8 +620,8 @@ def as_value_fn( value_fn=fn, transform_fn=transform_fn, params=params, - auto_output_ui=auto_output_ui, - auto_output_ui_passthrough_args=auto_output_ui_passthrough_args, + default_ui=default_ui, + default_ui_passthrough_args=default_ui_passthrough_args, ) if value_fn is None: From b4bbcf34cfccc6e8efa7e47819f3beabd2a52641 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 17 Jan 2024 11:28:31 -0600 Subject: [PATCH 23/25] Bump version to 0.6.1.9004 --- shiny/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/__init__.py b/shiny/__init__.py index 513122382..ae11bd126 100644 --- a/shiny/__init__.py +++ b/shiny/__init__.py @@ -1,6 +1,6 @@ """A package for building reactive web applications.""" -__version__ = "0.6.1.9003" +__version__ = "0.6.1.9004" from ._shinyenv import is_pyodide as _is_pyodide From 645b23407d96f51b4457f942cf64f219900c6f13 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 17 Jan 2024 11:30:29 -0600 Subject: [PATCH 24/25] Restore test --- tests/pytest/test_express_ui.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/pytest/test_express_ui.py b/tests/pytest/test_express_ui.py index 15fc8a575..0c1afcb43 100644 --- a/tests/pytest/test_express_ui.py +++ b/tests/pytest/test_express_ui.py @@ -60,14 +60,15 @@ def text2(): assert ui.TagList(text2.tagify()).get_html_string() == "" - # @render.code(placeholder=True) - # def code1(): - # return "text" - - # assert ( - # ui.TagList(code1.tagify()).get_html_string() - # == ui.output_code("code1", placeholder=True).get_html_string() - # ) + @output_args(placeholder=False) + @render.code + def code1(): + return "text" + + assert ( + ui.TagList(code1.tagify()).get_html_string() + == ui.output_code("code1", placeholder=False).get_html_string() + ) @output_args(width=100) @render.code From 3e43cba5f393a3226c04b38767b8b002d995c0a2 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 17 Jan 2024 11:43:32 -0600 Subject: [PATCH 25/25] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f0fafb4..60e39dc4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `@render.download` as a replacement for `@session.download`, which is now deprecated. (#977) +* Added `ui.output_code()`, which is currently an alias for `ui.output_text_verbatim()`. (#997) + +* Added `@render.code`, which is an alias for `@render.text`, but in Express mode, it displays the result using `ui.output_code()`. (#997) + ### Bug fixes * CLI command `shiny create`... (#965)