Exemplo n.º 1
0
def serialize_forward_msg(msg):
    """Serialize a ForwardMsg to send to a client.

    If the message is too large, it will be converted to an exception message
    instead.

    Parameters
    ----------
    msg : ForwardMsg
        The message to serialize

    Returns
    -------
    str
        The serialized byte string to send

    """
    populate_hash_if_needed(msg)
    msg_str = msg.SerializeToString()

    if len(msg_str) > MESSAGE_SIZE_LIMIT:
        import streamlit.elements.exception_proto as exception_proto

        error = RuntimeError(
            f"Data of size {len(msg_str)/1e6:.1f}MB exceeds write limit of {MESSAGE_SIZE_LIMIT/1e6}MB"
        )
        # Overwrite the offending ForwardMsg.delta with an error to display.
        # This assumes that the size limit wasn't exceeded due to metadata.
        exception_proto.marshall(msg.delta.new_element.exception, error)
        msg_str = msg.SerializeToString()

    return msg_str
Exemplo n.º 2
0
def _convert_msg_to_exception_msg(msg, e):
    import streamlit.elements.exception_proto as exception_proto

    delta_id = msg.metadata.delta_id
    msg.Clear()
    msg.metadata.delta_id = delta_id

    exception_proto.marshall(msg.delta.new_element.exception, e)
Exemplo n.º 3
0
    def _on_script_compile_error(self, exc):
        """Handles exceptions caught by ScriptRunner during script compilation.

        We deliver these exceptions to the client via SessionEvent messages.
        "Normal" exceptions that are thrown during script execution show up as
        inline elements in the report, but compilation exceptions are handled
        specially, so that the frontend can leave the previous report up.
        """
        from streamlit.elements import exception_proto
        msg = protobuf.ForwardMsg()
        exception_proto.marshall(
            msg.session_event.script_compilation_exception, exc)
        self.enqueue(msg)
Exemplo n.º 4
0
    def test_unhashable_type(self):
        @st.cache
        def unhashable_type_func():
            return threading.Lock()

        with self.assertRaises(hashing.UnhashableTypeError) as cm:
            unhashable_type_func()

        ep = ExceptionProto()
        exception_proto.marshall(ep, cm.exception)

        self.assertEqual(ep.type, "UnhashableTypeError")

        self.assertTrue(
            normalize_md(ep.message).startswith(
                normalize_md("""
Cannot hash object of type `_thread.lock`, found in the return value of
`unhashable_type_func()`.

While caching the return value of `unhashable_type_func()`, Streamlit
encountered an object of type `_thread.lock`, which it does not know how to
hash.

To address this, please try helping Streamlit understand how to hash that type
by passing the `hash_funcs` argument into `@st.cache`. For example:

```
@st.cache(hash_funcs={_thread.lock: my_hash_func})
def my_func(...):
    ...
```

If you don't know where the object of type `_thread.lock` is coming
from, try looking at the hash chain below for an object that you do recognize,
then pass that to `hash_funcs` instead:

```
Object of type _thread.lock:
                    """)))

        # Stack trace doesn't show in test :(
        # self.assertNotEqual(len(ep.stack_trace), 0)
        self.assertEqual(ep.message_is_markdown, True)
        self.assertEqual(ep.is_warning, False)
Exemplo n.º 5
0
    def test_user_hash_error(self):
        class MyObj(object):
            pass

        def bad_hash_func(x):
            x += 10  # Throws a TypeError since x has type MyObj.
            return x

        @st.cache(hash_funcs={MyObj: bad_hash_func})
        def user_hash_error_func(x):
            pass

        with self.assertRaises(hashing.UserHashError) as cm:
            my_obj = MyObj()
            user_hash_error_func(my_obj)

        ep = ExceptionProto()
        exception_proto.marshall(ep, cm.exception)

        self.assertEqual(ep.type, "TypeError")
        self.assertTrue(
            normalize_md(ep.message).startswith(
                normalize_md("""
unsupported operand type(s) for +=: 'MyObj' and 'int'

This error is likely due to a bug in `bad_hash_func()`, which is a
user-defined hash function that was passed into the `@st.cache` decorator of
`user_hash_error_func()`.

`bad_hash_func()` failed when hashing an object of type
`caching_test.CacheErrorsTest.test_user_hash_error.<locals>.MyObj`.  If you
don't know where that object is coming from, try looking at the hash chain
below for an object that you do recognize, then pass that to `hash_funcs` instead:

```
Object of type caching_test.CacheErrorsTest.test_user_hash_error.<locals>.MyObj:
<caching_test.CacheErrorsTest.test_user_hash_error.<locals>.MyObj object at
        """)))

        # Stack trace doesn't show in test :(
        # self.assertNotEqual(len(ep.stack_trace), 0)
        self.assertEqual(ep.message_is_markdown, True)
        self.assertEqual(ep.is_warning, False)
Exemplo n.º 6
0
    def enqueue_exception(self, e):
        """Enqueue an Exception message.

        Parameters
        ----------
        e : BaseException

        """
        # This does a few things:
        # 1) Clears the current report in the browser.
        # 2) Marks the current report as "stopped" in the browser.
        # 3) HACK: Resets any script params that may have been broken (e.g. the
        # command-line when rerunning with wrong argv[0])
        self._on_scriptrunner_event(ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS)
        self._on_scriptrunner_event(ScriptRunnerEvent.SCRIPT_STARTED)
        self._on_scriptrunner_event(ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS)

        msg = ForwardMsg()
        exception_proto.marshall(msg.delta.new_element.exception, e)

        self.enqueue(msg)
Exemplo n.º 7
0
    def enqueue_exception(self, e):
        """Enqueues an Exception message.

        Parameters
        ----------
        e : BaseException

        """
        # This does a few things:
        # 1) Clears the current report in the browser.
        # 2) Marks the current report as "stopped" in the browser.
        # 3) HACK: Resets any script params that may have been broken (e.g. the
        # command-line when rerunning with wrong argv[0])
        self._enqueue_script_state_changed_message(ScriptState.STOPPED)
        self._enqueue_script_state_changed_message(ScriptState.RUNNING)
        self._enqueue_script_state_changed_message(ScriptState.STOPPED)

        msg = protobuf.ForwardMsg()
        msg.delta.id = 0
        exception_proto.marshall(msg.delta.new_element.exception, e)

        self.enqueue(msg)
Exemplo n.º 8
0
    def test_strip_streamlit_stack_entries(self):
        """Test that StreamlitAPIExceptions don't include Streamlit entries
        in the stack trace.

        """
        # Create a StreamlitAPIException.
        err = None
        try:
            st.image("http://not_an_image.png", width=-1)
        except StreamlitAPIException as e:
            err = e
        self.assertIsNotNone(err)

        # Marshall it.
        proto = ExceptionProto()
        exception_proto.marshall(proto, err)

        # The streamlit package should not appear in any stack entry.
        streamlit_dir = os.path.dirname(st.__file__)
        streamlit_dir = os.path.join(os.path.realpath(streamlit_dir), "")
        for line in proto.stack_trace:
            self.assertNotIn(streamlit_dir, line, "Streamlit stack entry not stripped")
Exemplo n.º 9
0
    def test_markdown_flag(self):
        """Test that ExceptionProtos for StreamlitAPIExceptions (and
        subclasses) have the "message_is_markdown" flag set.
        """
        proto = ExceptionProto()
        exception_proto.marshall(proto, RuntimeError("oh no!"))
        self.assertFalse(proto.message_is_markdown)

        proto = ExceptionProto()
        exception_proto.marshall(proto, StreamlitAPIException("oh no!"))
        self.assertTrue(proto.message_is_markdown)

        proto = ExceptionProto()
        exception_proto.marshall(proto, errors.DuplicateWidgetID("oh no!"))
        self.assertTrue(proto.message_is_markdown)
Exemplo n.º 10
0
    def _on_scriptrunner_event(self, event, exception=None, client_state=None):
        """Called when our ScriptRunner emits an event.

        This is *not* called on the main thread.

        Parameters
        ----------
        event : ScriptRunnerEvent

        exception : BaseException | None
            An exception thrown during compilation. Set only for the
            SCRIPT_STOPPED_WITH_COMPILE_ERROR event.

        client_state : streamlit.proto.ClientState_pb2.ClientState | None
            The ScriptRunner's final ClientState. Set only for the
            SHUTDOWN event.

        """
        LOGGER.debug("OnScriptRunnerEvent: %s", event)

        prev_state = self._state

        if event == ScriptRunnerEvent.SCRIPT_STARTED:
            if self._state != ReportSessionState.SHUTDOWN_REQUESTED:
                self._state = ReportSessionState.REPORT_IS_RUNNING

            if config.get_option("server.liveSave"):
                # Enqueue into the IOLoop so it runs without blocking AND runs
                # on the main thread.
                self._ioloop.spawn_callback(self._save_running_report)

            self._clear_queue()
            self._maybe_enqueue_initialize_message()
            self._enqueue_new_report_message()

        elif (event == ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS
              or event == ScriptRunnerEvent.SCRIPT_STOPPED_WITH_COMPILE_ERROR):

            if self._state != ReportSessionState.SHUTDOWN_REQUESTED:
                self._state = ReportSessionState.REPORT_NOT_RUNNING

            script_succeeded = event == ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS

            self._enqueue_report_finished_message(
                ForwardMsg.FINISHED_SUCCESSFULLY if script_succeeded else
                ForwardMsg.FINISHED_WITH_COMPILE_ERROR)

            if config.get_option("server.liveSave"):
                # Enqueue into the IOLoop so it runs without blocking AND runs
                # on the main thread.
                self._ioloop.spawn_callback(self._save_final_report_and_quit)

            if script_succeeded:
                # When a script completes successfully, we update our
                # LocalSourcesWatcher to account for any source code changes
                # that change which modules should be watched. (This is run on
                # the main thread, because LocalSourcesWatcher is not
                # thread safe.)
                self._ioloop.spawn_callback(
                    self._local_sources_watcher.update_watched_modules)
            else:
                # When a script fails to compile, we send along the exception.
                from streamlit.elements import exception_proto

                msg = ForwardMsg()
                exception_proto.marshall(
                    msg.session_event.script_compilation_exception, exception)
                self.enqueue(msg)

        elif event == ScriptRunnerEvent.SHUTDOWN:
            # When ScriptRunner shuts down, update our local reference to it,
            # and check to see if we need to spawn a new one. (This is run on
            # the main thread.)

            if self._state == ReportSessionState.SHUTDOWN_REQUESTED:
                # Only clear media files if the script is done running AND the
                # report session is actually shutting down.
                media_file_manager.clear_session_files(self.id)

            def on_shutdown():
                self._client_state = client_state
                self._scriptrunner = None
                # Because a new ScriptEvent could have been enqueued while the
                # scriptrunner was shutting down, we check to see if we should
                # create a new one. (Otherwise, a newly-enqueued ScriptEvent
                # won't be processed until another event is enqueued.)
                self._maybe_create_scriptrunner()

            self._ioloop.spawn_callback(on_shutdown)

        # Send a message if our run state changed
        report_was_running = prev_state == ReportSessionState.REPORT_IS_RUNNING
        report_is_running = self._state == ReportSessionState.REPORT_IS_RUNNING
        if report_is_running != report_was_running:
            self._enqueue_session_state_changed_message()