Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions shiny/render/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
table,
text,
ui,
download,
)

__all__ = (
Expand All @@ -35,6 +36,7 @@
"image",
"table",
"ui",
"download",
"DataGrid",
"DataTable",
)
100 changes: 99 additions & 1 deletion shiny/render/_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,7 +27,7 @@
runtime_checkable,
)

from htmltools import TagChild
from htmltools import Tag, TagChild

if TYPE_CHECKING:
from ..session._utils import RenderedDeps
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button_label gets passed here, but it currently isn't used, and doesn't end up in the UI. It would need to somehow go to the call to download_button_wrapped().

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should work great in #964

Comment on lines +574 to +585
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would work today with:

Suggested change
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,
@output_transformer(
default_ui=ui.download_button,
default_ui_passthrough_args=["label"],
) # pyright: ignore[reportGeneralTypeIssues]
async def DownloadTransformer(
_meta: TransformerMetadata,
_fn: DownloadHandler,
*,
label: Optional[TagChild] = "Download",

Definitely will be easier with Barret's new class-based approach.

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,
),
)