def find_julia_version(self):
     julia_path = self._toolbox.qsettings().value("appSettings/juliaPath",
                                                  defaultValue="")
     if julia_path != "":
         self.julia_exe = julia_path
     else:
         self.julia_exe = JULIA_EXECUTABLE
     self.julia_project_path = self._toolbox.qsettings().value(
         "appSettings/juliaProjectPath", defaultValue="")
     if self.julia_project_path == "":
         self.julia_project_path = "@."
     args = list()
     args.append("-e")
     args.append("println(VERSION)")
     exec_mngr = QProcessExecutionManager(self._toolbox,
                                          self.julia_exe,
                                          args,
                                          silent=True)
     exec_mngr.start_execution()
     if exec_mngr.wait_for_process_finished(msecs=5000):
         self._julia_version = exec_mngr.process_output
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append("println(Base.active_project())")
     exec_mngr = QProcessExecutionManager(self._toolbox,
                                          self.julia_exe,
                                          args,
                                          silent=True)
     exec_mngr.start_execution()
     if exec_mngr.wait_for_process_finished(msecs=5000):
         self._julia_active_project = exec_mngr.process_output
Esempio n. 2
0
 def execute(self, **kwargs):
     """Executes a prepared instance."""
     self.exec_mngr = QProcessExecutionManager(self._logger, self.program,
                                               self.args, **kwargs)
     self.exec_mngr.execution_finished.connect(
         self.handle_execution_finished)
     self.exec_mngr.start_execution(workdir=self.basedir)
 def test_execute_python_interpreter(self):
     program = sys.executable
     logger = MagicMock()
     manager = QProcessExecutionManager(logger, program, args=["--version"])
     manager.start_execution()
     self.assertTrue(manager.wait_for_process_finished())
     self.assertFalse(manager.process_failed_to_start)
     self.assertFalse(manager.process_failed)
Esempio n. 4
0
 def execute(self, **kwargs):
     """Executes a prepared instance."""
     self.exec_mngr = QProcessExecutionManager(self._logger, self.program,
                                               self.args, **kwargs)
     self.exec_mngr.execution_finished.connect(
         self.handle_execution_finished)
     # TODO: Check if this sets the curDir argument. Is the curDir arg now useless?
     self.exec_mngr.start_execution(workdir=self.basedir)
Esempio n. 5
0
class GAMSToolInstance(ToolInstance):
    """Class for GAMS Tool instances."""
    def prepare(self, optional_input_files, input_database_urls,
                output_database_urls, tool_args):
        """See base class."""
        gams_path = self._settings.value("appSettings/gamsPath",
                                         defaultValue="")
        if gams_path != '':
            gams_exe = gams_path
        else:
            gams_exe = GAMS_EXECUTABLE
        self.program = gams_exe
        self.args.append(self.tool_specification.main_prgm)
        self.args.append("curDir=")
        self.args.append(self.basedir)
        self.args.append(
            "logoption=3")  # TODO: This should be an option in Settings
        self.append_cmdline_args(optional_input_files, input_database_urls,
                                 output_database_urls, tool_args)

    def execute(self, **kwargs):
        """Executes a prepared instance."""
        self.exec_mngr = QProcessExecutionManager(self._logger, self.program,
                                                  self.args, **kwargs)
        self.exec_mngr.execution_finished.connect(
            self.handle_execution_finished)
        # TODO: Check if this sets the curDir argument. Is the curDir arg now useless?
        self.exec_mngr.start_execution(workdir=self.basedir)

    @Slot(int)
    def handle_execution_finished(self, ret):
        """Handles execution finished.

        Args:
            ret (int)
        """
        self.exec_mngr.execution_finished.disconnect(
            self.handle_execution_finished)
        if self.exec_mngr.process_failed:  # process_failed should be True if ret != 0
            if self.exec_mngr.process_failed_to_start:
                self._logger.msg_error.emit(
                    f"\t<b>{self.exec_mngr.program()}</b> failed to start. Make sure that "
                    "GAMS is installed properly on your computer "
                    "and GAMS directory is given in Settings (F1).")
            else:
                try:
                    return_msg = self.tool_specification.return_codes[ret]
                    self._logger.msg_error.emit(
                        f"\t<b>{return_msg}</b> [exit code:{ret}]")
                except KeyError:
                    self._logger.msg_error.emit(
                        f"\tUnknown return code ({ret})")
        else:  # Return code 0: success
            self._logger.msg.emit("\tTool specification execution finished")
        self.exec_mngr.deleteLater()
        self.exec_mngr = None
        self.instance_finished.emit(ret)
 def check_py_call_program(self):
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append("using PyCall; println(PyCall.pyprogramname);")
     self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                               self.julia_exe,
                                               args,
                                               silent=True)
     self.exec_mngr.execution_finished.connect(
         self._handle_check_py_call_program_finished)
     self.exec_mngr.start_execution()
 def update_spine_opt(self):
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append("using Pkg; Pkg.update(ARGS[1]);")
     args.append("SpineOpt")
     self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                               self.julia_exe,
                                               args,
                                               semisilent=True)
     self.exec_mngr.execution_finished.connect(
         self._handle_spine_opt_process_finished)
     self.exec_mngr.start_execution()
Esempio n. 8
0
class ExecutableToolInstance(ToolInstance):
    """Class for Executable Tool instances."""
    def prepare(self, optional_input_files, input_database_urls,
                output_database_urls, tool_args):
        """See base class."""
        batch_path = os.path.join(self.basedir,
                                  self.tool_specification.main_prgm)
        if sys.platform != "win32":
            self.program = "sh"
            self.args.append(batch_path)
        else:
            self.program = batch_path
        self.append_cmdline_args(optional_input_files, input_database_urls,
                                 output_database_urls, tool_args)

    def execute(self, **kwargs):
        """Executes a prepared instance."""
        self.exec_mngr = QProcessExecutionManager(self._logger, self.program,
                                                  self.args, **kwargs)
        self.exec_mngr.execution_finished.connect(
            self.handle_execution_finished)
        self.exec_mngr.start_execution(workdir=self.basedir)

    @Slot(int)
    def handle_execution_finished(self, ret):
        """Handles execution finished.

        Args:
            ret (int): Tool specification process return value
        """
        self.exec_mngr.execution_finished.disconnect(
            self.handle_execution_finished)
        if self.exec_mngr.process_failed:  # process_failed should be True if ret != 0
            if self.exec_mngr.process_failed_to_start:
                self._logger.msg_error.emit(
                    f"\t<b>{self.exec_mngr.program()}</b> failed to start.")
            else:
                try:
                    return_msg = self.tool_specification.return_codes[ret]
                    self._logger.msg_error.emit(
                        f"\t<b>{return_msg}</b> [exit code:{ret}]")
                except KeyError:
                    self._logger.msg_error.emit(
                        f"\tUnknown return code ({ret})")
        else:  # Return code 0: success
            self._logger.msg.emit("\tTool specification execution finished")
        self.exec_mngr.deleteLater()
        self.exec_mngr = None
        self.instance_finished.emit(ret)
 def install_py_call(self):
     """Starts process that installs PyCall in current julia version.
     """
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append("using Pkg; Pkg.add(ARGS[1]);")
     args.append("PyCall")
     self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                               self.julia_exe,
                                               args,
                                               semisilent=True)
     self.exec_mngr.execution_finished.connect(
         self._handle_install_py_call_finished)
     self.exec_mngr.start_execution()
 def install_spine_opt(self):
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append(
         "using Pkg; Pkg.Registry.add(RegistrySpec(url=ARGS[1])); Pkg.add(ARGS[2]);"
     )
     args.append("https://github.com/Spine-project/SpineJuliaRegistry.git")
     args.append("SpineOpt")
     self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                               self.julia_exe,
                                               args,
                                               semisilent=True)
     self.exec_mngr.execution_finished.connect(
         self._handle_spine_opt_process_finished)
     self.exec_mngr.start_execution()
 def install_spine_model(self):
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append(
         "using Pkg; Pkg.add(PackageSpec(url=ARGS[1])); Pkg.add(PackageSpec(url=ARGS[2]));"
     )
     args.append("https://github.com/Spine-project/SpineInterface.jl.git")
     args.append("https://github.com/Spine-project/Spine-Model.git")
     self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                               self.julia_exe,
                                               args,
                                               semisilent=True)
     self.exec_mngr.execution_finished.connect(
         self._handle_spine_model_process_finished)
     self.exec_mngr.start_execution()
Esempio n. 12
0
 def execute(self, **kwargs):
     """Executes a prepared instance."""
     if (self._settings.value("appSettings/useEmbeddedPython",
                              defaultValue="0") == "2"
             and self._embedded_console is not None):
         self.exec_mngr = ConsoleExecutionManager(self._embedded_console,
                                                  self.ipython_command_list,
                                                  self._logger)
         self.exec_mngr.execution_finished.connect(
             self.handle_console_execution_finished)
         self.exec_mngr.start_execution()
     else:
         self.exec_mngr = QProcessExecutionManager(self._logger,
                                                   self.program, self.args,
                                                   **kwargs)
         self.exec_mngr.execution_finished.connect(
             self.handle_execution_finished)
         self.exec_mngr.start_execution(workdir=self.basedir)
 def reconfigure_py_call(self):
     """Starts process that reconfigures PyCall to use selected Python interpreter."""
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append(
         "using PyCall; ENV[ARGS[1]] = ARGS[2]; using Pkg; Pkg.build(ARGS[3]);"
     )
     args.append("PYTHON")
     args.append(self.python)
     args.append("PyCall")
     self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                               self.julia_exe,
                                               args,
                                               semisilent=True)
     self.exec_mngr.execution_finished.connect(
         self._handle_reconfigure_py_call_finished)
     self.exec_mngr.start_execution()
Esempio n. 14
0
def make_python_process(program, args, python_path, logger):
    """Returns an execution manager instance for running the given program in a QProcess.

    Args:
        program (str): Path to the program file
        importer_args (list): Arguments for the program
        python_path (str): Python executable to check
        logger (LoggerInterface)

    Returns:
        QProcessExecutionManager
    """
    program_path = os.path.abspath(program)
    python_cmd = python_path if python_path else PYTHON_EXECUTABLE
    if not python_exists(python_cmd, logger):
        return None
    process = QProcessExecutionManager(logger, python_cmd, [program_path])
    process.data_to_inject = args
    return process
Esempio n. 15
0
 def execute(self, **kwargs):
     """Executes a prepared instance."""
     if (self._settings.value("appSettings/useEmbeddedJulia",
                              defaultValue="2") == "2"
             and self._embedded_console is not None):
         self.exec_mngr = ConsoleExecutionManager(self._embedded_console,
                                                  self.ijulia_command_list,
                                                  self._logger)
         self.exec_mngr.execution_finished.connect(
             self.handle_repl_execution_finished)
         self.exec_mngr.start_execution()
     else:
         self.exec_mngr = QProcessExecutionManager(self._logger,
                                                   self.program, self.args,
                                                   **kwargs)
         self.exec_mngr.execution_finished.connect(
             self.handle_execution_finished)
         # On Julia the Qprocess workdir must be set to the path where the main script is
         # Otherwise it doesn't find input files in subdirectories
         self.exec_mngr.start_execution(workdir=self.basedir)
 def find_julia_version(self):
     args = list()
     args.append("-e")
     args.append("println(VERSION)")
     exec_mngr = QProcessExecutionManager(self._toolbox,
                                          self.julia_exe,
                                          args,
                                          silent=True)
     exec_mngr.start_execution()
     if exec_mngr.wait_for_process_finished(msecs=5000):
         self._julia_version = exec_mngr.process_output
     args = list()
     args.append(f"--project={self.julia_project_path}")
     args.append("-e")
     args.append("println(Base.active_project())")
     exec_mngr = QProcessExecutionManager(self._toolbox,
                                          self.julia_exe,
                                          args,
                                          silent=True)
     exec_mngr.start_execution()
     if exec_mngr.wait_for_process_finished(msecs=5000):
         self._julia_active_project = exec_mngr.process_output
Esempio n. 17
0
def python_exists(program, logger):
    """Checks that Python is set up correctly in Settings.
    This executes 'python -V' in a QProcess and if the process
    finishes successfully, the python is ready to be used.

    Args:
        program (str): Python executable to check
        logger (LoggerInterface)

    Returns:
        bool: True if Python is found, False otherwise
    """
    args = ["-V"]
    python_check_process = QProcessExecutionManager(logger,
                                                    program,
                                                    args,
                                                    silent=True)
    python_check_process.start_execution()
    if not python_check_process.wait_for_process_finished(msecs=3000):
        logger.msg_error.emit(
            "Couldn't execute Python. Please check the <b>Python interpreter</b> option in Settings."
        )
        return False
    return True
 def test_execute_nothing(self):
     logger = MagicMock()
     manager = QProcessExecutionManager(logger)
     manager.start_execution()
     self.assertFalse(manager.wait_for_process_finished())
     self.assertTrue(manager.process_failed_to_start)
class SpineModelConfigurationAssistant(StateMachineWidget):

    _required_julia_version = "1.1.0"
    py_call_program_check_needed = Signal()
    spine_model_process_failed = Signal()
    py_call_installation_needed = Signal()
    py_call_reconfiguration_needed = Signal()
    py_call_process_failed = Signal()
    spine_model_ready = Signal()

    def __init__(self, toolbox):
        super().__init__("SpineModel.jl configuration assistant", toolbox)
        self._toolbox = toolbox
        self.exec_mngr = None
        self._welcome_text = (
            "<html><p>Welcome! This assistant will help you configure Spine Toolbox for using SpineModel.jl</p></html>"
        )
        self.julia_exe = None
        self.julia_project_path = None
        self._julia_version = None
        self._julia_active_project = None
        self._py_call_program = None
        self.button_left.clicked.connect(self.close)

    @busy_effect
    def find_julia_version(self):
        julia_path = self._toolbox.qsettings().value("appSettings/juliaPath",
                                                     defaultValue="")
        if julia_path != "":
            self.julia_exe = julia_path
        else:
            self.julia_exe = JULIA_EXECUTABLE
        self.julia_project_path = self._toolbox.qsettings().value(
            "appSettings/juliaProjectPath", defaultValue="")
        if self.julia_project_path == "":
            self.julia_project_path = "@."
        args = list()
        args.append("-e")
        args.append("println(VERSION)")
        exec_mngr = QProcessExecutionManager(self._toolbox,
                                             self.julia_exe,
                                             args,
                                             silent=True)
        exec_mngr.start_execution()
        if exec_mngr.wait_for_process_finished(msecs=5000):
            self._julia_version = exec_mngr.process_output
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("println(Base.active_project())")
        exec_mngr = QProcessExecutionManager(self._toolbox,
                                             self.julia_exe,
                                             args,
                                             silent=True)
        exec_mngr.start_execution()
        if exec_mngr.wait_for_process_finished(msecs=5000):
            self._julia_active_project = exec_mngr.process_output

    def _make_processing_state(self, name, text):
        s = self._make_state(name)
        s.assignProperty(self.label_msg, "text", text)
        s.assignProperty(self.button_right, "visible", False)
        s.assignProperty(self.label_loader, "visible", True)
        s.assignProperty(self.button_left, "visible", False)
        return s

    def _make_report_state(self, name, text):
        s = self._make_state(name)
        s.assignProperty(self.label_msg, "text", text)
        s.assignProperty(self.button_right, "visible", False)
        s.assignProperty(self.label_loader, "visible", False)
        s.assignProperty(self.button_left, "visible", True)
        s.assignProperty(self.button_left, "text", "Close")
        return s

    def _make_prompt_state(self, name, text):
        s = self._make_state(name)
        s.assignProperty(self.label_msg, "text", text)
        s.assignProperty(self.button_right, "visible", True)
        s.assignProperty(self.button_right, "text", "Allow")
        s.assignProperty(self.label_loader, "visible", False)
        s.assignProperty(self.button_left, "visible", True)
        s.assignProperty(self.button_left, "text", "Cancel")
        return s

    def _make_report_julia_not_found(self):
        return self._make_report_state(
            "report_julia_not_found",
            "<html><p>Unable to find Julia. Make sure that Julia is installed correctly and try again.</p></html>",
        )

    def _make_report_bad_julia_version(self):
        return self._make_report_state(
            "report_bad_julia_version",
            f"<html><p>SpineModel.jl requires Julia version >= {self._required_julia_version}, whereas current version is {self._julia_version}. "
            "Upgrade Julia and try again.</p></html>",
        )

    def _make_welcome(self):
        self.find_julia_version()
        if self._julia_version is None:
            return self._make_report_julia_not_found()
        if self._julia_version < self._required_julia_version:
            return self._make_report_bad_julia_version()
        return super()._make_welcome()

    def _make_updating_spine_model(self):
        return self._make_processing_state(
            "updating_spine_model",
            "<html><p>Updating SpineModel.jl to the latest version...</p></html>"
        )

    def _make_prompt_to_install_latest_spine_model(self):
        return self._make_prompt_state(
            "prompt_to_install_latest_spine_model",
            "<html><p>Spine Toolbox needs to do the following modifications to the Julia project "
            f"at <b>{self._julia_active_project}</b>:</p><p>Install the SpineModel.jl package</p>",
        )

    def _make_installing_latest_spine_model(self):
        return self._make_processing_state(
            "installing_latest_spine_model",
            "<html><p>Installing latest SpineModel.jl...</p></html>")

    def _make_report_spine_model_installation_failed(self):
        return self._make_report_state(
            "report_spine_model_installation_failed",
            "<html><p>SpineModel.jl installation failed. See Process log for error messages.</p></html>",
        )

    def _make_checking_py_call_program(self):
        return self._make_processing_state(
            "checking_py_call_program",
            "<html><p>Checking PyCall.jl's configuration...</p></html>")

    def _make_prompt_to_reconfigure_py_call(self):
        return self._make_prompt_state(
            "prompt_to_reconfigure_py_call",
            "<html><p>Spine Toolbox needs to do the following modifications to the Julia project "
            f"at <b>{self._julia_active_project}</b>:</p>"
            f"<p>Change the Python program used by PyCall.jl to {sys.executable}</p>",
        )

    def _make_prompt_to_install_py_call(self):
        return self._make_prompt_state(
            "prompt_to_install_py_call",
            "<html><p>Spine Toolbox needs to do the following modifications to the Julia project "
            f"at <b>{self._julia_active_project}</b>:</p>"
            "<p>Install the PyCall.jl package.</p>",
        )

    def _make_report_spine_model_ready(self):
        return self._make_report_state(
            "report_spine_model_ready",
            "<html><p>SpineModel.jl has been successfully configured.</p></html>"
        )

    def _make_reconfiguring_py_call(self):
        return self._make_processing_state(
            "reconfiguring_py_call",
            "<html><p>Reconfiguring PyCall.jl...</p></html>")

    def _make_installing_py_call(self):
        return self._make_processing_state(
            "installing_py_call",
            "<html><p>Installing PyCall.jl...</p></html>")

    def _make_report_py_call_process_failed(self):
        return self._make_report_state(
            "report_py_call_process_failed",
            "<html><p>PyCall.jl installation/reconfiguration failed. See Process log for error messages.</p></html>",
        )

    @Slot()
    def update_spine_model(self):
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("using Pkg; Pkg.update(ARGS[1]);")
        args.append("SpineModel")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_spine_model_process_finished)
        self.exec_mngr.start_execution()

    @Slot()
    def install_spine_model(self):
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append(
            "using Pkg; Pkg.add(PackageSpec(url=ARGS[1])); Pkg.add(PackageSpec(url=ARGS[2]));"
        )
        args.append("https://github.com/Spine-project/SpineInterface.jl.git")
        args.append("https://github.com/Spine-project/Spine-Model.git")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_spine_model_process_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_spine_model_process_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_spine_model_process_finished)
        if ret == 0:
            self.py_call_program_check_needed.emit()
        else:
            self.spine_model_process_failed.emit()

    @Slot()
    def check_py_call_program(self):
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("using PyCall; println(PyCall.pyprogramname);")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  silent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_check_py_call_program_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_check_py_call_program_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_check_py_call_program_finished)
        if ret == 0:
            self._py_call_program = self.exec_mngr.process_output
            if self._py_call_program == sys.executable:
                self.spine_model_ready.emit()
            else:
                self.py_call_reconfiguration_needed.emit()
        else:
            self.py_call_installation_needed.emit()

    @Slot()
    def reconfigure_py_call(self):
        """Starts process that reconfigures PyCall to use given python program.
        """
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append(
            "using PyCall; ENV[ARGS[1]] = ARGS[2]; using Pkg; Pkg.build(ARGS[3]);"
        )
        args.append("PYTHON")
        args.append(sys.executable)
        args.append("PyCall")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_reconfigure_py_call_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_reconfigure_py_call_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_reconfigure_py_call_finished)
        if ret == 0:
            self.spine_model_ready.emit()
        else:
            self.py_call_process_failed.emit()

    @Slot()
    def install_py_call(self):
        """Starts process that installs PyCall in current julia version.
        """
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("using Pkg; Pkg.add(ARGS[1]);")
        args.append("PyCall")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_install_py_call_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_install_py_call_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_install_py_call_finished)
        if ret == 0:
            self.py_call_program_check_needed.emit()
        else:
            self.py_call_process_failed.emit()

    def set_up_machine(self):
        super().set_up_machine()
        # States
        updating_spine_model = self._make_updating_spine_model()
        prompt_to_install_latest_spine_model = self._make_prompt_to_install_latest_spine_model(
        )
        installing_latest_spine_model = self._make_installing_latest_spine_model(
        )
        report_spine_model_installation_failed = self._make_report_spine_model_installation_failed(
        )
        checking_py_call_program = self._make_checking_py_call_program()
        prompt_to_reconfigure_py_call = self._make_prompt_to_reconfigure_py_call(
        )
        prompt_to_install_py_call = self._make_prompt_to_install_py_call()
        report_spine_model_ready = self._make_report_spine_model_ready()
        reconfiguring_py_call = self._make_reconfiguring_py_call()
        installing_py_call = self._make_installing_py_call()
        report_py_call_process_failed = self._make_report_py_call_process_failed(
        )
        # Transitions
        self.welcome.addTransition(self.welcome.finished, updating_spine_model)
        updating_spine_model.addTransition(
            self.spine_model_process_failed,
            prompt_to_install_latest_spine_model)
        updating_spine_model.addTransition(self.py_call_program_check_needed,
                                           checking_py_call_program)
        prompt_to_install_latest_spine_model.addTransition(
            self.button_right.clicked, installing_latest_spine_model)
        installing_latest_spine_model.addTransition(
            self.spine_model_process_failed,
            report_spine_model_installation_failed)
        installing_latest_spine_model.addTransition(
            self.py_call_program_check_needed, checking_py_call_program)
        checking_py_call_program.addTransition(
            self.py_call_reconfiguration_needed, prompt_to_reconfigure_py_call)
        checking_py_call_program.addTransition(
            self.py_call_installation_needed, prompt_to_install_py_call)
        checking_py_call_program.addTransition(self.py_call_process_failed,
                                               report_py_call_process_failed)
        checking_py_call_program.addTransition(self.spine_model_ready,
                                               report_spine_model_ready)
        prompt_to_reconfigure_py_call.addTransition(self.button_right.clicked,
                                                    reconfiguring_py_call)
        prompt_to_install_py_call.addTransition(self.button_right.clicked,
                                                installing_py_call)
        reconfiguring_py_call.addTransition(self.py_call_process_failed,
                                            report_py_call_process_failed)
        reconfiguring_py_call.addTransition(self.spine_model_ready,
                                            report_spine_model_ready)
        installing_py_call.addTransition(self.py_call_process_failed,
                                         report_py_call_process_failed)
        installing_py_call.addTransition(self.py_call_program_check_needed,
                                         checking_py_call_program)
        # Entered
        updating_spine_model.entered.connect(self.update_spine_model)
        checking_py_call_program.entered.connect(self.check_py_call_program)
        installing_latest_spine_model.entered.connect(self.install_spine_model)
        reconfiguring_py_call.entered.connect(self.reconfigure_py_call)
        installing_py_call.entered.connect(self.install_py_call)
Esempio n. 20
0
class JuliaToolInstance(ToolInstance):
    """Class for Julia Tool instances."""
    def __init__(self, tool_specification, basedir, settings,
                 embedded_julia_console, logger):
        """
        Args:
            tool_specification (ToolSpecification): the tool specification for this instance
            basedir (str): the path to the directory where this instance should run
            settings (QSettings): Toolbox settings
            embedded_julia_console (JuliaREPLWidget): a Julia console for execution in the embedded console
            logger (LoggerInterface): a logger instance
        """
        super().__init__(tool_specification, basedir, settings, logger)
        self._embedded_console = embedded_julia_console
        self.ijulia_command_list = list()

    def prepare(self, optional_input_files, input_database_urls,
                output_database_urls, tool_args):
        """See base class."""
        work_dir = self.basedir
        use_embedded_julia = self._settings.value(
            "appSettings/useEmbeddedJulia", defaultValue="2")
        if use_embedded_julia == "2" and self._embedded_console is not None:
            # Prepare Julia REPL command
            mod_work_dir = repr(work_dir).strip("'")
            cmdline_args = self.tool_specification.get_cmdline_args(
                optional_input_files, input_database_urls,
                output_database_urls)
            cmdline_args += tool_args
            args = '["' + repr('", "'.join(cmdline_args)).strip("'") + '"]'
            self.ijulia_command_list += [
                f'cd("{mod_work_dir}");',
                'empty!(ARGS);',
                f'append!(ARGS, {args});',
                f'include("{self.tool_specification.main_prgm}")',
            ]
        else:
            # Prepare command "julia --project={PROJECT_DIR} script.jl"
            julia_path = self._settings.value("appSettings/juliaPath",
                                              defaultValue="")
            if julia_path != "":
                julia_exe = julia_path
            else:
                julia_exe = JULIA_EXECUTABLE
            julia_project_path = self._settings.value(
                "appSettings/juliaProjectPath", defaultValue="")
            script_path = os.path.join(work_dir,
                                       self.tool_specification.main_prgm)
            self.program = julia_exe
            self.args.append(f"--project={julia_project_path}")
            self.args.append(script_path)
            self.append_cmdline_args(optional_input_files, input_database_urls,
                                     output_database_urls, tool_args)

    def execute(self, **kwargs):
        """Executes a prepared instance."""
        if (self._settings.value("appSettings/useEmbeddedJulia",
                                 defaultValue="2") == "2"
                and self._embedded_console is not None):
            self.exec_mngr = ConsoleExecutionManager(self._embedded_console,
                                                     self.ijulia_command_list,
                                                     self._logger)
            self.exec_mngr.execution_finished.connect(
                self.handle_repl_execution_finished)
            self.exec_mngr.start_execution()
        else:
            self.exec_mngr = QProcessExecutionManager(self._logger,
                                                      self.program, self.args,
                                                      **kwargs)
            self.exec_mngr.execution_finished.connect(
                self.handle_execution_finished)
            # On Julia the Qprocess workdir must be set to the path where the main script is
            # Otherwise it doesn't find input files in subdirectories
            self.exec_mngr.start_execution(workdir=self.basedir)

    @Slot(int)
    def handle_repl_execution_finished(self, ret):
        """Handles repl-execution finished.

        Args:
            ret (int): Tool specification process return value
        """
        self.exec_mngr.execution_finished.disconnect(
            self.handle_repl_execution_finished)
        if ret != 0:
            try:
                return_msg = self.tool_specification.return_codes[ret]
                self._logger.msg_error.emit(
                    f"\t<b>{return_msg}</b> [exit code: {ret}]")
            except KeyError:
                self._logger.msg_error.emit(f"\tUnknown return code ({ret})")
        else:
            self._logger.msg.emit("\tTool specification execution finished")
        self.exec_mngr.deleteLater()
        self.exec_mngr = None
        self.instance_finished.emit(ret)

    @Slot(int)
    def handle_execution_finished(self, ret):
        """Handles execution finished.

        Args:
            ret (int): Tool specification process return value
        """
        self.exec_mngr.execution_finished.disconnect(
            self.handle_execution_finished)
        if self.exec_mngr.process_failed:  # process_failed should be True if ret != 0
            if self.exec_mngr.process_failed_to_start:
                self._logger.msg_error.emit(
                    f"\t<b>{self.exec_mngr.program()}</b> failed to start. Make sure that "
                    "Julia is installed properly on your computer.")
            else:
                try:
                    return_msg = self.tool_specification.return_codes[ret]
                    self._logger.msg_error.emit(
                        f"\t<b>{return_msg}</b> [exit code:{ret}]")
                except KeyError:
                    self._logger.msg_error.emit(
                        f"\tUnknown return code ({ret})")
        else:  # Return code 0: success
            self._logger.msg.emit("\tTool specification execution finished")
        self.exec_mngr.deleteLater()
        self.exec_mngr = None
        self.instance_finished.emit(ret)
class SpineOptConfigurationAssistant(StateMachineWidget):

    _required_julia_version = "1.1.0"
    py_call_program_check_needed = Signal()
    spine_opt_process_failed = Signal()
    py_call_installation_needed = Signal()
    py_call_reconfiguration_needed = Signal()
    py_call_process_failed = Signal()
    spine_opt_ready = Signal()

    def __init__(self, toolbox):
        super().__init__("SpineOpt.jl configuration assistant", toolbox)
        self._toolbox = toolbox
        self.exec_mngr = None
        self.julia_exe = None
        self.julia_project_path = None
        self._julia_version = None
        self._julia_active_project = None
        self._py_call_program = None
        self.julia_exe, self.julia_project_path = self.resolve_julia()
        self.python = self.resolve_python()
        self._welcome_text = (
            "<html><p>Welcome! This assistant will help you configure Spine Toolbox for using SpineOpt.jl<br><br>"
            "Your setup<br>"
            "Julia: <b>{0}</b><br>"
            "Julia Project: <b>{1}</b><br>"
            "Python for PyCall: <b>{2}</b>"
            "</p></html>".format(self.julia_exe, self.julia_project_path,
                                 self.python))
        self.button_left.clicked.connect(self.close)

    def resolve_julia(self):
        """Returns Julia executable and project according to user's Settings."""
        use_embedded_julia = int(self._toolbox.qsettings().value(
            "appSettings/useEmbeddedJulia", defaultValue="2"))
        if use_embedded_julia == 2:
            # If user has enabled the embedded Julia Console, we need to take julia exe and project from kernel specs
            # Read Julia kernel name from app settings
            kernel_name = self._toolbox.qsettings().value(
                "appSettings/juliaKernel", defaultValue="")
            if not self.check_kernel_is_ok(kernel_name, "Julia"):
                return None, None
            julia_kernels = find_julia_kernels()
            kernel_deats = KernelEditor.get_kernel_deats(
                julia_kernels[kernel_name])
            julia_exe = kernel_deats["exe"]
            julia_project_path = kernel_deats["project"]
        else:
            julia_path = self._toolbox.qsettings().value(
                "appSettings/juliaPath", defaultValue="")
            if julia_path != "":
                julia_exe = julia_path
            else:
                julia_exe = resolve_julia_executable_from_path()
            if julia_exe == "":
                self._toolbox.msg_error.emit(
                    "Julia not found in PATH. Please select a valid Julia executable in Settings->Tools."
                )
                # Julia was not found in PATH
                return None, None
            julia_project_path = self._toolbox.qsettings().value(
                "appSettings/juliaProjectPath", defaultValue="")
            if julia_project_path == "":
                julia_project_path = "@."
        return julia_exe, julia_project_path

    def resolve_python(self):
        """Returns the full path to Python interpreter according to user's Settings."""
        use_embedded_python = int(self._toolbox.qsettings().value(
            "appSettings/useEmbeddedPython", defaultValue="2"))
        if use_embedded_python == 2:
            # Get Python interpreter from selected Python kernel
            python_kernel_name = self._toolbox.qsettings().value(
                "appSettings/pythonKernel", defaultValue="")
            if not self.check_kernel_is_ok(python_kernel_name, "Python"):
                return None
            kernel_deats = KernelEditor.get_kernel_deats(
                find_python_kernels()[python_kernel_name])
            python_path = kernel_deats["exe"]
            return python_path
        else:
            return python_interpreter(self._toolbox.qsettings())

    def check_kernel_is_ok(self, kernel_name, prgm):
        """Checks that kernel spec is valid for the configuration assistant to continue.

        Args:
            kernel_name (str): Kernel name
            prgm (str): Either "Python" or "Julia", determines which kernel type to check

        Returns:
            bool: True if kernel is ok, False otherwise
        """
        if kernel_name == "":
            self._toolbox.msg_error.emit(
                f"No kernel selected. Go to Settings->Tools to select a {prgm} kernel"
            )
            return False
        if prgm == "Python":
            kernels = find_python_kernels()
        else:
            kernels = find_julia_kernels()
        try:
            kernel_path = kernels[kernel_name]
        except KeyError:
            self._toolbox.msg_error.emit(
                f"Kernel <b>{kernel_name}</b> not found. Go to Settings->Tools and select another {prgm} kernel."
            )
            return False
        kernel_json = os.path.join(kernel_path, "kernel.json")
        if not os.path.exists(kernel_json):
            self._toolbox.msg_error.emit(
                f"Path <b>{kernel_json}</b> does not exist")
            return False
        if os.stat(kernel_json).st_size == 0:
            self._toolbox.msg_error.emit(f"File <b>{kernel_json}</b> is empty")
            return False
        with open(kernel_json, "r") as fh:
            try:
                kernel_dict = json.load(fh)
            except json.decoder.JSONDecodeError:
                self._toolbox.msg_error.emit(
                    f"Error reading file <b>{kernel_json}</b> file. Invalid JSON."
                )
                return False
        return True

    @busy_effect
    def find_julia_version(self):
        args = list()
        args.append("-e")
        args.append("println(VERSION)")
        exec_mngr = QProcessExecutionManager(self._toolbox,
                                             self.julia_exe,
                                             args,
                                             silent=True)
        exec_mngr.start_execution()
        if exec_mngr.wait_for_process_finished(msecs=5000):
            self._julia_version = exec_mngr.process_output
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("println(Base.active_project())")
        exec_mngr = QProcessExecutionManager(self._toolbox,
                                             self.julia_exe,
                                             args,
                                             silent=True)
        exec_mngr.start_execution()
        if exec_mngr.wait_for_process_finished(msecs=5000):
            self._julia_active_project = exec_mngr.process_output

    def _make_processing_state(self, name, text):
        s = self._make_state(name)
        s.assignProperty(self.label_msg, "text", text)
        s.assignProperty(self.button_right, "visible", False)
        s.assignProperty(self.label_loader, "visible", True)
        s.assignProperty(self.button_left, "visible", False)
        return s

    def _make_report_state(self, name, text):
        s = self._make_state(name)
        s.assignProperty(self.label_msg, "text", text)
        s.assignProperty(self.button_right, "visible", False)
        s.assignProperty(self.label_loader, "visible", False)
        s.assignProperty(self.button_left, "visible", True)
        s.assignProperty(self.button_left, "text", "Close")
        return s

    def _make_prompt_state(self, name, text):
        s = self._make_state(name)
        s.assignProperty(self.label_msg, "text", text)
        s.assignProperty(self.button_right, "visible", True)
        s.assignProperty(self.button_right, "text", "Allow")
        s.assignProperty(self.label_loader, "visible", False)
        s.assignProperty(self.button_left, "visible", True)
        s.assignProperty(self.button_left, "text", "Cancel")
        return s

    def _make_report_julia_not_found(self):
        return self._make_report_state(
            "report_julia_not_found",
            "<html><p>Unable to find Julia. Make sure that Julia is installed correctly and try again.</p></html>",
        )

    def _make_report_bad_julia_version(self):
        return self._make_report_state(
            "report_bad_julia_version",
            f"<html><p>SpineOpt.jl requires Julia version >= {self._required_julia_version}, "
            f"whereas current version is {self._julia_version}. "
            "Upgrade Julia and try again.</p></html>",
        )

    def _make_welcome(self):
        self.find_julia_version()
        # TODO: Report and abort if Julia or Python is None
        if self._julia_version is None:
            return self._make_report_julia_not_found()
        if self._julia_version < self._required_julia_version:
            return self._make_report_bad_julia_version()
        return super()._make_welcome()

    def _make_updating_spine_opt(self):
        return self._make_processing_state(
            "updating_spine_opt",
            "<html><p>Updating SpineOpt.jl to the latest version...</p></html>"
        )

    def _make_prompt_to_install_latest_spine_opt(self):
        return self._make_prompt_state(
            "prompt_to_install_latest_spine_opt",
            "<html><p>Spine Toolbox needs to do the following modifications to the Julia project "
            f"at <b>{self._julia_active_project}</b>:</p><p>Install the SpineOpt.jl package</p>",
        )

    def _make_installing_latest_spine_opt(self):
        return self._make_processing_state(
            "installing_latest_spine_opt",
            "<html><p>Installing latest SpineOpt.jl...</p></html>")

    def _make_report_spine_opt_installation_failed(self):
        return self._make_report_state(
            "report_spine_opt_installation_failed",
            "<html><p>SpineOpt.jl installation failed. See Process log for error messages.</p></html>",
        )

    def _make_checking_py_call_program(self):
        return self._make_processing_state(
            "checking_py_call_program",
            "<html><p>Checking PyCall.jl's configuration...</p></html>")

    def _make_prompt_to_reconfigure_py_call(self):
        return self._make_prompt_state(
            "prompt_to_reconfigure_py_call",
            "<html><p>Spine Toolbox needs to do the following modifications to the Julia project "
            f"at <b>{self._julia_active_project}</b>:</p>"
            f"<p>Change the Python program used by PyCall.jl to {self.python}</p>",
        )

    def _make_prompt_to_install_py_call(self):
        return self._make_prompt_state(
            "prompt_to_install_py_call",
            "<html><p>Spine Toolbox needs to do the following modifications to the Julia project "
            f"at <b>{self._julia_active_project}</b>:</p>"
            "<p>Install the PyCall.jl package.</p>",
        )

    def _make_report_spine_opt_ready(self):
        return self._make_report_state(
            "report_spine_opt_ready",
            "<html><p>SpineOpt.jl has been successfully configured.</p></html>"
        )

    def _make_reconfiguring_py_call(self):
        return self._make_processing_state(
            "reconfiguring_py_call",
            "<html><p>Reconfiguring PyCall.jl...</p></html>")

    def _make_installing_py_call(self):
        return self._make_processing_state(
            "installing_py_call",
            "<html><p>Installing PyCall.jl...</p></html>")

    def _make_report_py_call_process_failed(self):
        return self._make_report_state(
            "report_py_call_process_failed",
            "<html><p>PyCall.jl installation/reconfiguration failed. See Process log for error messages.</p></html>",
        )

    @Slot()
    def update_spine_opt(self):
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("using Pkg; Pkg.update(ARGS[1]);")
        args.append("SpineOpt")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_spine_opt_process_finished)
        self.exec_mngr.start_execution()

    @Slot()
    def install_spine_opt(self):
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append(
            "using Pkg; Pkg.Registry.add(RegistrySpec(url=ARGS[1])); Pkg.add(ARGS[2]);"
        )
        args.append("https://github.com/Spine-project/SpineJuliaRegistry.git")
        args.append("SpineOpt")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_spine_opt_process_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_spine_opt_process_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_spine_opt_process_finished)
        if ret == 0:
            self.py_call_program_check_needed.emit()
        else:
            self.spine_opt_process_failed.emit()

    @Slot()
    def check_py_call_program(self):
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("using PyCall; println(PyCall.pyprogramname);")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  silent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_check_py_call_program_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_check_py_call_program_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_check_py_call_program_finished)
        if ret == 0:
            self._py_call_program = self.exec_mngr.process_output
            if os.path.normcase(self._py_call_program) == os.path.normcase(
                    self.python):
                self.spine_opt_ready.emit()
            else:
                self.py_call_reconfiguration_needed.emit()
        else:
            self.py_call_installation_needed.emit()

    @Slot()
    def reconfigure_py_call(self):
        """Starts process that reconfigures PyCall to use selected Python interpreter."""
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append(
            "using PyCall; ENV[ARGS[1]] = ARGS[2]; using Pkg; Pkg.build(ARGS[3]);"
        )
        args.append("PYTHON")
        args.append(self.python)
        args.append("PyCall")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_reconfigure_py_call_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_reconfigure_py_call_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_reconfigure_py_call_finished)
        if ret == 0:
            self.spine_opt_ready.emit()
        else:
            self.py_call_process_failed.emit()

    @Slot()
    def install_py_call(self):
        """Starts process that installs PyCall in current julia version."""
        args = list()
        args.append(f"--project={self.julia_project_path}")
        args.append("-e")
        args.append("ENV[ARGS[1]] = ARGS[2]; using Pkg; Pkg.add(ARGS[3]);")
        args.append("PYTHON")
        args.append(self.python)
        args.append("PyCall")
        self.exec_mngr = QProcessExecutionManager(self._toolbox,
                                                  self.julia_exe,
                                                  args,
                                                  semisilent=True)
        self.exec_mngr.execution_finished.connect(
            self._handle_install_py_call_finished)
        self.exec_mngr.start_execution()

    @Slot(int)
    def _handle_install_py_call_finished(self, ret):
        self.exec_mngr.execution_finished.disconnect(
            self._handle_install_py_call_finished)
        if ret == 0:
            self.py_call_program_check_needed.emit()
        else:
            self.py_call_process_failed.emit()

    def set_up_machine(self):
        super().set_up_machine()
        # States
        updating_spine_opt = self._make_updating_spine_opt()
        prompt_to_install_latest_spine_opt = self._make_prompt_to_install_latest_spine_opt(
        )
        installing_latest_spine_opt = self._make_installing_latest_spine_opt()
        report_spine_opt_installation_failed = self._make_report_spine_opt_installation_failed(
        )
        checking_py_call_program = self._make_checking_py_call_program()
        prompt_to_reconfigure_py_call = self._make_prompt_to_reconfigure_py_call(
        )
        prompt_to_install_py_call = self._make_prompt_to_install_py_call()
        report_spine_opt_ready = self._make_report_spine_opt_ready()
        reconfiguring_py_call = self._make_reconfiguring_py_call()
        installing_py_call = self._make_installing_py_call()
        report_py_call_process_failed = self._make_report_py_call_process_failed(
        )
        # Transitions
        self.welcome.addTransition(self.welcome.finished, updating_spine_opt)
        updating_spine_opt.addTransition(self.spine_opt_process_failed,
                                         prompt_to_install_latest_spine_opt)
        updating_spine_opt.addTransition(self.py_call_program_check_needed,
                                         checking_py_call_program)
        prompt_to_install_latest_spine_opt.addTransition(
            self.button_right.clicked, installing_latest_spine_opt)
        installing_latest_spine_opt.addTransition(
            self.spine_opt_process_failed,
            report_spine_opt_installation_failed)
        installing_latest_spine_opt.addTransition(
            self.py_call_program_check_needed, checking_py_call_program)
        checking_py_call_program.addTransition(
            self.py_call_reconfiguration_needed, prompt_to_reconfigure_py_call)
        checking_py_call_program.addTransition(
            self.py_call_installation_needed, prompt_to_install_py_call)
        checking_py_call_program.addTransition(self.py_call_process_failed,
                                               report_py_call_process_failed)
        checking_py_call_program.addTransition(self.spine_opt_ready,
                                               report_spine_opt_ready)
        prompt_to_reconfigure_py_call.addTransition(self.button_right.clicked,
                                                    reconfiguring_py_call)
        prompt_to_install_py_call.addTransition(self.button_right.clicked,
                                                installing_py_call)
        reconfiguring_py_call.addTransition(self.py_call_process_failed,
                                            report_py_call_process_failed)
        reconfiguring_py_call.addTransition(self.spine_opt_ready,
                                            report_spine_opt_ready)
        installing_py_call.addTransition(self.py_call_process_failed,
                                         report_py_call_process_failed)
        installing_py_call.addTransition(self.py_call_program_check_needed,
                                         checking_py_call_program)
        # Entered
        updating_spine_opt.entered.connect(self.update_spine_opt)
        checking_py_call_program.entered.connect(self.check_py_call_program)
        installing_latest_spine_opt.entered.connect(self.install_spine_opt)
        reconfiguring_py_call.entered.connect(self.reconfigure_py_call)
        installing_py_call.entered.connect(self.install_py_call)
Esempio n. 22
0
class PythonToolInstance(ToolInstance):
    """Class for Python Tool instances."""
    def __init__(self, tool_specification, basedir, settings,
                 embedded_python_console, logger):
        """

        Args:
            tool_specification (ToolSpecification): the tool specification for this instance
            basedir (str): the path to the directory where this instance should run
            settings (QSettings): Toolbox settings
            embedded_python_console (PythonReplWidget): a Python console widget for execution in embedded console
            logger (LoggerInterface): A logger instance
        """
        super().__init__(tool_specification, basedir, settings, logger)
        self._embedded_console = embedded_python_console
        self.ipython_command_list = list()

    def prepare(self, optional_input_files, input_database_urls,
                output_database_urls, tool_args):
        """See base class."""
        work_dir = self.basedir
        use_embedded_python = self._settings.value(
            "appSettings/useEmbeddedPython", defaultValue="0")
        if use_embedded_python == "2" and self._embedded_console is not None:
            # Prepare a command list (FIFO queue) with two commands for Python Console
            # 1st cmd: Change current work directory
            # 2nd cmd: Run script with given args
            # Cast args in list to strings and combine them to a single string
            tool_spec_args = self.tool_specification.get_cmdline_args(
                optional_input_files, input_database_urls,
                output_database_urls)
            all_args = tool_spec_args + tool_args
            cd_work_dir_cmd = f"%cd -q {work_dir}"  # -q: quiet
            run_script_cmd = f'%run "{self.tool_specification.main_prgm}"'
            if all_args:
                run_script_cmd = run_script_cmd + " " + '"' + '" "'.join(
                    all_args) + '"'
            # Populate FIFO command queue
            self.ipython_command_list.append(cd_work_dir_cmd)
            self.ipython_command_list.append(run_script_cmd)
        else:
            # Prepare command "python script.py script_arguments"
            python_path = self._settings.value("appSettings/pythonPath",
                                               defaultValue="")
            if python_path != "":
                python_cmd = python_path
            else:
                python_cmd = PYTHON_EXECUTABLE
            script_path = os.path.join(work_dir,
                                       self.tool_specification.main_prgm)
            self.program = python_cmd
            self.args.append(
                script_path
            )  # First argument for the Python interpreter is path to the tool script
            self.append_cmdline_args(optional_input_files, input_database_urls,
                                     output_database_urls, tool_args)

    def execute(self, **kwargs):
        """Executes a prepared instance."""
        if (self._settings.value("appSettings/useEmbeddedPython",
                                 defaultValue="0") == "2"
                and self._embedded_console is not None):
            self.exec_mngr = ConsoleExecutionManager(self._embedded_console,
                                                     self.ipython_command_list,
                                                     self._logger)
            self.exec_mngr.execution_finished.connect(
                self.handle_console_execution_finished)
            self.exec_mngr.start_execution()
        else:
            self.exec_mngr = QProcessExecutionManager(self._logger,
                                                      self.program, self.args,
                                                      **kwargs)
            self.exec_mngr.execution_finished.connect(
                self.handle_execution_finished)
            self.exec_mngr.start_execution(workdir=self.basedir)

    @Slot(int)
    def handle_console_execution_finished(self, ret):
        """Handles console-execution finished.

        Args:
            ret (int): Tool specification process return value
        """
        self.exec_mngr.execution_finished.disconnect(
            self.handle_console_execution_finished)
        if ret != 0:
            try:
                return_msg = self.tool_specification.return_codes[ret]
                self._logger.msg_error.emit(
                    f"\t<b>{return_msg}</b> [exit code: {ret}]")
            except KeyError:
                self._logger.msg_error.emit(f"\tUnknown return code ({ret})")
        else:
            self._logger.msg.emit("\tTool specification execution finished")
        self.exec_mngr.deleteLater()
        self.exec_mngr = None
        self.instance_finished.emit(ret)

    @Slot(int)
    def handle_execution_finished(self, ret):
        """Handles execution finished.

        Args:
            ret (int): Tool specification process return value
        """
        self.exec_mngr.execution_finished.disconnect(
            self.handle_execution_finished)
        if self.exec_mngr.process_failed:  # process_failed should be True if ret != 0
            if self.exec_mngr.process_failed_to_start:
                self._logger.msg_error.emit(
                    f"\t<b>{self.exec_mngr.program()}</b> failed to start. Make sure that "
                    "Python is installed properly on your computer.")
            else:
                try:
                    return_msg = self.tool_specification.return_codes[ret]
                    self._logger.msg_error.emit(
                        f"\t<b>{return_msg}</b> [exit code:{ret}]")
                except KeyError:
                    self._logger.msg_error.emit(
                        f"\tUnknown return code ({ret})")
        else:  # Return code 0: success
            self._logger.msg.emit("\tTool specification execution finished")
        self.exec_mngr.deleteLater()
        self.exec_mngr = None
        self.instance_finished.emit(ret)