1818import os
1919import sys
2020import typing
21+ from abc import ABC , abstractmethod
2122from typing import (
2223 TYPE_CHECKING ,
2324 Any ,
@@ -146,39 +147,55 @@ def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None:
146147HandlerFn = Callable [Concatenate [RenderMeta , RenderFnAsync [IT ], P ], Awaitable [OT ]]
147148
148149
149- class Renderer (Generic [OT ]):
150+ class Renderer (Generic [OT ], ABC ):
150151 """
151152 Output Renderer
152153
153- Base class to build up :class:`~shiny.render.RendererSync` and
154+ Base class for classes :class:`~shiny.render.RendererSync` and
154155 :class:`~shiny.render.RendererAsync`.
155156
156- When the `.__call__` method is invoked, the handler function (typically defined by
157- package authors) is called. The handler function is given `meta` information, the
158- (app-supplied) render function, and any keyword arguments supplied to the render
159- decorator.
157+ When the `.__call__` method is invoked, the handler function (`handler_fn`)
158+ (typically defined by package authors) is asynchronously called. The handler
159+ function is given `meta` information, the (app-supplied) render function, and any
160+ keyword arguments supplied to the render decorator. For consistency, the first two
161+ parameters have been (arbitrarily) implemented as `_meta` and `_fn`.
160162
161- The (app-supplied) render function should return type `IT`. The handler function
162- (defined by package authors) defines the parameter specification of type `P` and
163- should asynchronously return an object of type `OT`. Note that in many cases but not
164- all, `IT` and `OT` will be the same. `None` values must always be defined in `IT`
165- and `OT`.
163+ The (app-supplied) render function (`render_fn`) returns type `IT`. The handler
164+ function (defined by package authors) defines the parameter specification of type
165+ `P` and asynchronously returns an object of type `OT`. Note that in many cases but
166+ not all, `IT` and `OT` will be the same. `None` values should always be defined in
167+ `IT` and `OT`.
166168
167169
170+ Methods
171+ -------
172+ _is_async
173+ If `TRUE`, the app-supplied render function is asynchronous. Must be implemented
174+ in subclasses.
175+ _meta
176+ A named tuple of values: `is_async`, `session` (the :class:`~shiny.Session`
177+ object), and `name` (the name of the output being rendered)
178+
168179 See Also
169180 --------
170- * :class:`~shiny.render.RendererRun`
171181 * :class:`~shiny.render.RendererSync`
172182 * :class:`~shiny.render.RendererAsync`
173183 """
174184
185+ @abstractmethod
175186 def __call__ (self ) -> OT :
176187 """
177188 Executes the renderer as a function. Must be implemented by subclasses.
178189 """
179- raise NotImplementedError
190+ ...
180191
181- def __init__ (self , * , name : str ) -> None :
192+ def __init__ (
193+ self ,
194+ * ,
195+ render_fn : RenderFn [IT ],
196+ handler_fn : HandlerFn [IT , P , OT ],
197+ params : RendererParams [P ],
198+ ) -> None :
182199 """
183200 Renderer init method
184201
@@ -190,7 +207,21 @@ def __init__(self, *, name: str) -> None:
190207 Documentation of the output function. Ex: `"My text output will be displayed
191208 verbatim".
192209 """
193- self .__name__ = name
210+ # Copy over function name as it is consistent with how Session and Output
211+ # retrieve function names
212+ self .__name__ = render_fn .__name__
213+
214+ if not _utils .is_async_callable (handler_fn ):
215+ raise TypeError (
216+ self .__class__ .__name__ + " requires an async handler function"
217+ )
218+
219+ # `render_fn` is not required to be async. For consistency, we wrapped in an
220+ # async function so that when it's passed in to `handler_fn`, `render_fn` is
221+ # **always** an async function.
222+ self ._render_fn = _utils .wrap_async (render_fn )
223+ self ._handler_fn = handler_fn
224+ self ._params = params
194225
195226 def _set_metadata (self , session : Session , name : str ) -> None :
196227 """
@@ -200,60 +231,16 @@ def _set_metadata(self, session: Session, name: str) -> None:
200231 self ._session : Session = session
201232 self ._name : str = name
202233
203-
204- class RendererRun (Renderer [OT ]):
205- """
206- Convenience class to define a `_run` method
207-
208- This class is used to define a `_run` method that is called by the `.__call__`
209- method in subclasses.
210-
211- Methods
212- -------
213- _is_async
214- If `TRUE`, the app-supplied render function is asynchronous. Must be implemented
215- in subclasses.
216- _meta
217- A named dictionary of values: `is_async`, `session` (the :class:`~shiny.Session`
218- object), and `name` (the name of the output being rendered)
219-
220- See Also
221- --------
222- * :class:`~shiny.render.Renderer`
223- * :class:`~shiny.render.RendererSync`
224- * :class:`~shiny.render.RendererAsync`
225- """
226-
227- def _is_async (self ) -> bool :
228- raise NotImplementedError ()
229-
230234 def _meta (self ) -> RenderMeta :
231235 return RenderMeta (
232236 is_async = self ._is_async (),
233237 session = self ._session ,
234238 name = self ._name ,
235239 )
236240
237- def __init__ (
238- self ,
239- render_fn : RenderFn [IT ],
240- handler_fn : HandlerFn [IT , P , OT ],
241- params : RendererParams [P ],
242- ) -> None :
243- if not _utils .is_async_callable (handler_fn ):
244- raise TypeError (
245- self .__class__ .__name__ + " requires an async handler function"
246- )
247- super ().__init__ (
248- name = render_fn .__name__ ,
249- )
250-
251- # `render_fn` is not required to be async. For consistency, we wrapped in an
252- # async function so that when it's passed in to `handler_fn`, `render_fn` is
253- # **always** an async function.
254- self ._render_fn = _utils .wrap_async (render_fn )
255- self ._handler_fn = handler_fn
256- self ._params = params
241+ @abstractmethod
242+ def _is_async (self ) -> bool :
243+ ...
257244
258245 async def _run (self ) -> OT :
259246 """
@@ -283,7 +270,7 @@ async def _run(self) -> OT:
283270
284271
285272# Using a second class to help clarify that it is of a particular type
286- class RendererSync (RendererRun [OT ]):
273+ class RendererSync (Renderer [OT ]):
287274 """
288275 Output Renderer (Synchronous)
289276
@@ -298,7 +285,6 @@ class RendererSync(RendererRun[OT]):
298285 See Also
299286 --------
300287 * :class:`~shiny.render.Renderer`
301- * :class:`~shiny.render.RendererRun`
302288 * :class:`~shiny.render.RendererAsync`
303289 """
304290
@@ -315,11 +301,11 @@ def __init__(
315301 raise TypeError (
316302 self .__class__ .__name__ + " requires a synchronous render function"
317303 )
318- # super == RendererRun
304+ # super == Renderer
319305 super ().__init__ (
320- render_fn ,
321- handler_fn ,
322- params ,
306+ render_fn = render_fn ,
307+ handler_fn = handler_fn ,
308+ params = params ,
323309 )
324310
325311 def __call__ (self ) -> OT :
@@ -329,7 +315,7 @@ def __call__(self) -> OT:
329315# The reason for having a separate RendererAsync class is because the __call__
330316# method is marked here as async; you can't have a single class where one method could
331317# be either sync or async.
332- class RendererAsync (RendererRun [OT ]):
318+ class RendererAsync (Renderer [OT ]):
333319 # TODO-barret; docs
334320 def _is_async (self ) -> bool :
335321 return True
@@ -344,11 +330,11 @@ def __init__(
344330 raise TypeError (
345331 self .__class__ .__name__ + " requires an asynchronous render function"
346332 )
347- # super == RendererRun
333+ # super == Renderer
348334 super ().__init__ (
349- render_fn ,
350- handler_fn ,
351- params ,
335+ render_fn = render_fn ,
336+ handler_fn = handler_fn ,
337+ params = params ,
352338 )
353339
354340 async def __call__ (self ) -> OT : # pyright: ignore[reportIncompatibleMethodOverride]
@@ -360,10 +346,10 @@ async def __call__(self) -> OT: # pyright: ignore[reportIncompatibleMethodOverr
360346# ======================================================================================
361347
362348
363- # A RenderFunction object is given a user-provided function which returns an IT . When
364- # the .__call___ method is invoked, it calls the user-provided function (which returns
365- # an IT ), then converts the IT to an OT . Note that in many cases but not all, IT and OT
366- # will be the same.
349+ # A RenderFunction object is given a app-supplied function which returns an `IT` . When
350+ # the .__call__ method is invoked, it calls the app-supplied function (which returns an
351+ # `IT` ), then converts the `IT` to an `OT` . Note that in many cases but not all, `IT`
352+ # and `OT` will be the same.
367353class RenderFunction (Generic [IT , OT ], Renderer [OT ]):
368354 """
369355 Deprecated. Please use :func:`~shiny.render.renderer_components` instead.
@@ -372,6 +358,7 @@ class RenderFunction(Generic[IT, OT], Renderer[OT]):
372358 def __init__ (self , fn : Callable [[], IT ]) -> None :
373359 self .__name__ = fn .__name__
374360 self .__doc__ = fn .__doc__
361+ # TODO-barret; call super and make a __call__ method
375362
376363
377364# The reason for having a separate RenderFunctionAsync class is because the __call__
@@ -442,7 +429,11 @@ def _assert_handler_fn(handler_fn: HandlerFn[IT, P, OT]) -> None:
442429# ======================================================================================
443430
444431
432+ # Signature of a renderer decorator function
445433RendererDeco = Callable [[RenderFn [IT ]], Renderer [OT ]]
434+ # Signature of a decorator that can be called with and without parenthesis
435+ # With parens returns a `Renderer[OT]`
436+ # Without parens returns a `RendererDeco[IT, OT]`
446437RenderImplFn = Callable [
447438 [
448439 Optional [RenderFn [IT ]],
0 commit comments