Skip to content

Commit 05e93e7

Browse files
committed
Merger RendererRun into Renderer. Use ABC to mark some methods as abstract.
1 parent df843d0 commit 05e93e7

File tree

1 file changed

+68
-77
lines changed

1 file changed

+68
-77
lines changed

shiny/render/_render.py

Lines changed: 68 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import os
1919
import sys
2020
import typing
21+
from abc import ABC, abstractmethod
2122
from typing import (
2223
TYPE_CHECKING,
2324
Any,
@@ -146,39 +147,55 @@ def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None:
146147
HandlerFn = 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.
367353
class 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
445433
RendererDeco = 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]`
446437
RenderImplFn = Callable[
447438
[
448439
Optional[RenderFn[IT]],

0 commit comments

Comments
 (0)