Skip to content

Commit 8b87e09

Browse files
committed
Move outputRPC to be defined within Outputs class; Create a session proxy and isolate to prevent handlers from requiring it
1 parent bceaf85 commit 8b87e09

File tree

1 file changed

+62
-52
lines changed

1 file changed

+62
-52
lines changed

shiny/session/_session.py

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -36,33 +36,31 @@
3636
from starlette.responses import HTMLResponse, PlainTextResponse, StreamingResponse
3737
from starlette.types import ASGIApp
3838

39-
from .._typing_extensions import NotRequired
40-
from ..types import Jsonifiable
41-
42-
if TYPE_CHECKING:
43-
from .._app import App
44-
4539
from .. import _utils, reactive, render
4640
from .._connection import Connection, ConnectionClosed
4741
from .._deprecated import warn_deprecated
4842
from .._docstring import add_example
4943
from .._fileupload import FileInfo, FileUploadManager
5044
from .._namespaces import Id, ResolvedId, Root
51-
from .._typing_extensions import TypedDict
45+
from .._typing_extensions import NotRequired, TypedDict
5246
from .._utils import wrap_async
5347
from ..http_staticfiles import FileResponse
5448
from ..input_handler import input_handlers
5549
from ..reactive import Effect_, Value, effect, flush, isolate
5650
from ..reactive._core import lock, on_flushed
5751
from ..render.renderer import Renderer, RendererT
5852
from ..types import (
53+
Jsonifiable,
5954
SafeException,
6055
SilentCancelOutputException,
6156
SilentException,
6257
SilentOperationInProgressException,
6358
)
6459
from ._utils import RenderedDeps, read_thunk_opt, session_context
6560

61+
if TYPE_CHECKING:
62+
from .._app import App
63+
6664

6765
class ConnectionState(enum.Enum):
6866
Start = 0
@@ -186,6 +184,10 @@ def __init__(
186184
self.id: str = id
187185
self._conn: Connection = conn
188186
self._debug: bool = debug
187+
self._message_handlers: dict[
188+
str,
189+
Callable[..., Awaitable[Jsonifiable]],
190+
] = self._create_message_handlers()
189191

190192
# The HTTPConnection representing the WebSocket. This is used so that we can
191193
# query information about the request, like headers, cookies, etc.
@@ -215,10 +217,6 @@ def __init__(
215217

216218
self._outbound_message_queues = OutBoundMessageQueues()
217219

218-
self._message_handlers: dict[
219-
str,
220-
Callable[..., Awaitable[Jsonifiable]],
221-
] = self._create_message_handlers()
222220
self._file_upload_manager: FileUploadManager = FileUploadManager()
223221
self._on_ended_callbacks = _utils.AsyncCallbacks()
224222
self._has_run_session_end_tasks: bool = False
@@ -405,7 +403,7 @@ async def _dispatch(self, message: ClientMessageOther) -> None:
405403
# TODO: handle `blobs`
406404
value = await func(*message["args"])
407405
except Exception as e:
408-
await self._send_error_response(message, "Error: " + str(e))
406+
await self._send_error_response(message, str(e))
409407
return
410408

411409
await self._send_response(message, value)
@@ -419,45 +417,6 @@ def _create_message_handlers(
419417
) -> dict[str, Callable[..., Awaitable[Jsonifiable]]]:
420418
# TODO-future; Make sure these methods work within MockSession
421419

422-
# TODO-barret; Move this to Outputs class
423-
async def outputRPC(
424-
outputId: str,
425-
handler: str,
426-
msg: object,
427-
) -> Jsonifiable:
428-
"""
429-
Used to handle request messages from an Output Renderer.
430-
431-
Typically, this is used when an Output Renderer needs to set an input value.
432-
E.g. a @render.data_frame result with a `mode="edit"` parameter will use
433-
this to set the value of the data frame when a cell is edited.
434-
435-
The value returned by the handler is sent back to the client with the original unique id (`tag`).
436-
"""
437-
# TODO-barret; can this be used right before calling the function?
438-
with session_context(self):
439-
if not (outputId in self.output._outputs):
440-
raise RuntimeError(
441-
f"Received request message for an unknown Output Renderer with id `{outputId}`"
442-
)
443-
444-
output_info = self.output._outputs[outputId]
445-
renderer = output_info.renderer
446-
handler = f"_handle_{handler}"
447-
if not (
448-
hasattr(renderer, handler) and callable(getattr(renderer, handler))
449-
):
450-
raise RuntimeError(
451-
f"Output Renderer with id `{outputId}` does not have method `{handler}` to handler request message"
452-
)
453-
try:
454-
return await getattr(renderer, handler)(msg)
455-
except Exception as e:
456-
print(e)
457-
raise RuntimeError(
458-
f"Error while handling request message for Output Renderer with id `{outputId}`"
459-
)
460-
461420
async def uploadInit(file_infos: list[FileInfo]) -> dict[str, Jsonifiable]:
462421
with session_context(self):
463422
if self._debug:
@@ -493,7 +452,6 @@ async def uploadEnd(job_id: str, input_id: str) -> None:
493452
return None
494453

495454
return {
496-
"outputRPC": outputRPC,
497455
"uploadInit": uploadInit,
498456
"uploadEnd": uploadEnd,
499457
}
@@ -1105,6 +1063,58 @@ def __init__(
11051063
self._ns = ns
11061064
self._outputs = outputs
11071065

1066+
self._init_message_handlers()
1067+
1068+
def _init_message_handlers(self) -> None:
1069+
async def outputRPC(
1070+
outputId: str,
1071+
handler: str,
1072+
msg: object,
1073+
) -> Jsonifiable:
1074+
"""
1075+
Used to handle request messages from an Output Renderer.
1076+
1077+
Typically, this is used when an Output Renderer needs to set an input value.
1078+
E.g. a @render.data_frame result with a `mode="edit"` parameter will use
1079+
this to set the value of the data frame when a cell is edited.
1080+
1081+
The value returned by the handler is sent back to the client with the original unique id (`tag`).
1082+
"""
1083+
if not (outputId in self._outputs):
1084+
raise RuntimeError(
1085+
f"Received request message for an unknown Output Renderer with id `{outputId}`"
1086+
)
1087+
1088+
output_info = self._outputs[outputId]
1089+
renderer = output_info.renderer
1090+
handler = f"_handle_{handler}"
1091+
if not (
1092+
hasattr(renderer, handler) and callable(getattr(renderer, handler))
1093+
):
1094+
raise RuntimeError(
1095+
f"Output Renderer with id `{outputId}` does not have method `{handler}` to handler request message"
1096+
)
1097+
1098+
# Make a new session proxy if the output is namespaced;
1099+
# No need to make recursive proxies as the important part is a prefix exists
1100+
sessProxy = self._session
1101+
if "-" in outputId:
1102+
modPrefix, _ = outputId.rsplit("-", 1)
1103+
sessProxy = self._session.make_scope(modPrefix)
1104+
with session_context(sessProxy):
1105+
with isolate():
1106+
try:
1107+
# TODO-barret assert that the function has been marked as an outputRPC handler
1108+
return await getattr(renderer, handler)(msg)
1109+
except Exception as e:
1110+
print("error: ", e)
1111+
raise RuntimeError(
1112+
f"Error while handling request message for Output Renderer with id `{outputId}`"
1113+
)
1114+
1115+
# Add the message handler
1116+
self._session._message_handlers["outputRPC"] = outputRPC
1117+
11081118
@overload
11091119
def __call__(self, renderer: RendererT) -> RendererT:
11101120
pass

0 commit comments

Comments
 (0)