Ejemplo n.º 1
0
def _read_from_cache(mem_cache,
                     key,
                     persist,
                     allow_output_mutation,
                     func_or_code,
                     hash_funcs=None):
    """Read a value from the cache.

    Our goal is to read from memory if possible. If the data was mutated (hash
    changed), we show a warning. If reading from memory fails, we either read
    from disk or rerun the code.
    """
    try:
        return _read_from_mem_cache(mem_cache, key, allow_output_mutation,
                                    func_or_code, hash_funcs)

    except CachedObjectMutationError as e:
        handle_uncaught_app_exception(CachedObjectMutationWarning(e))
        return e.cached_value

    except CacheKeyNotFoundError as e:
        if persist:
            value = _read_from_disk_cache(key)
            _write_to_mem_cache(mem_cache, key, value, allow_output_mutation,
                                func_or_code, hash_funcs)
            return value
        raise e
Ejemplo n.º 2
0
    def test_handle_uncaught_app_exception_with_rich(self):
        """Test if the exception is logged with rich enabled and disabled."""
        exc = Exception("boom!")
        with testutil.patch_config_options({"logger.enableRich": True}):
            with io.StringIO() as buf:
                # Capture stdout logs (rich logs to stdout)
                with contextlib.redirect_stdout(buf):
                    handle_uncaught_app_exception(exc)
                # Capture the stdout output
                captured_output = buf.getvalue()

                assert "Exception:" in captured_output
                assert "boom!" in captured_output
                # Uncaught app exception is only used by the non-rich exception logging
                assert "Uncaught app exception" not in captured_output
        with testutil.patch_config_options({"logger.enableRich": False}):
            with io.StringIO() as buf:
                # Capture stdout logs
                with contextlib.redirect_stdout(buf):
                    handle_uncaught_app_exception(exc)
                # Capture the stdout output
                captured_output = buf.getvalue()

                # With rich deactivated, the exception is not logged to stdout
                assert "Exception:" not in captured_output
                assert "boom!" not in captured_output
Ejemplo n.º 3
0
    def test_uncaught_exception_no_details(self, mock_st_error,
                                           mock_st_exception):
        """If client.showErrorDetails is false, uncaught app errors are logged,
        and a generic error message is printed to the frontend."""
        with testutil.patch_config_options({"client.showErrorDetails": False}):
            exc = RuntimeError("boom!")
            handle_uncaught_app_exception(exc)

            mock_st_error.assert_not_called()
            mock_st_exception.assert_called_once()
Ejemplo n.º 4
0
    def test_uncaught_exception_show_details(self, mock_st_error,
                                             mock_st_exception):
        """If client.showErrorDetails is true, uncaught app errors print
        to the frontend."""
        with testutil.patch_config_options({"client.showErrorDetails": True}):
            exc = RuntimeError("boom!")
            handle_uncaught_app_exception(exc)

            mock_st_error.assert_not_called()
            mock_st_exception.assert_called_once_with(exc)
Ejemplo n.º 5
0
    def test_uncaught_exception_no_tracebacks(self, mock_st_error,
                                              mock_st_exception):
        """If client.showTracebacks is false, uncaught app errors are logged,
        and a generic error message is printed to the frontend."""
        with testutil.patch_config_options({"client.showTracebacks": False}):
            exc = RuntimeError("boom!")
            handle_uncaught_app_exception(exc)

            mock_st_exception.assert_not_called()
            mock_st_error.assert_called_once_with(
                _GENERIC_UNCAUGHT_EXCEPTION_TEXT)
Ejemplo n.º 6
0
    def _run_script(self, rerun_data):
        """Run our script.

        Parameters
        ----------
        rerun_data: RerunData
            The RerunData to use.

        """
        assert self._is_in_script_thread()

        LOGGER.debug("Running script %s", rerun_data)

        # Reset DeltaGenerators, widgets, media files.
        media_file_manager.clear_session_files()

        ctx = get_report_ctx()
        if ctx is None:
            # This should never be possible on the script_runner thread.
            raise RuntimeError(
                "ScriptRunner thread has a null ReportContext. Something has gone very wrong!"
            )

        ctx.reset(query_string=rerun_data.query_string)

        self.on_event.send(ScriptRunnerEvent.SCRIPT_STARTED)

        # Compile the script. Any errors thrown here will be surfaced
        # to the user via a modal dialog in the frontend, and won't result
        # in their previous report disappearing.

        try:
            with source_util.open_python_file(self._report.script_path) as f:
                filebody = f.read()

            if config.get_option("runner.magicEnabled"):
                filebody = magic.add_magic(filebody, self._report.script_path)

            code = compile(
                filebody,
                # Pass in the file path so it can show up in exceptions.
                self._report.script_path,
                # We're compiling entire blocks of Python, so we need "exec"
                # mode (as opposed to "eval" or "single").
                mode="exec",
                # Don't inherit any flags or "future" statements.
                flags=0,
                dont_inherit=1,
                # Use the default optimization options.
                optimize=-1,
            )

        except BaseException as e:
            # We got a compile error. Send an error event and bail immediately.
            LOGGER.debug("Fatal script error: %s" % e)
            self.on_event.send(
                ScriptRunnerEvent.SCRIPT_STOPPED_WITH_COMPILE_ERROR,
                exception=e)
            return

        # If we get here, we've successfully compiled our script. The next step
        # is to run it. Errors thrown during execution will be shown to the
        # user as ExceptionElements.

        # Update the Widget object with the new widget_states.
        # (The ReportContext has a reference to this object, so we just update it in-place)
        if rerun_data.widget_states is not None:
            self._widgets.set_state(rerun_data.widget_states)

        if config.get_option("runner.installTracer"):
            self._install_tracer()

        # This will be set to a RerunData instance if our execution
        # is interrupted by a RerunException.
        rerun_with_data = None

        try:
            # Create fake module. This gives us a name global namespace to
            # execute the code in.
            module = _new_module("__main__")

            # Install the fake module as the __main__ module. This allows
            # the pickle module to work inside the user's code, since it now
            # can know the module where the pickled objects stem from.
            # IMPORTANT: This means we can't use "if __name__ == '__main__'" in
            # our code, as it will point to the wrong module!!!
            sys.modules["__main__"] = module

            # Add special variables to the module's globals dict.
            # Note: The following is a requirement for the CodeHasher to
            # work correctly. The CodeHasher is scoped to
            # files contained in the directory of __main__.__file__, which we
            # assume is the main script directory.
            module.__dict__["__file__"] = self._report.script_path

            with modified_sys_path(self._report), self._set_execing_flag():
                exec(code, module.__dict__)

        except RerunException as e:
            rerun_with_data = e.rerun_data

        except StopException:
            pass

        except BaseException as e:
            handle_uncaught_app_exception(e)

        finally:
            self._widgets.reset_triggers()
            self._widgets.cull_nonexistent(ctx.widget_ids_this_run.items())
            self.on_event.send(ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS)
            # delete expired files now that the script has run and files in use
            # are marked as active
            media_file_manager.del_expired_files()

        # Use _log_if_error() to make sure we never ever ever stop running the
        # script without meaning to.
        _log_if_error(_clean_problem_modules)

        if rerun_with_data is not None:
            self._run_script(rerun_with_data)
Ejemplo n.º 7
0
    def _run_script(self, rerun_data: RerunData) -> None:
        """Run our script.

        Parameters
        ----------
        rerun_data: RerunData
            The RerunData to use.

        """
        assert self._is_in_script_thread()

        LOGGER.debug("Running script %s", rerun_data)

        # Reset DeltaGenerators, widgets, media files.
        in_memory_file_manager.clear_session_files()

        ctx = self._get_script_run_ctx()
        ctx.reset(query_string=rerun_data.query_string)

        self.on_event.send(ScriptRunnerEvent.SCRIPT_STARTED)

        # Compile the script. Any errors thrown here will be surfaced
        # to the user via a modal dialog in the frontend, and won't result
        # in their previous script elements disappearing.

        try:
            with source_util.open_python_file(
                    self._session_data.script_path) as f:
                filebody = f.read()

            if config.get_option("runner.magicEnabled"):
                filebody = magic.add_magic(filebody,
                                           self._session_data.script_path)

            code = compile(
                filebody,
                # Pass in the file path so it can show up in exceptions.
                self._session_data.script_path,
                # We're compiling entire blocks of Python, so we need "exec"
                # mode (as opposed to "eval" or "single").
                mode="exec",
                # Don't inherit any flags or "future" statements.
                flags=0,
                dont_inherit=1,
                # Use the default optimization options.
                optimize=-1,
            )

        except BaseException as e:
            # We got a compile error. Send an error event and bail immediately.
            LOGGER.debug("Fatal script error: %s" % e)
            self._session_state[SCRIPT_RUN_WITHOUT_ERRORS_KEY] = False
            self.on_event.send(
                ScriptRunnerEvent.SCRIPT_STOPPED_WITH_COMPILE_ERROR,
                exception=e)
            return

        # If we get here, we've successfully compiled our script. The next step
        # is to run it. Errors thrown during execution will be shown to the
        # user as ExceptionElements.

        if config.get_option("runner.installTracer"):
            self._install_tracer()

        # This will be set to a RerunData instance if our execution
        # is interrupted by a RerunException.
        rerun_with_data = None

        try:
            # Create fake module. This gives us a name global namespace to
            # execute the code in.
            module = _new_module("__main__")

            # Install the fake module as the __main__ module. This allows
            # the pickle module to work inside the user's code, since it now
            # can know the module where the pickled objects stem from.
            # IMPORTANT: This means we can't use "if __name__ == '__main__'" in
            # our code, as it will point to the wrong module!!!
            sys.modules["__main__"] = module

            # Add special variables to the module's globals dict.
            # Note: The following is a requirement for the CodeHasher to
            # work correctly. The CodeHasher is scoped to
            # files contained in the directory of __main__.__file__, which we
            # assume is the main script directory.
            module.__dict__["__file__"] = self._session_data.script_path

            with modified_sys_path(
                    self._session_data), self._set_execing_flag():
                # Run callbacks for widgets whose values have changed.
                if rerun_data.widget_states is not None:
                    # Update the WidgetManager with the new widget_states.
                    # The old states, used to skip callbacks if values
                    # haven't changed, are also preserved in the
                    # WidgetManager.
                    self._session_state.compact_state()
                    self._session_state.set_widgets_from_proto(
                        rerun_data.widget_states)

                    self._session_state.call_callbacks()

                ctx.on_script_start()
                exec(code, module.__dict__)
                self._session_state[SCRIPT_RUN_WITHOUT_ERRORS_KEY] = True
        except RerunException as e:
            rerun_with_data = e.rerun_data

        except StopException:
            pass

        except BaseException as e:
            self._session_state[SCRIPT_RUN_WITHOUT_ERRORS_KEY] = False
            handle_uncaught_app_exception(e)

        finally:
            self._on_script_finished(ctx)

        # Use _log_if_error() to make sure we never ever ever stop running the
        # script without meaning to.
        _log_if_error(_clean_problem_modules)

        if rerun_with_data is not None:
            self._run_script(rerun_with_data)