Skip to content

api!: Renderer.auto_output_ui() drops id arg. Make RendererBase.output_id a non-namespaced value. #1030

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 18, 2024
Merged
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: 1 addition & 1 deletion shiny/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""A package for building reactive web applications."""

__version__ = "0.6.1.9004"
__version__ = "0.6.1.9005"

from ._shinyenv import is_pyodide as _is_pyodide

Expand Down
8 changes: 4 additions & 4 deletions shiny/api-examples/Renderer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ class render_capitalize(Renderer[str]):
Whether to render a placeholder value. (Defaults to `True`)
"""

def auto_output_ui(self, id: str):
def auto_output_ui(self):
"""
Express UI for the renderer
"""
return ui.output_text_verbatim(id, placeholder=self.placeholder)
return ui.output_text_verbatim(self.output_name, placeholder=self.placeholder)

def __init__(
self,
Expand Down Expand Up @@ -94,11 +94,11 @@ class render_upper(Renderer[str]):
Note: This renderer is equivalent to `render_capitalize(to="upper")`.
"""

def auto_output_ui(self, id: str):
def auto_output_ui(self):
"""
Express UI for the renderer
"""
return ui.output_text_verbatim(id, placeholder=True)
return ui.output_text_verbatim(self.output_name, placeholder=True)

async def transform(self, value: str) -> str:
"""
Expand Down
2 changes: 0 additions & 2 deletions shiny/express/_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ def suspend_display_ctxmgr() -> Generator[None, None, None]:


def null_ui(
id: str,
*args: object,
**kwargs: object,
) -> ui.TagList:
return ui.TagList()
Expand Down
4 changes: 2 additions & 2 deletions shiny/render/_dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ class data_frame(Renderer[DataFrameResult]):
objects you can return from the rendering function to specify options.
"""

def auto_output_ui(self, id: str) -> Tag:
return ui.output_data_frame(id=id)
def auto_output_ui(self) -> Tag:
return ui.output_data_frame(id=self.output_id)

async def transform(self, value: DataFrameResult) -> Jsonifiable:
if not isinstance(value, AbstractTabularData):
Expand Down
3 changes: 1 addition & 2 deletions shiny/render/_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
class display(Renderer[None]):
def auto_output_ui(
self,
id: str,
*,
inline: bool | MISSING_TYPE = MISSING,
container: TagFunction | MISSING_TYPE = MISSING,
Expand All @@ -35,7 +34,7 @@ def auto_output_ui(
set_kwargs_value(kwargs, "fillable", fillable, self.fillable)

return _ui.output_ui(
id,
self.output_id,
# (possibly) contains `inline`, `container`, `fill`, and `fillable` keys!
**kwargs, # pyright: ignore[reportGeneralTypeIssues]
)
Expand Down
25 changes: 11 additions & 14 deletions shiny/render/_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,13 @@ class text(Renderer[str]):

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)
return _ui.output_text(self.output_id, **kwargs)

def __init__(
self,
Expand Down Expand Up @@ -155,13 +154,12 @@ class code(Renderer[str]):

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)
return _ui.output_code(self.output_id, **kwargs)

def __init__(
self,
Expand Down Expand Up @@ -243,7 +241,6 @@ class plot(Renderer[object]):

def auto_output_ui(
self,
id: str,
*,
width: str | float | int | MISSING_TYPE = MISSING,
height: str | float | int | MISSING_TYPE = MISSING,
Expand All @@ -253,7 +250,7 @@ def auto_output_ui(
set_kwargs_value(kwargs, "width", width, self.width)
set_kwargs_value(kwargs, "height", height, self.height)
return _ui.output_plot(
id,
self.output_id,
# (possibly) contains `width` and `height` keys!
**kwargs, # pyright: ignore[reportGeneralTypeIssues]
)
Expand Down Expand Up @@ -413,9 +410,9 @@ class image(Renderer[ImgData]):
* ~shiny.render.plot
"""

def auto_output_ui(self, id: str, **kwargs: object):
def auto_output_ui(self, **kwargs: object):
return _ui.output_image(
id,
self.output_id,
**kwargs, # pyright: ignore[reportGeneralTypeIssues]
)
# TODO: Make width/height handling consistent with render_plot
Expand Down Expand Up @@ -506,8 +503,8 @@ class table(Renderer[TableResult]):
* ~shiny.ui.output_table for the corresponding UI component to this render function.
"""

def auto_output_ui(self, id: str, **kwargs: TagAttrValue) -> Tag:
return _ui.output_table(id, **kwargs)
def auto_output_ui(self, **kwargs: TagAttrValue) -> Tag:
return _ui.output_table(self.output_id, **kwargs)
# TODO: Deal with kwargs

def __init__(
Expand Down Expand Up @@ -585,8 +582,8 @@ class ui(Renderer[TagChild]):
* ~shiny.ui.output_ui
"""

def auto_output_ui(self, id: str) -> Tag:
return _ui.output_ui(id)
def auto_output_ui(self) -> Tag:
return _ui.output_ui(self.output_id)

async def transform(self, value: TagChild) -> Jsonifiable:
session = require_active_session(None)
Expand Down Expand Up @@ -623,9 +620,9 @@ class download(Renderer[str]):
* ~shiny.ui.download_button
"""

def auto_output_ui(self, id: str) -> Tag:
def auto_output_ui(self) -> Tag:
return _ui.download_button(
id,
self.output_id,
label=self.label,
)

Expand Down
45 changes: 26 additions & 19 deletions shiny/render/renderer/_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"Jsonifiable",
"ValueFn",
"AsyncValueFn",
"RendererBaseT",
)

RendererBaseT = TypeVar("RendererBaseT", bound="RendererBase")
Expand Down Expand Up @@ -106,42 +107,46 @@ class RendererBase(ABC):
# 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
_auto_output_ui_kwargs: dict[str, Any] = dict()
# _auto_output_ui_args: tuple[Any, ...] = tuple()

__name__: str
"""
Name of output function supplied. (The value will not contain any module prefix.)

Set within `.__call__()` method.
Set within `Renderer.__call__()` method.
"""

# Meta
output_id: str
"""
Output function name or ID (provided to `@output(id=)`). This value will contain any module prefix.
Output function name or ID (provided to `@output(id=)`).

Set when the output is registered with the session.
This value **will not** contain a module prefix (or session name-spacing). To get
the fully resolved ID, call
`shiny.session.require_active_session(None).ns(self.output_id)`.

An initial value of `.__name__` (set within `Renderer.__call__(_fn)`) will be used until the
output renderer is registered within the session.
"""

def _set_output_metadata(
self,
*,
output_name: str,
output_id: str,
) -> None:
"""
Method to be called within `@output` to set the renderer's metadata.

Parameters
----------
output_name : str
Output function name or ID (provided to `@output(id=)`). This value will contain any module prefix.
output_id : str
Output function name or ID (provided to `@output(id=)`). This value **will
not** contain a module prefix (or session name-spacing).
"""
self.output_id = output_name
self.output_id = output_id

def auto_output_ui(
self,
id: str,
# *args: object,
# *
# **kwargs: object,
) -> DefaultUIFnResultOrNone:
return None
Expand Down Expand Up @@ -174,7 +179,6 @@ def tagify(self) -> DefaultUIFnResult:

def _render_auto_output_ui(self) -> DefaultUIFnResultOrNone:
return self.auto_output_ui(
self.__name__,
# Pass the `@output_args(foo="bar")` kwargs through to the auto_output_ui function.
**self._auto_output_ui_kwargs,
)
Expand Down Expand Up @@ -206,11 +210,8 @@ def _auto_register(self) -> None:

s = get_current_session()
if s is not None:
from ._renderer import RendererBase

# Cast to avoid circular import as this mixin is ONLY used within RendererBase
renderer_self = cast(RendererBase, self)
s.output(renderer_self)
# Register output on reactive graph
s.output(self)
# We mark the fact that we're auto-registered so that, if an explicit
# registration now occurs, we can undo this auto-registration.
self._auto_registered = True
Expand Down Expand Up @@ -309,16 +310,22 @@ def __call__(self, _fn: ValueFn[IT]) -> Self:
if not callable(_fn):
raise TypeError("Value function must be callable")

# Set value function with extra meta information
self.fn = AsyncValueFn(_fn)

# Copy over function name as it is consistent with how Session and Output
# retrieve function names
self.__name__: str = _fn.__name__
self.__name__ = _fn.__name__

# Set value function with extra meta information
self.fn: AsyncValueFn[IT] = AsyncValueFn(_fn)
# Set the value of `output_id` to the function name.
# This helps with testing and other situations where no session is present
# for auto-registration to occur.
self.output_id = self.__name__

# Allow for App authors to not require `@output`
self._auto_register()

# Return self for possible chaining of methods!
return self

def __init__(
Expand Down
11 changes: 5 additions & 6 deletions shiny/render/transformer/_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,6 @@ def __init__(
"`shiny.render.transformer.output_transformer()` and `shiny.render.transformer.OutputRenderer()` output render function utilities have been superseded by `shiny.render.renderer.Renderer` and will be removed in the near future."
)

# Copy over function name as it is consistent with how Session and Output
# retrieve function names
self.__name__ = value_fn.__name__

if not is_async_callable(transform_fn):
raise TypeError(
self.__class__.__name__
Expand All @@ -278,7 +274,11 @@ def __init__(

self._value_fn = value_fn
self._value_fn_is_async = is_async_callable(value_fn) # legacy key
# Copy over function name as it is consistent with how Session and Output
# retrieve function names
self.__name__ = value_fn.__name__
# Initial value for output_id until it is set by the Session
self.output_id = value_fn.__name__

self._transformer = transform_fn
self._params = params
Expand Down Expand Up @@ -333,7 +333,6 @@ async def _run(self) -> OT:

def auto_output_ui(
self,
id: str,
**kwargs: object,
) -> DefaultUIFnResultOrNone:
if self._default_ui is None:
Expand All @@ -348,7 +347,7 @@ def auto_output_ui(
}
)

return self._default_ui(id, *self._default_ui_args, **kwargs)
return self._default_ui(self.output_id, *self._default_ui_args, **kwargs)

async def render(self) -> Jsonifiable:
ret = await self._run()
Expand Down
5 changes: 3 additions & 2 deletions shiny/session/_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,10 +1002,11 @@ def set_renderer(renderer: RendererBaseT) -> RendererBaseT:
)

# Get the (possibly namespaced) output id
output_name = self._ns(id or renderer.__name__)
output_id = id or renderer.__name__
output_name = self._ns(output_id)

# renderer is a Renderer object. Give it a bit of metadata.
renderer._set_output_metadata(output_name=output_name)
renderer._set_output_metadata(output_id=output_name)

renderer._on_register()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class render_custom_component(Renderer[int]):
"""

# The UI used within Shiny Express mode
def auto_output_ui(self, id: str) -> Tag:
return custom_component(id, height=self.height)
def auto_output_ui(self) -> Tag:
return custom_component(self.output_id, height=self.height)

# The init method is used to set up the renderer's parameters.
# If no parameters are needed, then the `__init__()` method can be omitted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class render_custom_component(Renderer[str]):
"""

# The UI used within Shiny Express mode
def auto_output_ui(self, id: str) -> Tag:
return output_custom_component(id)
def auto_output_ui(self) -> Tag:
return output_custom_component(self.output_id)

# # There are no parameters being supplied to the `output_custom_component` rendering function.
# # Therefore, we can omit the `__init__()` method.
Expand Down
18 changes: 6 additions & 12 deletions tests/pytest/test_output_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,12 @@ def async_renderer(

test_val = "Test: Hello World!"

def app_render_fn() -> str:
return test_val

# ## Test Sync: X =============================================

renderer_sync = async_renderer(app_render_fn)
renderer_sync._set_output_metadata(
output_name="renderer_sync",
)
@async_renderer
def renderer_sync() -> str:
return test_val

# All renderers are async in execution.
assert not is_async_callable(renderer_sync)

Expand All @@ -264,14 +261,11 @@ def app_render_fn() -> str:

async_test_val = "Async: Hello World!"

async def async_app_render_fn() -> str:
@async_renderer
async def renderer_async() -> str:
await asyncio.sleep(0)
return async_test_val

renderer_async = async_renderer(async_app_render_fn)
renderer_async._set_output_metadata(
output_name="renderer_async",
)
if not is_async_callable(renderer_async):
raise RuntimeError("Expected `renderer_async` to be a coro function")

Expand Down