From 3797a5de7bc6e7beda288a601fc67511bf0ccea9 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 17 Jan 2024 16:25:37 -0500 Subject: [PATCH 1/4] Expose `render.renderer.RendererBaseT` Fixes #1022 --- shiny/render/renderer/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shiny/render/renderer/__init__.py b/shiny/render/renderer/__init__.py index 57fff6cc6..f61367f39 100644 --- a/shiny/render/renderer/__init__.py +++ b/shiny/render/renderer/__init__.py @@ -3,7 +3,7 @@ Renderer, ValueFn, Jsonifiable, - RendererBaseT, # pyright: ignore[reportUnusedImport] + RendererBaseT, AsyncValueFn, # IT, # pyright: ignore[reportUnusedImport] ) @@ -14,4 +14,5 @@ "ValueFn", "Jsonifiable", "AsyncValueFn", + "RendererBaseT", ) From 4decf0a8cd39ec391f1e98e22bdd82b0d7346979 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 17 Jan 2024 16:42:53 -0500 Subject: [PATCH 2/4] Do not require `| None` when defining Renderers Fixes #1021 --- shiny/render/renderer/_renderer.py | 26 +++++++++++++++--------- shiny/render/transformer/_transformer.py | 24 +++++++++++----------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/shiny/render/renderer/_renderer.py b/shiny/render/renderer/_renderer.py index a76519257..0cbf4f8c4 100644 --- a/shiny/render/renderer/_renderer.py +++ b/shiny/render/renderer/_renderer.py @@ -82,9 +82,12 @@ DefaultUIFnResultOrNone = Union[DefaultUIFnResult, None] DefaultUIFn = Callable[[str], DefaultUIFnResultOrNone] +# Requiring `None` type throughout the value functions as `return` returns `None` type. +# This is typically paired with `req(False)` to exit quickly. +# If package authors want to NOT allow `None` type, they can capture it in a custom render method with a runtime error. (Or make a new RendererThatCantBeNone class) ValueFn = Union[ - Callable[[], IT], - Callable[[], Awaitable[IT]], + Callable[[], Union[IT, None]], + Callable[[], Awaitable[Union[IT, None]]], ] """ App-supplied output value function which returns type `IT`. This function can be @@ -223,7 +226,10 @@ class AsyncValueFn(Generic[IT]): Type definition: `Callable[[], Awaitable[IT]]` """ - def __init__(self, fn: Callable[[], IT] | Callable[[], Awaitable[IT]]): + def __init__( + self, + fn: Callable[[], IT | None] | Callable[[], Awaitable[IT | None]], + ): if isinstance(fn, AsyncValueFn): raise TypeError( "Must not call `AsyncValueFn.__init__` with an object of class `AsyncValueFn`" @@ -232,7 +238,7 @@ def __init__(self, fn: Callable[[], IT] | Callable[[], Awaitable[IT]]): self._fn = wrap_async(fn) self._orig_fn = fn - async def __call__(self) -> IT: + async def __call__(self) -> IT | None: """ Call the asynchronous function. """ @@ -249,7 +255,7 @@ def is_async(self) -> bool: """ return self._is_async - def get_async_fn(self) -> Callable[[], Awaitable[IT]]: + def get_async_fn(self) -> Callable[[], Awaitable[IT | None]]: """ Return the async value function. @@ -260,7 +266,7 @@ def get_async_fn(self) -> Callable[[], Awaitable[IT]]: """ return self._fn - def get_sync_fn(self) -> Callable[[], IT]: + def get_sync_fn(self) -> Callable[[], IT | None]: """ Retrieve the original, synchronous value function function. @@ -286,14 +292,14 @@ class Renderer(RendererBase, Generic[IT]): TODO-barret-docs """ - fn: AsyncValueFn[IT | None] + fn: AsyncValueFn[IT] """ App-supplied output value function which returns type `IT`. This function is always asyncronous as the original app-supplied function possibly wrapped to execute asynchonously. """ - def __call__(self, _fn: ValueFn[IT | None]) -> Self: + def __call__(self, _fn: ValueFn[IT]) -> Self: """ Renderer __call__ docs here; Sets app's value function @@ -308,7 +314,7 @@ def __call__(self, _fn: ValueFn[IT | None]) -> Self: self.__name__: str = _fn.__name__ # Set value function with extra meta information - self.fn: AsyncValueFn[IT | None] = AsyncValueFn(_fn) + self.fn: AsyncValueFn[IT] = AsyncValueFn(_fn) # Allow for App authors to not require `@output` self._auto_register() @@ -317,7 +323,7 @@ def __call__(self, _fn: ValueFn[IT | None]) -> Self: def __init__( self, - _fn: Optional[ValueFn[IT | None]] = None, + _fn: Optional[ValueFn[IT]] = None, ): # Do not display docs here. If docs are present, it could highjack the docs of # the subclass's `__init__` method. diff --git a/shiny/render/transformer/_transformer.py b/shiny/render/transformer/_transformer.py index 114c88269..42fb9626e 100644 --- a/shiny/render/transformer/_transformer.py +++ b/shiny/render/transformer/_transformer.py @@ -31,7 +31,7 @@ overload, ) -from ..renderer import AsyncValueFn, Jsonifiable, RendererBase +from ..renderer import Jsonifiable, RendererBase from ..renderer._renderer import DefaultUIFn, DefaultUIFnResultOrNone if TYPE_CHECKING: @@ -255,13 +255,16 @@ def __init__( " Ex `async def my_transformer(....`" ) - # Upgrade value function to be async; - # Calling an async function has a ~35ns overhead (barret's machine) - # Checking if a function is async has a 180+ns overhead (barret's machine) - # -> It is faster to always call an async function than to always check if it is async - # Always being async simplifies the execution - self._fn = AsyncValueFn(value_fn) - self._value_fn_is_async = self._fn.is_async() # legacy key + # # Upgrade value function to be async; + # # Calling an async function has a ~35ns overhead (barret's machine) + # # Checking if a function is async has a 180+ns overhead (barret's machine) + # # -> It is faster to always call an async function than to always check if it is async + # # Always being async simplifies the execution + # # Not used + # self._fn = AsyncValueFn(value_fn) + + self._value_fn = value_fn + self._value_fn_is_async = is_async_callable(value_fn) # legacy key self.__name__ = value_fn.__name__ self._transformer = transform_fn @@ -302,14 +305,11 @@ async def _run(self) -> OT: `*args` is required to use with `**kwargs` when using `typing.ParamSpec`. """ - value_fn = ( - self._fn.get_async_fn() if self._fn.is_async() else self._fn.get_sync_fn() - ) ret = await self._transformer( # TransformerMetadata self._meta(), # Callable[[], IT] | Callable[[], Awaitable[IT]] - value_fn, + self._value_fn, # P *self._params.args, **self._params.kwargs, From eb28fabb2f896207d5ff5faf1791e1683c85c1e0 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 17 Jan 2024 16:45:15 -0500 Subject: [PATCH 3/4] Remove `| None` Renderer type usage --- 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 426e319f2..8f0887f4e 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -104,7 +104,7 @@ def auto_output_ui( def __init__( self, - _fn: Optional[ValueFn[str | None]] = None, + _fn: Optional[ValueFn[str]] = None, *, inline: bool = False, ) -> None: @@ -165,7 +165,7 @@ def auto_output_ui( def __init__( self, - _fn: Optional[ValueFn[str | None]] = None, + _fn: Optional[ValueFn[str]] = None, *, placeholder: bool = True, ) -> None: From 9115f2477957969fb90b0559e2d65df765f4390c Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 17 Jan 2024 16:47:35 -0500 Subject: [PATCH 4/4] Rename more `OutputRenderer` `default_ui` variables back to original name (where possible) --- shiny/render/transformer/_transformer.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shiny/render/transformer/_transformer.py b/shiny/render/transformer/_transformer.py index 42fb9626e..dc6101bed 100644 --- a/shiny/render/transformer/_transformer.py +++ b/shiny/render/transformer/_transformer.py @@ -269,11 +269,11 @@ def __init__( self._transformer = transform_fn self._params = params - self._auto_output_ui = default_ui - self._auto_output_ui_passthrough_args = default_ui_passthrough_args + self._default_ui = default_ui + self._default_ui_passthrough_args = default_ui_passthrough_args - self._auto_output_ui_args: tuple[object, ...] = tuple() - self._auto_output_ui_kwargs: dict[str, object] = dict() + self._default_ui_args: tuple[object, ...] = tuple() + self._default_ui_kwargs: dict[str, object] = dict() # Allow for App authors to not require `@output` self._auto_register() @@ -323,19 +323,19 @@ def auto_output_ui( id: str, **kwargs: object, ) -> DefaultUIFnResultOrNone: - if self._auto_output_ui is None: + if self._default_ui is None: return None - if self._auto_output_ui_passthrough_args is not None: + if self._default_ui_passthrough_args is not None: kwargs.update( { k: v for k, v in self._params.kwargs.items() - if k in self._auto_output_ui_passthrough_args and v is not MISSING + if k in self._default_ui_passthrough_args and v is not MISSING } ) - return self._auto_output_ui(id, *self._auto_output_ui_args, **kwargs) + return self._default_ui(id, *self._default_ui_args, **kwargs) async def render(self) -> Jsonifiable: ret = await self._run()