From e720a0303685ebad9e7b3f70e1d3d8c3bc45736e Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 3 Jan 2024 10:25:49 -0600 Subject: [PATCH 1/2] Express' value_box() no longer includes named positional args (partially addresses #947) --- shiny/express/ui/_cm_components.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/shiny/express/ui/_cm_components.py b/shiny/express/ui/_cm_components.py index de4e251d2..15a1fef39 100644 --- a/shiny/express/ui/_cm_components.py +++ b/shiny/express/ui/_cm_components.py @@ -1115,8 +1115,6 @@ def nav_menu( # Value boxes # ====================================================================================== def value_box( - title: TagChild, - value: TagChild, *, showcase: Optional[TagChild] = None, showcase_layout: ui._valuebox.SHOWCASE_LAYOUTS_STR @@ -1134,16 +1132,14 @@ def value_box( This function wraps :func:`~shiny.ui.value_box`. - An opinionated (:func:`~shiny.ui.card`-powered) box, designed for - displaying a `value` and `title`. Optionally, a `showcase` can provide for context - for what the `value` represents (for example, it could hold an icon, or even a + An opinionated (:func:`~shiny.ui.card`-powered) box, designed for displaying a title + (the 1st child), value (the 2nd child), and other explanation text (other children, + if any). Optionally, a `showcase` can provide for context for what the `value` + represents (for example, it could hold an icon, or even a :func:`~shiny.ui.output_plot`). Parameters ---------- - title,value - A string, number, or :class:`~htmltools.Tag` child to display as - the title or value of the value box. The `title` appears above the `value`. showcase A :class:`~htmltools.Tag` child to showcase (e.g., an icon, a :func:`~shiny.ui.output_plot`, etc). @@ -1184,7 +1180,6 @@ def value_box( """ return RecallContextManager( ui.value_box, - args=(title, value), kwargs=dict( showcase=showcase, showcase_layout=showcase_layout, From 5cda25233d591a2f326b216baa3cb641af718e6b Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 3 Jan 2024 18:42:03 -0600 Subject: [PATCH 2/2] Add render.download --- shiny/express/ui/_cm_components.py | 13 ++-- shiny/render/__init__.py | 2 + shiny/render/_render.py | 100 ++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/shiny/express/ui/_cm_components.py b/shiny/express/ui/_cm_components.py index 15a1fef39..de4e251d2 100644 --- a/shiny/express/ui/_cm_components.py +++ b/shiny/express/ui/_cm_components.py @@ -1115,6 +1115,8 @@ def nav_menu( # Value boxes # ====================================================================================== def value_box( + title: TagChild, + value: TagChild, *, showcase: Optional[TagChild] = None, showcase_layout: ui._valuebox.SHOWCASE_LAYOUTS_STR @@ -1132,14 +1134,16 @@ def value_box( This function wraps :func:`~shiny.ui.value_box`. - An opinionated (:func:`~shiny.ui.card`-powered) box, designed for displaying a title - (the 1st child), value (the 2nd child), and other explanation text (other children, - if any). Optionally, a `showcase` can provide for context for what the `value` - represents (for example, it could hold an icon, or even a + An opinionated (:func:`~shiny.ui.card`-powered) box, designed for + displaying a `value` and `title`. Optionally, a `showcase` can provide for context + for what the `value` represents (for example, it could hold an icon, or even a :func:`~shiny.ui.output_plot`). Parameters ---------- + title,value + A string, number, or :class:`~htmltools.Tag` child to display as + the title or value of the value box. The `title` appears above the `value`. showcase A :class:`~htmltools.Tag` child to showcase (e.g., an icon, a :func:`~shiny.ui.output_plot`, etc). @@ -1180,6 +1184,7 @@ def value_box( """ return RecallContextManager( ui.value_box, + args=(title, value), kwargs=dict( showcase=showcase, showcase_layout=showcase_layout, diff --git a/shiny/render/__init__.py b/shiny/render/__init__.py index 8e9d32551..0da6cb2fe 100644 --- a/shiny/render/__init__.py +++ b/shiny/render/__init__.py @@ -24,6 +24,7 @@ table, text, ui, + download, ) __all__ = ( @@ -35,6 +36,7 @@ "image", "table", "ui", + "download", "DataGrid", "DataTable", ) diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 9c0d986e0..76296b695 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -6,15 +6,18 @@ "image", "table", "ui", + "download", ) import base64 import os import sys import typing +import urllib.parse from typing import ( TYPE_CHECKING, Any, + Callable, Literal, Optional, Protocol, @@ -24,7 +27,7 @@ runtime_checkable, ) -from htmltools import TagChild +from htmltools import Tag, TagChild if TYPE_CHECKING: from ..session._utils import RenderedDeps @@ -33,6 +36,7 @@ from .. import _utils from .. import ui as _ui from .._namespaces import ResolvedId +from ..session._session import DownloadHandler, DownloadInfo from ..types import MISSING, MISSING_TYPE, ImgData from ._try_render_plot import ( PlotSizeInfo, @@ -560,3 +564,97 @@ def ui( ~shiny.ui.output_ui """ return UiTransformer(_fn) + + +# ====================================================================================== +# RenderDownload +# ====================================================================================== + + +def download_button_wrapped(id: str) -> Tag: + return _ui.download_button(id, label=id) + + +@output_transformer( + default_ui=download_button_wrapped +) # pyright: ignore[reportGeneralTypeIssues] +async def DownloadTransformer( + _meta: TransformerMetadata, + _fn: DownloadHandler, + *, + button_label: Optional[TagChild] = None, + filename: Optional[str | Callable[[], str]] = None, + media_type: None | str | Callable[[], str] = None, + encoding: str = "utf-8", +) -> str | None: + session = _meta.session + + effective_name = _meta.name + + session._downloads[effective_name] = DownloadInfo( + filename=filename, + content_type=media_type, + handler=_fn, + encoding=encoding, + ) + + return f"session/{urllib.parse.quote(session.id)}/download/{urllib.parse.quote(effective_name)}?w=" + + +@overload +def download( + *, + filename: Optional[str | Callable[[], str]] = None, + media_type: None | str | Callable[[], str] = None, + encoding: str = "utf-8", +) -> DownloadTransformer.OutputRendererDecorator: + ... + + +@overload +def download( + _fn: DownloadTransformer.ValueFn, + *, + filename: Optional[str | Callable[[], str]] = None, + media_type: None | str | Callable[[], str] = None, + encoding: str = "utf-8", +) -> DownloadTransformer.OutputRenderer: + ... + + +def download( + _fn: DownloadTransformer.ValueFn | None = None, + *, + button_label: Optional[TagChild] = None, + filename: Optional[str | Callable[[], str]] = None, + media_type: None | str | Callable[[], str] = None, + encoding: str = "utf-8", +) -> DownloadTransformer.OutputRenderer | DownloadTransformer.OutputRendererDecorator: + """ + Decorator to register a function to handle a download. + + Parameters + ---------- + id + The name of the download. + filename + The filename of the download. + media_type + The media type of the download. + encoding + The encoding of the download. + + Returns + ------- + : + The decorated function. + """ + return DownloadTransformer( + _fn, + DownloadTransformer.params( + button_label=button_label, + filename=filename, + media_type=media_type, + encoding=encoding, + ), + )