Exemple #1
0
def trigger_rerun():
    ctx = ReportThread.get_report_ctx()

    this_session = None

    current_server = Server.get_current()
    if hasattr(current_server, '_session_infos'):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if (
                # Streamlit < 0.54.0
            (hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg) or
                # Streamlit >= 0.54.0
            (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue)):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')
    this_session.request_rerun()
def _get_session_object():
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    this_session = None
    current_server = Server.get_current()
    if hasattr(current_server, '_session_infos'):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if (
                # Streamlit < 0.54.0
            (hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg) or
                # Streamlit >= 0.54.0
            (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue) or
                # Streamlit >= 0.65.2
            (not hasattr(s, '_main_dg')
             and s._uploaded_file_mgr == ctx.uploaded_file_mgr)):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')

    return this_session
Exemple #3
0
def _get_session_raw():
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    this_session = None

    current_server = Server.get_current()
    if hasattr(current_server, "_session_infos"):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info
        if (
                # Streamlit < 0.54.0
            (hasattr(s, "_main_dg") and s.session._main_dg == ctx.main_dg) or
                # Streamlit >= 0.54.0
            (not hasattr(s, "_main_dg") and s.session.enqueue == ctx.enqueue)):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            "Are you doing something fancy with threads?")
    return this_session
Exemple #4
0
def _get_widget_states():
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    session = None

    current_server = Server.get_current()
    if hasattr(current_server, '_session_infos'):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        if session_info.session.enqueue == ctx.enqueue:
            session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            "Are you doing something fancy with threads?")
    # Got the session object!

    return session._widget_states
Exemple #5
0
def get(**kwargs):

    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    this_session = None

    current_server = Server.get_current()
    if hasattr(current_server, '_session_infos'):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if (
                # Streamlit < 0.54.0
            (hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg) or
                # Streamlit >= 0.54.0
            (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue)):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')

    # Got the session object! Now let's attach some state into it.

    if not hasattr(this_session, '_custom_session_state'):
        this_session._custom_session_state = SessionState(**kwargs)

    return this_session._custom_session_state
Exemple #6
0
def get_current_session() -> ReportSession:  # type: ignore
    # Hack to get the session object from Streamlit.
    ctx = ReportThread.get_report_ctx()

    this_session = None

    current_server = Server.get_current()
    if hasattr(current_server, "_session_infos"):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if ((hasattr(s, "_main_dg") and s._main_dg == ctx.main_dg
             )  # Streamlit < 0.54.0
                or (not hasattr(s, "_main_dg") and s.enqueue == ctx.enqueue
                    )  # Streamlit >= 0.54.0
                or (not hasattr(s, "_main_dg")
                    and s._uploaded_file_mgr == ctx.uploaded_file_mgr
                    )  # Streamlit >= 0.65.2
            ):
            this_session = s
    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object. "
            "Are you doing something fancy with threads?")
    return this_session
def get(**kwargs):
    """Gets a SessionState object for the current session.
    Creates a new object if necessary.
    Parameters
    ----------
    **kwargs : any
        Default values you want to add to the session state, if we're creating a
        new one.
    Example
    -------
    >>> session_state = get(user_name='', favorite_color='black')
    >>> session_state.user_name
    ''
    >>> session_state.user_name = 'Mary'
    >>> session_state.favorite_color
    'black'
    Since you set user_name above, next time your script runs this will be the
    result:
    >>> session_state = get(user_name='', favorite_color='black')
    >>> session_state.user_name
    'Mary'
    """
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    this_session = None

    current_server = Server.get_current()
    if hasattr(current_server, "_session_infos"):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if (
                # Streamlit < 0.54.0
            (hasattr(s, "_main_dg") and s._main_dg == ctx.main_dg)
                or  # noqa: W504
                # Streamlit >= 0.54.0
            (not hasattr(s, "_main_dg") and s.enqueue == ctx.enqueue
             ) or  # noqa: W504
                # Streamlit >= 0.65.2
            (not hasattr(s, "_main_dg")
             and s._uploaded_file_mgr == ctx.uploaded_file_mgr)):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object. "
            "Are you doing something fancy with threads?")

    # Got the session object! Now let's attach some state into it.

    if not hasattr(this_session, "_custom_session_state"):
        this_session._custom_session_state = SessionState(**kwargs)

    return this_session._custom_session_state
Exemple #8
0
 def get_app(self):
     # Create a Server, and patch its _on_stopped function
     # to no-op. This prevents it from shutting down the
     # ioloop when it stops.
     self.server = Server(self.io_loop, '/not/a/script.py', [])
     self.server._on_stopped = mock.MagicMock()
     app = self.server._create_app()
     return app
 def get_app(self):
     # Create a Server, and patch its _on_stopped function
     # to no-op. This prevents it from shutting down the
     # ioloop when it stops.
     self.server = Server(self.io_loop, "/not/a/script.py",
                          "test command line")
     self.server._on_stopped = mock.MagicMock()  # type: ignore[assignment]
     app = self.server._create_app()
     return app
def get(**kwargs):
    # """Gets a SessionState object for the current session.
    # Creates a new object if necessary.
    # Parameters
    # ----------
    # **kwargs : any
    #     Default values you want to add to the session state, if we're creating a
    #     new one.
    # Example
    # -------
    # >>> session_state = get(user_name='', favorite_color='black')
    # >>> session_state.user_name
    # ''
    # >>> session_state.user_name = 'Mary'
    # >>> session_state.favorite_color
    # 'black'
    # Since you set user_name above, next time your script runs this will be the
    # result:
    # >>> session_state = get(user_name='', favorite_color='black')
    # >>> session_state.user_name
    # 'Mary'
    # """
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    this_session = None

    current_server = Server.get_current()
    if hasattr(current_server, '_session_infos'):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if (
                # Streamlit < 0.54.0
                (hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg)
                or
                # Streamlit >= 0.54.0
                (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue)
        ):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')

    # Got the session object! Now let's attach some state into it.

    if not hasattr(this_session, '_custom_session_state'):
        this_session._custom_session_state = SessionState(**kwargs)

    return this_session._custom_session_state
Exemple #11
0
def get(**kwargs):
    """Gets a SessionState object for the current session.
    Creates a new object if necessary.
    Parameters
    ----------
    **kwargs : any
        Default values you want to add to the session state, if we're creating a
        new one.
    Example
    -------

    'Mary'
    """
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    this_session = None

    current_server = Server.get_current()
    if hasattr(current_server, '_session_infos'):
        # Streamlit < 0.56
        session_infos = Server.get_current()._session_infos.values()
    else:
        session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if (
            # Streamlit < 0.54.0
            (hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg)
            or
            # Streamlit >= 0.54.0
            (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue)
            or
            # Streamlit >= 0.65.2
            (not hasattr(s, '_main_dg') and s._uploaded_file_mgr == ctx.uploaded_file_mgr)
        ):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object. "
            'Are you doing something fancy with threads?')

    # Got the session object! Now let's attach some state into it.

    if not hasattr(this_session, '_custom_session_state'):
        this_session._custom_session_state = SessionState(**kwargs)

    return this_session._custom_session_state
Exemple #12
0
def _get_session():
    session_id = get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)
    if session_info is None:
        raise RuntimeError("Couldn't get your Streamlit Session object.")

    return session_info.session
Exemple #13
0
def get_session_id():
    # Copied from tvst's great gist:
    # https://gist.github.com/tvst/6ef6287b2f3363265d51531c62a84f51
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_infos.values()

    for session_info in session_infos:
        s = session_info.session
        if ((hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg)
                # Streamlit < 0.54.0
                or
                # Streamlit >= 0.54.0
            (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue)):
            session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')

    return id(session)
Exemple #14
0
def get_state(setup_func: Callable[..., T], **kwargs) -> T:
    ctx = ReportThread.get_report_ctx()

    this_session = None
    session_infos = Server.get_current()._session_infos.values()

    for session_info in session_infos:
        '''
        if session_info.session._main_dg == ctx.main_dg:
            session = session_info.session
        '''
        s = session_info.session
        if ((hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg)
                or (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue)):
            this_session = s

    if this_session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')

    # Got the session object! Now let's attach some state into it.

    if not getattr(this_session, '_custom_session_state', None):
        this_session._custom_session_state = setup_func(**kwargs)

    return this_session._custom_session_state
Exemple #15
0
def _get_full_session():
    session_id = get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)

    if session_info is None:
        raise RuntimeError("Couldn't get your Streamlit Session object.")

    # MODIFIED ORIGINAL _get_session CODE SO WE CAN ACCESS HEADERS FOR USER
    # return session_info.session
    return session_info
Exemple #16
0
def run(script_path):
    """Run a script in a separate thread and start a server for the app.

    This starts a blocking ioloop.

    Parameters
    ----------
    script_path : str

    """
    _fix_sys_path(script_path)
    _fix_matplotlib_crash()

    # Install a signal handler that will shut down the ioloop
    # and close all our threads
    _set_up_signal_handler()

    ioloop = tornado.ioloop.IOLoop.current()

    # Create and start the server.
    server = Server(ioloop, script_path, sys.argv)
    server.add_preheated_report_session()
    server.start(_on_server_start)

    # Start the ioloop. This function will not return until the
    # server is shut down.
    ioloop.start()
Exemple #17
0
def run(script_path, command_line, args):
    """Run a script in a separate thread and start a server for the app.

    This starts a blocking ioloop.

    Parameters
    ----------
    script_path : str
    command_line : str
    args : [str]

    """
    _fix_sys_path(script_path)
    _fix_matplotlib_crash()
    _fix_tornado_crash()
    _fix_sys_argv(script_path, args)

    # Install a signal handler that will shut down the ioloop
    # and close all our threads
    _set_up_signal_handler()

    ioloop = tornado.ioloop.IOLoop.current()

    # Create and start the server.
    server = Server(ioloop, script_path, command_line)
    server.start(_on_server_start)

    # (Must com after start(), because this starts a new thread and start() may
    # call sys.exit() which doesn't kill other threads.
    server.add_preheated_report_session()

    # Start the ioloop. This function will not return until the
    # server is shut down.
    ioloop.start()
Exemple #18
0
def get_user_id():
    ctx = ReportThread.get_report_ctx()
    session_infos = Server.get_current()._session_info_by_id.values()
    # print(dir(session_infos[0]))
    for session_info in session_infos:
        # print("Session info session dir:")
        # print(dir(session_info.session))
        if session_info.session.enqueue == ctx.enqueue:
            user_id = session_info.session.id
            # print("Current: " + str(session_info.session.id))
        else:
            # print(session_info.session.id)
            pass
    return user_id
Exemple #19
0
def get(**kwargs):
    """Gets a SessionState object for the current session.

    Creates a new object if necessary.

    Parameters
    ----------
    **kwargs : any
        Default values you want to add to the session state, if we're creating a
        new one.

    Example
    -------
    >>> session_state = get(user_name='', favorite_color='black')
    >>> session_state.user_name
    ''
    >>> session_state.user_name = 'Mary'
    >>> session_state.favorite_color
    'black'

    Since you set user_name above, next time your script runs this will be the
    result:
    >>> session_state = get(user_name='', favorite_color='black')
    >>> session_state.user_name
    'Mary'

    """
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_infos.values()

    for session_info in session_infos:
        if session_info.session._main_dg == ctx.main_dg:
            session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            "Are you doing something fancy with threads?")

    # Got the session object! Now let's attach some state into it.

    if not getattr(session, "_custom_session_state", None):
        session._custom_session_state = SessionState(**kwargs)

    return session._custom_session_state
def _get_widget_states():
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_infos.values()

    for session_info in session_infos:
        if session_info.session._main_dg == ctx.main_dg:
            session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')
    # Got the session object!

    return session._widget_states

    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_infos.values()

    for session_info in session_infos:
        if session_info.session._main_dg == ctx.main_dg:
            session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')
    # Got the session object!

    return session._widget_states
Exemple #21
0
def _get_widget_states():
    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_infos.values()

    for session_info in session_infos:
        session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            "Are you doing something fancy with threads?")

    return session._widget_states
Exemple #22
0
def _get_widget_states():
    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_infos.values()

    for session_info in session_infos:
        s = session_info.session
        if ((hasattr(s, '_main_dg') and s._main_dg == ctx.main_dg)
                or (not hasattr(s, '_main_dg') and s.enqueue == ctx.enqueue)):
            session = s

    if session is None:
        raise RuntimeError(
            "ERROR: while working with SessionState of streamlit")

    return session._widget_states
Exemple #23
0
    def get(**kwargs):
        ctx = ReportThread.get_report_ctx()

        session = None
        session_infos = Server.get_current()._session_infos.values()

        for session_info in session_infos:
            if session_info.session._main_dg == ctx.main_dg:
                session = session_info.session

        if session is None:
            raise RuntimeError(
                "Oh noes. Couldn't get your Streamlit Session object"
                'Are you doing something fancy with threads?')

        if not getattr(session, '_custom_session_state', None):
            session._custom_session_state = SessionState(**kwargs)

        return session._custom_session_state
Exemple #24
0
def get_state(setup_func: Callable[..., T], **kwargs) -> T:
    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        if session_info.session.enqueue == ctx.enqueue:
            session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            'Are you doing something fancy with threads?')

    # Got the session object! Now let's attach some state into it.

    if not getattr(session, '_custom_session_state', None):
        session._custom_session_state = setup_func(**kwargs)

    return session._custom_session_state
Exemple #25
0
def run(script_path, command_line, args):
    bootstrap._fix_sys_path(script_path)
    bootstrap._fix_matplotlib_crash()
    bootstrap._fix_tornado_crash()
    bootstrap._fix_sys_argv(script_path, args)
    bootstrap._fix_pydeck_mapbox_api_warning()

    bootstrap._set_up_signal_handler()

    ioloop = tornado.ioloop.IOLoop.current()

    server = Server(ioloop, script_path, command_line)

    server.start(_on_server_start)
    server.add_preheated_report_session()
    ioloop.start()
def get_session_id():
    # Hack to get the session object from Streamlit.

    ctx = ReportThread.get_report_ctx()

    session = None
    session_infos = Server.get_current()._session_info_by_id.values()

    for session_info in session_infos:
        s = session_info.session
        if ((hasattr(s, "_main_dg") and s._main_dg == ctx.main_dg)
                # Streamlit < 0.54.0
                or
                # Streamlit >= 0.54.0
            (not hasattr(s, "_main_dg") and s.enqueue == ctx.enqueue)):
            session = session_info.session

    if session is None:
        raise RuntimeError(
            "Oh noes. Couldn't get your Streamlit Session object"
            "Are you doing something fancy with threads?")

    return id(session)
Exemple #27
0
class ServerTestCase(tornado.testing.AsyncHTTPTestCase):
    """Base class for async streamlit.Server testing.

    Subclasses should patch 'streamlit.server.Server.ReportSession',
    to prevent ReportSessions from being created, and scripts from
    being run. (Script running involves creating new threads, which
    interfere with other tests if not properly terminated.)

    See the "ServerTest" class for example usage.
    """
    def get_app(self):
        # Create a Server, and patch its _on_stopped function
        # to no-op. This prevents it from shutting down the
        # ioloop when it stops.
        self.server = Server(self.io_loop, '/not/a/script.py', [])
        self.server._on_stopped = mock.MagicMock()
        app = self.server._create_app()
        return app

    def tearDown(self):
        super(ServerTestCase, self).tearDown()
        # Clear the Server singleton for the next test
        Server._singleton = None

    def start_server_loop(self):
        """Starts the server's loop coroutine.

        Returns
        -------
        Future
            A Future that resolves when the loop has started.
            You need to yield on this value from within a
            'tornado.testing.gen_test' coroutine.

        """
        server_started = Future()
        self.io_loop.spawn_callback(self.server._loop_coroutine,
                                    lambda _: server_started.set_result(None))
        return server_started

    def get_ws_url(self, path):
        """Return a ws:// URL with the given path for our test server."""
        # get_url() gives us a result with the 'http' scheme;
        # we swap it out for 'ws'.
        url = self.get_url(path)
        parts = list(requests.utils.urlparse(url))
        parts[0] = 'ws'
        return requests.utils.urlunparse(tuple(parts))

    def ws_connect(self):
        """Open a websocket connection to the server.

        Returns
        -------
        Future
            A Future that resolves with the connected websocket client.
            You need to yield on this value from within a
            'tornado.testing.gen_test' coroutine.

        """
        return tornado.websocket.websocket_connect(self.get_ws_url('/stream'))
Exemple #28
0
 def signal_handler(signal_number, stack_frame):
     # The server will shut down its threads and stop the ioloop
     Server.get_current().stop()
Exemple #29
0
class ServerTestCase(tornado.testing.AsyncHTTPTestCase):
    """Base class for async streamlit.Server testing.

    Subclasses should patch 'streamlit.server.Server.ReportSession',
    to prevent ReportSessions from being created, and scripts from
    being run. (Script running involves creating new threads, which
    interfere with other tests if not properly terminated.)

    See the "ServerTest" class for example usage.
    """
    def get_app(self):
        # Create a Server, and patch its _on_stopped function
        # to no-op. This prevents it from shutting down the
        # ioloop when it stops.
        self.server = Server(self.io_loop, "/not/a/script.py",
                             "test command line")
        self.server._on_stopped = mock.MagicMock()  # type: ignore[assignment]
        app = self.server._create_app()
        return app

    def tearDown(self):
        super(ServerTestCase, self).tearDown()
        # Clear the Server singleton for the next test
        Server._singleton = None

    def start_server_loop(self):
        """Starts the server's loop coroutine.

        Returns
        -------
        Future
            A Future that resolves when the loop has started.
            You need to yield on this value from within a
            'tornado.testing.gen_test' coroutine.

        """
        server_started = Future()  # type: ignore[var-annotated]
        self.io_loop.spawn_callback(self.server._loop_coroutine,
                                    lambda _: server_started.set_result(None))
        return server_started

    def get_ws_url(self, path):
        """Return a ws:// URL with the given path for our test server."""
        # get_url() gives us a result with the 'http' scheme;
        # we swap it out for 'ws'.
        url = self.get_url(path)
        parts = list(urllib.parse.urlparse(url))
        parts[0] = "ws"
        return urllib.parse.urlunparse(tuple(parts))

    def ws_connect(self):
        """Open a websocket connection to the server.

        Returns
        -------
        Future
            A Future that resolves with the connected websocket client.
            You need to yield on this value from within a
            'tornado.testing.gen_test' coroutine.

        """
        return tornado.websocket.websocket_connect(self.get_ws_url("/stream"))

    @tornado.gen.coroutine
    def read_forward_msg(self, ws_client):
        """Parse the next message from a Websocket client into a ForwardMsg."""
        data = yield ws_client.read_message()
        message = ForwardMsg()
        message.ParseFromString(data)
        raise gen.Return(message)