Skip to content

Commit

Permalink
Show Python 3.8 deprecation warning on Toolbox startup (#2914)
Browse files Browse the repository at this point in the history
  • Loading branch information
soininen authored Aug 7, 2024
2 parents 5cb26c0 + 3fcba39 commit ee9508f
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 29 deletions.
2 changes: 1 addition & 1 deletion spinetoolbox/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def main():
logging.warning("Could not load fonts from resources file. Some icons may not render properly.")
window = ToolboxUI()
window.show()
QTimer.singleShot(0, lambda: window.init_project(args.project))
QTimer.singleShot(0, lambda: window.init_tasks(args.project))
# Enter main event loop and wait until exit() is called
return_code = app.exec()
return return_code
Expand Down
1 change: 0 additions & 1 deletion spinetoolbox/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,6 @@ def load(self, spec_factories, item_factories):
Returns:
bool: True if the operation was successful, False otherwise
"""
self._toolbox.ui.textBrowser_eventlog.clear()
project_dict = load_project_dict(self.config_dir, self._logger)
if project_dict is None:
return False
Expand Down
59 changes: 43 additions & 16 deletions spinetoolbox/ui_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,43 +429,66 @@ def update_window_title(self):
return
self.setWindowTitle(f"{self._project.name} [{self._project.project_dir}][*] - Spine Toolbox")

@Slot()
def init_project(self, project_dir):
"""Initializes project at application start-up.
Opens the last project that was open when app was closed
(if enabled in Settings) or starts the app without a project.
@Slot(str)
def init_tasks(self, project_dir_from_args):
"""Performs tasks right after the main window is shown.
Args:
project_dir (str): project directory
project_dir_from_args (str, optional): initial project directory from command line arguments
"""
self._display_welcome_message()
if sys.version_info < (3, 9):
self._display_python_38_deprecation_message()
self.init_project(project_dir_from_args)

def _display_welcome_message(self):
"""Shows a welcome message in the Event log."""
p = os.path.join(f"{ONLINE_DOCUMENTATION_URL}", "getting_started.html")
getting_started_anchor = (
"<a style='color:#99CCFF;' title='"
+ p
+ f"' href='{ONLINE_DOCUMENTATION_URL}/getting_started.html'>Getting Started</a>"
)
welcome_msg = f"Welcome to Spine Toolbox! If you need help, please read the {getting_started_anchor} guide."
self.msg.emit(welcome_msg)

def _display_python_38_deprecation_message(self):
"""Shows Python 3.8 deprecation message in the event log."""
self.msg_warning.emit("Please upgrade your Python.")
self.msg_warning.emit(
f"Looks like you are running Python {sys.version_info[0]}.{sys.version_info[1]}. "
f"Support for <b>Python older than 3.9 </b> will be dropped in September 2024."
)

def init_project(self, project_dir):
"""Initializes project at application start-up.
Opens the project given on command line
or, if missing,
the last project that was open when app was closed (if enabled in Settings).
Args:
project_dir (str): project directory
"""
if not project_dir:
open_previous_project = int(self._qsettings.value("appSettings/openPreviousProject", defaultValue="0"))
# 2: Qt.CheckState.Checked, ie. open_previous_project==True
if (
open_previous_project != Qt.CheckState.Checked.value
): # 2: Qt.CheckState.Checked, ie. open_previous_project==True
self.msg.emit(welcome_msg)
):
return
# Get previous project (directory)
project_dir = self._qsettings.value("appSettings/previousProject", defaultValue="")
if not project_dir:
return
if os.path.isfile(project_dir) and project_dir.endswith(".proj"):
# Previous project was a .proj file -> Show welcome message instead
self.msg.emit(welcome_msg)
# Before we had project dirs, we opened .proj files.
# Now we just give up.
return
if not os.path.isdir(project_dir):
self.msg_error.emit(f"Cannot open previous project. Directory <b>{project_dir}</b> may have been moved.")
self.remove_path_from_recent_projects(project_dir)
return
self.open_project(project_dir)
self.open_project(project_dir, clear_event_log=False)

@Slot()
def new_project(self):
Expand Down Expand Up @@ -531,13 +554,14 @@ def create_project(self, proj_dir):
self.msg.emit(f"New project <b>{self._project.name}</b> is now open")

@Slot()
def open_project(self, load_dir=None):
def open_project(self, load_dir=None, clear_event_log=True):
"""Opens project from a selected or given directory.
Args:
load_dir (str, optional): Path to project base directory. If default value is used,
a file explorer dialog is opened where the user can select the
project to open.
clear_event_log (bool): True clears the Event log before opening the project
Returns:
bool: True when opening the project succeeded, False otherwise
Expand All @@ -558,7 +582,7 @@ def open_project(self, load_dir=None):
load_dir = QFileDialog.getExistingDirectory(self, caption="Open Spine Toolbox Project", dir=start_dir)
if not load_dir:
return False # Cancelled
if not self.restore_project(load_dir):
if not self.restore_project(load_dir, clear_event_log=clear_event_log):
if not self.undo_stack.isClean(): # If current project not saved, don't exit it
self.msg_warning.emit(f"Cancelled opening project {load_dir}. Current project has unsaved changes.")
return False
Expand All @@ -567,12 +591,13 @@ def open_project(self, load_dir=None):
return False
return True

def restore_project(self, project_dir, ask_confirmation=True):
def restore_project(self, project_dir, ask_confirmation=True, clear_event_log=True):
"""Initializes UI, Creates project, models, connections, etc., when opening a project.
Args:
project_dir (str): Project directory
ask_confirmation (bool): True closes the previous project with a confirmation box if user has enabled this
clear_event_log (bool): True clears the Event log before loading the project
Returns:
bool: True when restoring project succeeded, False otherwise
Expand All @@ -594,6 +619,8 @@ def restore_project(self, project_dir, ask_confirmation=True):
self._connect_project_signals()
self.update_window_title()
# Populate project model with project items
if clear_event_log:
self.ui.textBrowser_eventlog.clear()
success = self._project.load(self._item_specification_factories, self.item_factories)
if not success:
self.remove_path_from_recent_projects(self._project.project_dir)
Expand Down
24 changes: 13 additions & 11 deletions tests/mock_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@

def create_toolboxui():
"""Returns ToolboxUI, where QSettings among others has been mocked."""
with mock.patch(
"spinetoolbox.ui_main.QSettings.value") as mock_qsettings_value, mock.patch(
"spinetoolbox.ui_main.ToolboxUI.set_app_style") as mock_set_app_style, mock.patch(
"spinetoolbox.plugin_manager.PluginManager.load_installed_plugins"
):
with mock.patch("spinetoolbox.ui_main.QSettings.value") as mock_qsettings_value, mock.patch(
"spinetoolbox.ui_main.ToolboxUI.set_app_style"
) as mock_set_app_style, mock.patch("spinetoolbox.plugin_manager.PluginManager.load_installed_plugins"):
mock_qsettings_value.side_effect = qsettings_value_side_effect
mock_set_app_style.return_value = True
toolbox = ToolboxUI()
Expand All @@ -45,12 +43,16 @@ def create_toolboxui_with_project(project_dir):
"""Returns ToolboxUI with a project instance where
QSettings among others has been mocked."""
with mock.patch("spinetoolbox.ui_main.QSettings.value") as mock_qsettings_value, mock.patch(
"spinetoolbox.ui_main.ToolboxUI.set_app_style") as mock_set_app_style, mock.patch(
"spinetoolbox.ui_main.ToolboxUI.save_project"), mock.patch(
"spinetoolbox.ui_main.QSettings.setValue"), mock.patch(
"spinetoolbox.ui_main.QSettings.sync"), mock.patch(
"spinetoolbox.plugin_manager.PluginManager.load_installed_plugins"), mock.patch(
"spinetoolbox.ui_main.QScrollArea.setWidget"):
"spinetoolbox.ui_main.ToolboxUI.set_app_style"
) as mock_set_app_style, mock.patch("spinetoolbox.ui_main.ToolboxUI.save_project"), mock.patch(
"spinetoolbox.ui_main.QSettings.setValue"
), mock.patch(
"spinetoolbox.ui_main.QSettings.sync"
), mock.patch(
"spinetoolbox.plugin_manager.PluginManager.load_installed_plugins"
), mock.patch(
"spinetoolbox.ui_main.QScrollArea.setWidget"
):
mock_qsettings_value.side_effect = qsettings_value_side_effect
mock_set_app_style.return_value = True
toolbox = ToolboxUI()
Expand Down

0 comments on commit ee9508f

Please sign in to comment.