def _handle_toplevel_response(self, msg: ToplevelResponse) -> None: if msg.get("error"): self._insert_text_directly(msg["error"] + "\n", ("toplevel", "stderr")) self._ensure_visible() if "user_exception" in msg: self._show_user_exception(msg["user_exception"]) self._ensure_visible() welcome_text = msg.get("welcome_text") if welcome_text and welcome_text != self._last_welcome_text: self._insert_text_directly(welcome_text, ("comment", )) self._last_welcome_text = welcome_text if "value_info" in msg: num_stripped_question_marks = getattr( msg, "num_stripped_question_marks", 0) if num_stripped_question_marks > 0: # show the value in object inspector get_workbench().event_generate("ObjectSelect", object_id=msg["value_info"].id) else: # show the value in shell value_repr = shorten_repr(msg["value_info"].repr, 10000) if value_repr != "None": if get_workbench().in_heap_mode(): value_repr = memory.format_object_id( msg["value_info"].id) object_tag = "object_" + str(msg["value_info"].id) self._insert_text_directly( value_repr + "\n", ("toplevel", "value", object_tag)) if running_on_mac_os(): sequence = "<Command-Button-1>" else: sequence = "<Control-Button-1>" self.tag_bind( object_tag, sequence, lambda _: get_workbench().event_generate( "ObjectSelect", object_id=msg["value_info"].id), ) self.active_object_tags.add(object_tag) self.mark_set("output_end", self.index("end-1c")) self._discard_old_content() self._update_visible_io(None) self._reset_ansi_attributes() self._io_cursor_offset = 0 self._insert_prompt() self._try_submit_input( ) # Trying to submit leftover code (eg. second magic command) self.see("end")
def handle_toplevel_response(self, msg: ToplevelResponse) -> None: # Can be called by event system or by Workbench # (if Assistant wasn't created yet but an error came) if not msg.get("user_exception") and msg.get("command_name") in [ "execute_system_command", "execute_source", ]: # Shell commands may be used to investigate the problem, don't clear assistance return self._clear() from thonny.plugins.cpython import CPythonProxy if not isinstance(get_runner().get_backend_proxy(), CPythonProxy): # TODO: add some support for MicroPython as well return # prepare for snapshot key = msg.get("filename", "<pyshell>") self._current_snapshot = { "timestamp": datetime.datetime.now().isoformat()[:19], "main_file_path": key, } self._snapshots_per_main_file.setdefault(key, []) self._snapshots_per_main_file[key].append(self._current_snapshot) if msg.get("user_exception"): if not msg["user_exception"].get("message", None): msg["user_exception"]["message"] = "<no message>" self._exception_info = msg["user_exception"] self._explain_exception(msg["user_exception"]) if get_workbench().get_option( "assistance.open_assistant_on_errors"): get_workbench().show_view("AssistantView", set_focus=False) else: self._exception_info = None if msg.get("filename") and os.path.exists(msg["filename"]): self.main_file_path = msg["filename"] source = read_source(msg["filename"]) self._start_program_analyses( msg["filename"], source, _get_imported_user_files(msg["filename"], source)) else: self.main_file_path = None self._present_conclusion()
def _execute_file(self, cmd, executor_class): self._check_update_tty_mode(cmd) if len(cmd.args) >= 1: sys.argv = cmd.args filename = cmd.args[0] if os.path.isabs(filename): full_filename = filename else: full_filename = os.path.abspath(filename) with tokenize.open(full_filename) as fp: source = fp.read() for preproc in self._source_preprocessors: source = preproc(source, cmd) result_attributes = self._execute_source(source, full_filename, "exec", executor_class, cmd, self._ast_postprocessors) result_attributes["filename"] = full_filename return ToplevelResponse(command_name=cmd.name, **result_attributes) else: raise UserError("Command '%s' takes at least one argument" % cmd.name)
def _send_ready_message(self): args = dict(cwd=self._cwd) # if not clean, then welcome text is already printed as output from the last session if not self._welcome_text_printed: args["welcome_text"] = self._welcome_text self.send_message(ToplevelResponse(**args))
def mainloop(self): while True: try: cmd = self._fetch_command() if isinstance(cmd, InputSubmission): raise AssertionError("InputSubmission not supported") elif isinstance(cmd, EOFCommand): self.send_message(ToplevelResponse(SystemExit=True)) sys.exit() else: self.handle_command(cmd) except KeyboardInterrupt: # Interrupt must always result in waiting_toplevel_command state # Don't show error messages, as the interrupted command may have been InlineCommand # (handlers of ToplevelCommands in normal cases catch the interrupt and provide # relevant message) self.send_message(ToplevelResponse())
def create_error_response(**kw): if "error" not in kw: kw["error"] = traceback.format_exc() if isinstance(cmd, ToplevelCommand): return ToplevelResponse(command_name=cmd.name, **kw) else: return InlineResponse(command_name=cmd.name, **kw)
def _cmd_Reset(self, cmd): if len(cmd.args) == 0: # nothing to do, because Reset always happens in fresh process return ToplevelResponse( command_name="Reset", welcome_text="Python " + _get_python_version_string(), executable=sys.executable, ) else: raise UserError("Command 'Reset' doesn't take arguments")
def _soft_reboot(self): # Need to go to normal mode. MP doesn't run user code in raw mode # (CP does, but it doesn't hurt to do it there as well) self._connection.write(NORMAL_MODE_CMD) self._raw_prompt_ensured = False self._connection.read_until(NORMAL_PROMPT) self._connection.write(SOFT_REBOOT_CMD) self._forward_output_until_active_prompt(self._send_output) self._ensure_raw_prompt() self.send_message(ToplevelResponse(cwd=self._cwd))
def _cmd_cd(self, cmd): if len(cmd.args) == 1: path = cmd.args[0] try: os.chdir(path) return ToplevelResponse() except FileNotFoundError: raise UserError("No such folder: " + path) except OSError as e: raise UserError(str(e)) else: raise UserError("cd takes one parameter")
def _soft_reboot_for_restarting_user_program(self): # Need to go to normal mode. MP doesn't run user code in raw mode # (CP does, but it doesn't hurt to do it there as well) self._write(NORMAL_MODE_CMD) self._connection.read_until(NORMAL_PROMPT) self._write(SOFT_REBOOT_CMD) self._check_reconnect() self._forward_output_until_active_prompt(self._send_output) logger.debug("Restoring helpers") self._prepare_helpers() self._update_cwd() self.send_message(ToplevelResponse(cwd=self._cwd))
def _cmd_cd(self, cmd): if len(cmd.args) == 1: path = cmd.args[0] try: os.chdir(path) return ToplevelResponse() except FileNotFoundError: raise UserError("No such folder: " + path) except OSError as e: raise UserError("\n".join( traceback.format_exception_only(type(e), e))) else: raise UserError("cd takes one parameter")
def _cmd_execute_source(self, cmd): """Executes Python source entered into shell""" self._check_update_tty_mode(cmd) filename = "<pyshell>" ws_stripped_source = cmd.source.strip() source = ws_stripped_source.strip("?") num_stripped_question_marks = len(ws_stripped_source) - len(source) # let's see if it's single expression or something more complex try: root = ast.parse(source, filename=filename, mode="exec") except SyntaxError as e: error = "".join(traceback.format_exception_only(type(e), e)) sys.stderr.write(error) return ToplevelResponse() assert isinstance(root, ast.Module) if len(root.body) == 1 and isinstance(root.body[0], ast.Expr): mode = "eval" elif len(root.body) > 1 and isinstance(root.body[-1], ast.Expr): mode = "exec+eval" else: mode = "exec" result_attributes = self._execute_source( source, filename, mode, NiceTracer if getattr(cmd, "debug_mode", False) else SimpleRunner, cmd, ) result_attributes[ "num_stripped_question_marks"] = num_stripped_question_marks return ToplevelResponse(command_name="execute_source", **result_attributes)
def _cmd_get_environment_info(self, cmd): return ToplevelResponse( main_dir=self._main_dir, path=sys.path, usersitepackages=None, prefix=None, welcome_text="MicroPython " + _get_python_version_string(), executable=sys.executable, exe_dirs=[], in_venv=False, python_version=_get_python_version_string(), cwd=os.getcwd(), )
def _soft_reboot(self, side_command): if side_command: self._interrupt_to_raw_prompt() # Need to go to normal mode. MP doesn't run user code in raw mode # (CP does, but it doesn't hurt to do it there as well) self._connection.write(NORMAL_MODE_CMD) self._connection.read_until(NORMAL_PROMPT) self._connection.write(SOFT_REBOOT_CMD) if not side_command: self._process_until_raw_prompt() self.send_message(ToplevelResponse(cwd=self._cwd))
def _cmd_get_environment_info(self, cmd): return ToplevelResponse( main_dir=self._main_dir, sys_path=sys.path, usersitepackages=site.getusersitepackages() if site.ENABLE_USER_SITE else None, prefix=sys.prefix, welcome_text="Python " + get_python_version_string(), executable=sys.executable, exe_dirs=get_exe_dirs(), in_venv=(hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix or hasattr(sys, "real_prefix") and getattr(sys, "real_prefix") != sys.prefix), python_version=get_python_version_string(), cwd=os.getcwd(), )
def handle_command(self, cmd): assert isinstance(cmd, (ToplevelCommand, InlineCommand)) def create_error_response(**kw): if isinstance(cmd, ToplevelCommand): return ToplevelResponse(command_name=cmd.name, **kw) else: return InlineResponse(command_name=cmd.name, **kw) handler = getattr(self, "_cmd_" + cmd.name, None) if handler is None: response = create_error_response(error="Unknown command: " + cmd.name) else: try: response = handler(cmd) except SystemExit: # Must be caused by Thonny or plugins code if isinstance(cmd, ToplevelCommand): traceback.print_exc() response = create_error_response(SystemExit=True) except UserError as e: sys.stderr.write(str(e) + "\n") response = create_error_response() except KeyboardInterrupt: response = create_error_response( user_exception=self._prepare_user_exception()) except Exception: _report_internal_error() response = create_error_response( context_info="other unhandled exception") if response is False: # Command doesn't want to send any response return elif isinstance(response, dict): if isinstance(cmd, ToplevelCommand): response = ToplevelResponse(command_name=cmd.name, **response) elif isinstance(cmd, InlineCommand): response = InlineResponse(cmd.name, **response) debug("cmd: " + str(cmd) + ", respin: " + str(response)) self.send_message(response)
def _prepare_command_response( self, response: Union[MessageFromBackend, Dict, None], command: CommandToBackend) -> MessageFromBackend: if "id" in command and "command_id" not in response: response["command_id"] = command["id"] if isinstance(response, MessageFromBackend): return response else: if isinstance(response, dict): args = response else: args = {} if isinstance(command, ToplevelCommand): return ToplevelResponse(command_name=command.name, **args) else: assert isinstance(command, InlineCommand) return InlineResponse(command_name=command.name, **args)
def mainloop(self): try: while self._should_keep_going(): try: try: msg = self._incoming_message_queue.get(block=True, timeout=0.01) except queue.Empty: self._perform_idle_tasks() else: if isinstance(msg, InputSubmission): self._handle_user_input(msg) elif isinstance(msg, EOFCommand): self._handle_eof_command(msg) else: self._handle_normal_command(msg) except KeyboardInterrupt: self._send_output("KeyboardInterrupt", "stderr") # CPython idle REPL does this self.send_message(ToplevelResponse()) except ConnectionClosedException: sys.exit(0)
def _forward_unexpected_output(self, stream_name="stdout"): "Invoked between commands" data = self._connection.read_all(check_error=False) if data: met_prompt = False while data.endswith(NORMAL_PROMPT) or data.endswith(FIRST_RAW_PROMPT): # looks like the device was resetted met_prompt = True if data.endswith(NORMAL_PROMPT): terminator = NORMAL_PROMPT else: terminator = FIRST_RAW_PROMPT # hide the prompt from the output ... data = data[: -len(terminator)] self._send_output(data.decode(ENCODING, "replace"), stream_name) if met_prompt: # ... and recreate Thonny prompt self.send_message(ToplevelResponse())
def mainloop(self): # Don't use threading for creating a management thread, because I don't want them # to be affected by threading.settrace _thread.start_new_thread(self._read_incoming_messages, ()) while self._should_keep_going(): try: try: msg = self._incoming_message_queue.get(block=True, timeout=0.01) except queue.Empty: self._perform_idle_tasks() else: if isinstance(msg, InputSubmission): self._handle_user_input(msg) elif isinstance(msg, EOFCommand): self._handle_eof_command(msg) else: self._handle_normal_command(msg) except KeyboardInterrupt: self._send_output("KeyboardInterrupt", "stderr") # CPython idle REPL does this self.send_message(ToplevelResponse())
def _handle_eof_command(self, msg: EOFCommand) -> None: self.send_message(ToplevelResponse(SystemExit=True)) sys.exit()
def handle_command(self, cmd): self._report_time("before " + cmd.name) assert isinstance(cmd, (ToplevelCommand, InlineCommand)) if "local_cwd" in cmd: self._local_cwd = cmd["local_cwd"] def create_error_response(**kw): if "error" not in kw: kw["error"] = traceback.format_exc() if isinstance(cmd, ToplevelCommand): return ToplevelResponse(command_name=cmd.name, **kw) else: return InlineResponse(command_name=cmd.name, **kw) handler = getattr(self, "_cmd_" + cmd.name, None) if handler is None: response = create_error_response(error="Unknown command: " + cmd.name) else: try: response = handler(cmd) except SystemExit: # Must be caused by Thonny or plugins code if isinstance(cmd, ToplevelCommand): traceback.print_exc() response = create_error_response(SystemExit=True) except UserError as e: sys.stderr.write(str(e) + "\n") response = create_error_response() except KeyboardInterrupt: response = create_error_response(error="Interrupted", interrupted=True) except ProtocolError as e: self._send_output( "THONNY FAILED TO EXECUTE %s (%s)\n" % (cmd.name, e.message), "stderr") self._send_output("CAPTURED DATA: %r\n" % e.captured, "stderr") self._send_output("TRYING TO RECOVER ...\n", "stderr") # TODO: detect when there is no output for long time and suggest interrupt self._forward_output_until_active_prompt("stdout") response = create_error_response(error=e.message) except Exception: _report_internal_error() response = create_error_response( context_info="other unhandled exception") if response is None: response = {} if response is False: # Command doesn't want to send any response return elif isinstance(response, dict): if isinstance(cmd, ToplevelCommand): response = ToplevelResponse(command_name=cmd.name, **response) elif isinstance(cmd, InlineCommand): response = InlineResponse(cmd.name, **response) if "id" in cmd and "command_id" not in response: response["command_id"] = cmd["id"] debug("cmd: " + str(cmd) + ", respin: " + str(response)) self.send_message(response) self._report_time("after " + cmd.name)
def _send_ready_message(self): self.send_message( ToplevelResponse(welcome_text=self._welcome_text, cwd=self._cwd))
def _handle_normal_command(self, cmd: CommandToBackend) -> None: self._report_time("before " + cmd.name) assert isinstance(cmd, (ToplevelCommand, InlineCommand)) if "local_cwd" in cmd: self._local_cwd = cmd["local_cwd"] def create_error_response(**kw): if "error" not in kw: kw["error"] = traceback.format_exc() if isinstance(cmd, ToplevelCommand): return ToplevelResponse(command_name=cmd.name, **kw) else: return InlineResponse(command_name=cmd.name, **kw) handler = getattr(self, "_cmd_" + cmd.name, None) if handler is None: response = create_error_response(error="Unknown command: " + cmd.name) else: try: response = handler(cmd) except SystemExit: # Must be caused by Thonny or plugins code if isinstance(cmd, ToplevelCommand): traceback.print_exc() response = create_error_response(SystemExit=True) except UserError as e: sys.stderr.write(str(e) + "\n") response = create_error_response() except KeyboardInterrupt: response = create_error_response(error="Interrupted", interrupted=True) except ConnectionClosedException as e: self._on_connection_closed(e) except ManagementError as e: if "KeyboardInterrupt" in e.err: response = create_error_response(error="Interrupted", interrupted=True) else: self._send_output( "THONNY FAILED TO EXECUTE COMMAND %s\n" % cmd.name, "stderr") # traceback.print_exc() # I'll know the trace from command self._show_error("\n") self._show_error("SCRIPT:\n" + e.script + "\n") self._show_error("STDOUT:\n" + e.out + "\n") self._show_error("STDERR:\n" + e.err + "\n") response = create_error_response(error="ManagementError") except Exception: _report_internal_error() response = create_error_response( context_info="other unhandled exception") if response is None: response = {} if response is False: # Command doesn't want to send any response return elif isinstance(response, dict): if isinstance(cmd, ToplevelCommand): response = ToplevelResponse(command_name=cmd.name, **response) elif isinstance(cmd, InlineCommand): response = InlineResponse(cmd.name, **response) debug("cmd: " + str(cmd) + ", respin: " + str(response)) self.send_message(self._prepare_command_response(response, cmd)) self._check_perform_just_in_case_gc() self._report_time("after " + cmd.name)
def create_error_response(**kw): if isinstance(cmd, ToplevelCommand): return ToplevelResponse(command_name=cmd.name, **kw) else: return InlineResponse(command_name=cmd.name, **kw)
class ExecutionError(Exception): pass def _report_internal_error(): print("PROBLEM WITH THONNY'S BACK-END:\n", file=sys.stderr) traceback.print_exc() if __name__ == "__main__": port = None if sys.argv[1] == "None" else sys.argv[1] try: if port == "webrepl": url = sys.argv[2] password = sys.argv[3] from thonny.plugins.micropython.webrepl_connection import WebReplConnection connection = WebReplConnection(url, password) else: from thonny.plugins.micropython.serial_connection import SerialConnection connection = SerialConnection(port, BAUDRATE) except ConnectionFailedException as e: msg = ToplevelResponse(error=str(e)) sys.stdout.write(serialize_message(msg) + "\n") connection = None vm = MicroPythonBackend(connection)
def _send_ready_message(self): args = dict(cwd=self._cwd) args["welcome_text"] = self._welcome_text self.send_message(ToplevelResponse(**args))