3636from starlette .responses import HTMLResponse , PlainTextResponse , StreamingResponse
3737from 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-
4539from .. import _utils , reactive , render
4640from .._connection import Connection , ConnectionClosed
4741from .._deprecated import warn_deprecated
4842from .._docstring import add_example
4943from .._fileupload import FileInfo , FileUploadManager
5044from .._namespaces import Id , ResolvedId , Root
51- from .._typing_extensions import TypedDict
45+ from .._typing_extensions import NotRequired , TypedDict
5246from .._utils import wrap_async
5347from ..http_staticfiles import FileResponse
5448from ..input_handler import input_handlers
5549from ..reactive import Effect_ , Value , effect , flush , isolate
5650from ..reactive ._core import lock , on_flushed
5751from ..render .renderer import Renderer , RendererT
5852from ..types import (
53+ Jsonifiable ,
5954 SafeException ,
6055 SilentCancelOutputException ,
6156 SilentException ,
6257 SilentOperationInProgressException ,
6358)
6459from ._utils import RenderedDeps , read_thunk_opt , session_context
6560
61+ if TYPE_CHECKING :
62+ from .._app import App
63+
6664
6765class 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