Source code for spinetoolbox.configuration_assistants.spine_model.configuration_assistant

######################################################################################################################
# Copyright (C) 2017-2020 Spine project consortium
# This file is part of Spine Toolbox.
# Spine Toolbox is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
# Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
######################################################################################################################

"""
Widget for assisting the user in configuring SpineModel.jl.

:author: M. Marin (KTH)
:date:   9.1.2019
"""

import sys
from PySide2.QtCore import Signal, Slot
from spinetoolbox.widgets.state_machine_widget import StateMachineWidget
from spinetoolbox.execution_managers import QProcessExecutionManager
from spinetoolbox.config import JULIA_EXECUTABLE
from spinetoolbox.helpers import busy_effect


[docs]class SpineModelConfigurationAssistant(StateMachineWidget):
[docs] _required_julia_version = "1.1.0"
[docs] py_call_program_check_needed = Signal()
[docs] spine_model_process_failed = Signal()
[docs] py_call_installation_needed = Signal()
[docs] py_call_reconfiguration_needed = Signal()
[docs] py_call_process_failed = Signal()
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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>",
)
[docs] 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>", )
[docs] 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()
[docs] 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>"
)
[docs] 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>", )
[docs] def _make_installing_latest_spine_model(self): return self._make_processing_state( "installing_latest_spine_model", "<html><p>Installing latest SpineModel.jl...</p></html>"
)
[docs] 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>",
)
[docs] 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>"
)
[docs] 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>", )
[docs] 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>", )
[docs] 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>"
)
[docs] def _make_reconfiguring_py_call(self): return self._make_processing_state("reconfiguring_py_call", "<html><p>Reconfiguring PyCall.jl...</p></html>")
[docs] def _make_installing_py_call(self): return self._make_processing_state("installing_py_call", "<html><p>Installing PyCall.jl...</p></html>")
[docs] 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()
[docs] 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()
[docs] 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)
[docs] 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()
[docs] 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)
[docs] 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()
[docs] 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)
[docs] 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()
[docs] 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)
[docs] 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()
[docs] 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)