From f34cf709eb35dfca70dc135f6fdc8446c1036aa9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Mon, 31 Jul 2023 12:19:46 -0400
Subject: [PATCH 01/27] Update icons.py
---
sardes/config/icons.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sardes/config/icons.py b/sardes/config/icons.py
index dc0b610a..0774bd1c 100644
--- a/sardes/config/icons.py
+++ b/sardes/config/icons.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © SARDES Project Contributors
-# https://github.com/cgq-qgc/sardes
+# https://github.com/geo-stack/sardes
#
# This file is part of SARDES.
# Licensed under the terms of the GNU General Public License.
From 804a633177615e9218e4ff51ca2a8d1125fd1128 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Mon, 31 Jul 2023 12:19:51 -0400
Subject: [PATCH 02/27] Create updates.py
---
sardes/widgets/updates.py | 257 ++++++++++++++++++++++++++++++++++++++
1 file changed, 257 insertions(+)
create mode 100644 sardes/widgets/updates.py
diff --git a/sardes/widgets/updates.py b/sardes/widgets/updates.py
new file mode 100644
index 00000000..8bba2486
--- /dev/null
+++ b/sardes/widgets/updates.py
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright © SARDES Project Contributors
+# https://github.com/geo-stack/sardes
+#
+# This file is part of SARDES.
+# Licensed under the terms of the GNU General Public License.
+#
+# Copyright (c) 2017 Spyder Project Contributors
+# https://github.com/spyder-ide/spyder
+#
+# Some parts of this file is a derivative work of the Spyder project.
+# Licensed under the terms of the MIT License.
+# https://github.com/spyder-ide/spyder/master/spyder/workers/updates.py
+# https://github.com/spyder-ide/spyder/blob/master/spyder/utils/programs.py
+#
+# Copyright (C) 2013 The IPython Development Team
+# https://github.com/ipython/ipython
+#
+# See gwhat/__init__.py for more details.
+# -----------------------------------------------------------------------------
+
+# ---- Standard imports
+import re
+from distutils.version import LooseVersion
+
+# ---- Third party imports
+from qtpy.QtCore import QObject, Qt, QThread, Signal
+from qtpy.QtWidgets import QApplication, QMessageBox
+import requests
+
+# ---- Local imports
+from sardes import (
+ __version__, __releases_url__, __releases_api__,
+ __namever__, __project_url__)
+from sardes.config.icons import get_icon
+from sardes.config.locale import _
+
+
+class UpdatesManager(QObject):
+ """
+ Self contained manager that checks if updates are available on GitHub
+ and displays the ressults in a message box.
+ """
+
+ def __init__(self, parent=None):
+ super().__init__()
+
+ self.dialog_updates = UpdatesDialog(parent)
+ self._latest_release = None
+ self._update_available = None
+ self._show_only_if_update = False
+
+ self.thread_updates = QThread()
+
+ self.worker_updates = WorkerUpdates()
+ self.worker_updates.moveToThread(self.thread_updates)
+ self.worker_updates.sig_ready.connect(self._receive_updates_check)
+
+ self.thread_updates.started.connect(self.worker_updates.start)
+
+ def start_updates_check(self, show_only_if_update: bool = False):
+ """Check if updates are available."""
+ self._show_only_if_update = show_only_if_update
+ self.thread_updates.start()
+
+ def _receive_updates_check(self, update_available, latest_release, error):
+ """Receive results from an update check."""
+ self.thread_updates.quit()
+ if update_available is False and self._show_only_if_update is True:
+ return
+
+ if error is not None:
+ icn = QMessageBox.Warning
+ msg = error
+ else:
+ icn = QMessageBox.Information
+ if update_available:
+ msg = _(
+ "Sardes {} is available!
"
+ "This new version can be downloaded from our "
+ "Releases page.
"
+ ).format(latest_release, __releases_url__)
+ else:
+ url_m = __project_url__ + "/milestones"
+ url_t = __project_url__ + "/issues"
+ msg = _(
+ "{} is up to date
"
+ "Further information about Sardes releases are "
+ "available on our Releases page.
"
+ "The roadmap of the Sardes project can be consulted "
+ "on our Milestones page.
"
+ "Please help Sardes by reporting bugs or proposing "
+ "new features on our Issues Tracker.
"
+ ).format(__namever__, __releases_url__, url_m, url_t)
+
+ self.dialog_updates.setText(msg)
+ self.dialog_updates.setIcon(icn)
+ self.dialog_updates.exec_()
+
+
+class UpdatesDialog(QMessageBox):
+ """
+ Dialog to display update checks.
+ """
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self.setWindowTitle(_('Updates'))
+ self.setWindowIcon(get_icon('master'))
+ self.setMinimumSize(800, 700)
+ self.setWindowFlags(
+ Qt.Window | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint)
+
+ self.setStandardButtons(QMessageBox.Ok)
+ self.setDefaultButton(QMessageBox.Ok)
+
+
+class WorkerUpdates(QObject):
+ """
+ Worker that checks for releases using the Github API.
+ """
+ sig_ready = Signal(object, object, object)
+
+ def __init__(self):
+ super(WorkerUpdates, self).__init__()
+ self.error = None
+ self.latest_release = None
+ self.update_available = False
+
+ def start(self):
+ """Main method of the WorkerUpdates worker."""
+ self.update_available = False
+ self.latest_release = __version__
+ self.error = None
+
+ try:
+ page = requests.get(__releases_api__)
+ data = page.json()
+ except requests.exceptions.HTTPError:
+ self.error = _(
+ "Unable to retrieve information because of an http error.")
+ except requests.exceptions.ConnectionError:
+ self.error = (
+ "Unable to connect to the internet. Make "
+ "sure that your connection is working properly.")
+ except requests.exceptions.Timeout:
+ self.error = (
+ "Unable to retrieve information because the "
+ "connection timed out.")
+ except Exception:
+ self.error = (
+ "Unable to check for updates because of "
+ "an unexpected error.")
+
+ releases = [item['tag_name'] for item in data]
+ result = check_update_available(__version__, releases)
+ self.update_available, self.latest_release = result
+
+ self.sig_ready.emit(
+ self.update_available, self.latest_release, self.error)
+
+
+def check_update_available(version, releases):
+ """
+ Checks if there is an update available.
+
+ It takes as parameters the current version of GWHAT and a list of
+ valid cleaned releases in chronological order (what github api returns
+ by default). Example: ['2.3.4', '2.3.3' ...]
+
+ Copyright (c) Spyder Project Contributors
+ Licensed under the terms of the MIT License
+ """
+ if is_stable_version(version):
+ # Remove non stable versions from the list.
+ releases = [r for r in releases if is_stable_version(r)]
+
+ if len(releases) == 0:
+ return False, None
+
+ latest_release = releases[0]
+ if version.endswith('dev'):
+ return (False, latest_release)
+ else:
+ return (check_version(version, latest_release, '<'),
+ latest_release)
+
+
+def check_version(actver, version, cmp_op):
+ """
+ Check version string of an active module against a required version.
+
+ If dev/prerelease tags result in TypeError for string-number comparison,
+ it is assumed that the dependency is satisfied. Users on dev branches are
+ responsible for keeping their own packages up to date.
+
+ Copyright (C) 2013 The IPython Development Team
+ Licensed under the terms of the BSD License
+ """
+ if isinstance(version, tuple):
+ version = '.'.join([str(i) for i in version])
+
+ # Hacks needed so that LooseVersion understands that (for example)
+ # version = '3.0.0' is in fact bigger than actver = '3.0.0rc1'
+ if (is_stable_version(version) and not is_stable_version(actver) and
+ actver.startswith(version) and version != actver):
+ version = version + 'zz'
+ elif (is_stable_version(actver) and not is_stable_version(version) and
+ version.startswith(actver) and version != actver):
+ actver = actver + 'zz'
+
+ try:
+ if cmp_op == '>':
+ return LooseVersion(actver) > LooseVersion(version)
+ elif cmp_op == '>=':
+ return LooseVersion(actver) >= LooseVersion(version)
+ elif cmp_op == '=':
+ return LooseVersion(actver) == LooseVersion(version)
+ elif cmp_op == '<':
+ return LooseVersion(actver) < LooseVersion(version)
+ elif cmp_op == '<=':
+ return LooseVersion(actver) <= LooseVersion(version)
+ else:
+ return False
+ except TypeError:
+ return True
+
+
+def is_stable_version(version):
+ """
+ Returns wheter this is a stable version or not. A stable version has no
+ letters in the final component, but only numbers.
+
+ Stable version example: 1.2, 1.3.4, 1.0.5
+ Not stable version: 1.2alpha, 1.3.4beta, 0.1.0rc1, 3.0.0dev
+
+ Copyright (c) 2017 Spyder Project Contributors
+ Licensed under the terms of the MIT License
+ """
+ if not isinstance(version, tuple):
+ version = version.split('.')
+ last_part = version[-1]
+
+ if not re.search('[a-zA-Z]', last_part):
+ return True
+ else:
+ return False
+
+
+if __name__ == "__main__":
+ import sys
+ app = QApplication(sys.argv)
+ updates_manager = UpdatesManager()
+ updates_manager.start_updates_check(show_only_if_update=False)
+ sys.exit(app.exec_())
From e341ed7445089a603dfb541a12873c6e5de5fde5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Tue, 1 Aug 2023 21:40:34 -0400
Subject: [PATCH 03/27] Setup update manager in mainwindow
---
sardes/app/mainwindow.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/sardes/app/mainwindow.py b/sardes/app/mainwindow.py
index ea6eafef..ac72ca02 100644
--- a/sardes/app/mainwindow.py
+++ b/sardes/app/mainwindow.py
@@ -99,6 +99,10 @@ def __init__(self, splash=None, sys_capture_manager=None):
self.db_connection_manager)
print("Table models manager set up succesfully.")
+ # Setup the update manager.
+ from sardes.widgets.updates import UpdatesManager
+ self.updates_manager = UpdatesManager(parent=self)
+
self.setup()
def set_splash(self, message):
@@ -299,6 +303,10 @@ class Separator(object):
shortcut='Ctrl+Shift+I',
context=Qt.ApplicationShortcut
)
+ update_action = create_action(
+ self, _('Check for updates...'), icon='update',
+ triggered=lambda: self.updates_manager.start_updates_check()
+ )
exit_action = create_action(
self, _('Exit'), icon='exit', triggered=self.close,
shortcut='Ctrl+Shift+Q', context=Qt.ApplicationShortcut
From b23f01e53beec391733feb43811a1b812574045e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Tue, 1 Aug 2023 21:40:57 -0400
Subject: [PATCH 04/27] Reorder actions in menu
---
sardes/app/mainwindow.py | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/sardes/app/mainwindow.py b/sardes/app/mainwindow.py
index ac72ca02..6ba5ad56 100644
--- a/sardes/app/mainwindow.py
+++ b/sardes/app/mainwindow.py
@@ -266,6 +266,15 @@ class Separator(object):
triggered=self.confdialog.show
)
+ # Create show console action.
+ self.console_action = None
+ if self.console is not None:
+ self.console_action = create_action(
+ self, _('Sardes Console...'), icon='console',
+ shortcut='Ctrl+Shift+J', context=Qt.ApplicationShortcut,
+ triggered=self.console.show
+ )
+
# Create the panes and toolbars menus and actions
self.panes_menu = QMenu(_("Panes"), self)
self.panes_menu.setIcon(get_icon('panes'))
@@ -286,13 +295,6 @@ class Separator(object):
triggered=self.reset_window_layout)
# Create help related actions and menus.
- self.console_action = None
- if self.console is not None:
- self.console_action = create_action(
- self, _('Sardes Console...'), icon='console',
- shortcut='Ctrl+Shift+J', context=Qt.ApplicationShortcut,
- triggered=self.console.show
- )
report_action = create_action(
self, _('Report an issue...'), icon='bug',
shortcut='Ctrl+Shift+R', context=Qt.ApplicationShortcut,
@@ -314,11 +316,12 @@ class Separator(object):
# Add the actions and menus to the options menu.
options_menu_items = [
- self.lang_menu, preferences_action, Separator(),
- self.panes_menu, self.toolbars_menu,
+ self.lang_menu, preferences_action, self.console_action,
+ Separator(), self.panes_menu, self.toolbars_menu,
self.lock_dockwidgets_and_toolbars_action,
self.reset_window_layout_action, Separator(),
- self.console_action, report_action, about_action, exit_action
+ report_action, update_action, about_action,
+ Separator(), exit_action
]
for item in options_menu_items:
if isinstance(item, Separator):
From bcda1ceb12df5abd269bcb953e43671e95daa290 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Tue, 1 Aug 2023 21:41:07 -0400
Subject: [PATCH 05/27] Add update icons
---
sardes/config/icons.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/sardes/config/icons.py b/sardes/config/icons.py
index 0774bd1c..3fcac3f7 100644
--- a/sardes/config/icons.py
+++ b/sardes/config/icons.py
@@ -272,6 +272,9 @@
'undo': [
('mdi.undo-variant',),
{'color': ICON_COLOR}],
+ 'update': [
+ ('mdi.update',),
+ {'color': ICON_COLOR, 'scale_factor': 1.3}],
'update_blue': [
('mdi.update',),
{'color': BLUE, 'scale_factor': 1.3}],
From 281bf60e014281ba1520eeff82d2650a5ba3865b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Tue, 1 Aug 2023 21:41:35 -0400
Subject: [PATCH 06/27] Add a startup_check mode
---
sardes/widgets/updates.py | 72 ++++++++++++++++++++++++---------------
1 file changed, 45 insertions(+), 27 deletions(-)
diff --git a/sardes/widgets/updates.py b/sardes/widgets/updates.py
index 8bba2486..22194ee4 100644
--- a/sardes/widgets/updates.py
+++ b/sardes/widgets/updates.py
@@ -19,6 +19,7 @@
#
# See gwhat/__init__.py for more details.
# -----------------------------------------------------------------------------
+from __future__ import annotations
# ---- Standard imports
import re
@@ -26,7 +27,7 @@
# ---- Third party imports
from qtpy.QtCore import QObject, Qt, QThread, Signal
-from qtpy.QtWidgets import QApplication, QMessageBox
+from qtpy.QtWidgets import QApplication, QMessageBox, QCheckBox
import requests
# ---- Local imports
@@ -35,6 +36,7 @@
__namever__, __project_url__)
from sardes.config.icons import get_icon
from sardes.config.locale import _
+from sardes.config.main import CONF
class UpdatesManager(QObject):
@@ -47,9 +49,7 @@ def __init__(self, parent=None):
super().__init__()
self.dialog_updates = UpdatesDialog(parent)
- self._latest_release = None
- self._update_available = None
- self._show_only_if_update = False
+ self._startup_check = False
self.thread_updates = QThread()
@@ -59,16 +59,26 @@ def __init__(self, parent=None):
self.thread_updates.started.connect(self.worker_updates.start)
- def start_updates_check(self, show_only_if_update: bool = False):
+ def start_updates_check(self, startup_check: bool = False):
"""Check if updates are available."""
- self._show_only_if_update = show_only_if_update
+ self._startup_check = startup_check
self.thread_updates.start()
- def _receive_updates_check(self, update_available, latest_release, error):
+ def _receive_updates_check(self, releases: list[str], error: str):
"""Receive results from an update check."""
self.thread_updates.quit()
- if update_available is False and self._show_only_if_update is True:
- return
+
+ update_available, latest_release = check_update_available(
+ __version__, releases)
+
+ if self._startup_check:
+ if update_available is False:
+ return
+
+ last_shown_update = CONF.get(
+ 'main', 'last_shown_update', __version__)
+ if check_version(latest_release, last_shown_update, '<='):
+ return
if error is not None:
icn = QMessageBox.Warning
@@ -94,10 +104,18 @@ def _receive_updates_check(self, update_available, latest_release, error):
"new features on our Issues Tracker.
"
).format(__namever__, __releases_url__, url_m, url_t)
+ if self._startup_check:
+ # Add some space between text and checkbox.
+ msg += "
"
+ self.dialog_updates.chkbox.setVisible(self._startup_check)
+
self.dialog_updates.setText(msg)
self.dialog_updates.setIcon(icn)
self.dialog_updates.exec_()
+ if self.dialog_updates.chkbox.isChecked():
+ CONF.set('main', 'last_update_shown', latest_release)
+
class UpdatesDialog(QMessageBox):
"""
@@ -110,31 +128,32 @@ def __init__(self, parent=None):
self.setWindowTitle(_('Updates'))
self.setWindowIcon(get_icon('master'))
self.setMinimumSize(800, 700)
+
self.setWindowFlags(
Qt.Window | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint)
+ self.chkbox = QCheckBox(_("Do not show this message again."))
+ self.setCheckBox(self.chkbox)
+
self.setStandardButtons(QMessageBox.Ok)
self.setDefaultButton(QMessageBox.Ok)
class WorkerUpdates(QObject):
"""
- Worker that checks for releases using the Github API.
+ Worker that fetch available releases using the Github API.
"""
- sig_ready = Signal(object, object, object)
+ sig_ready = Signal(object, object)
def __init__(self):
super(WorkerUpdates, self).__init__()
- self.error = None
- self.latest_release = None
- self.update_available = False
+ self._error = None
+ self._releases = None
def start(self):
"""Main method of the WorkerUpdates worker."""
- self.update_available = False
- self.latest_release = __version__
- self.error = None
-
+ error = None
+ releases = []
try:
page = requests.get(__releases_api__)
data = page.json()
@@ -155,14 +174,13 @@ def start(self):
"an unexpected error.")
releases = [item['tag_name'] for item in data]
- result = check_update_available(__version__, releases)
- self.update_available, self.latest_release = result
- self.sig_ready.emit(
- self.update_available, self.latest_release, self.error)
+ self._error = error
+ self._releases = releases
+ self.sig_ready.emit(releases, error)
-def check_update_available(version, releases):
+def check_update_available(version: str, releases: list[str]):
"""
Checks if there is an update available.
@@ -173,13 +191,13 @@ def check_update_available(version, releases):
Copyright (c) Spyder Project Contributors
Licensed under the terms of the MIT License
"""
+ if len(releases) == 0:
+ return False, None
+
if is_stable_version(version):
# Remove non stable versions from the list.
releases = [r for r in releases if is_stable_version(r)]
- if len(releases) == 0:
- return False, None
-
latest_release = releases[0]
if version.endswith('dev'):
return (False, latest_release)
@@ -253,5 +271,5 @@ def is_stable_version(version):
import sys
app = QApplication(sys.argv)
updates_manager = UpdatesManager()
- updates_manager.start_updates_check(show_only_if_update=False)
+ updates_manager.start_updates_check(startup_check=False)
sys.exit(app.exec_())
From e02f62446d8d31bc21cad4a549e17b0e1103c4b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Tue, 1 Aug 2023 22:01:15 -0400
Subject: [PATCH 07/27] Check for updates on startup
---
sardes/app/mainwindow.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/sardes/app/mainwindow.py b/sardes/app/mainwindow.py
index 6ba5ad56..7e302a89 100644
--- a/sardes/app/mainwindow.py
+++ b/sardes/app/mainwindow.py
@@ -624,6 +624,8 @@ def show(self):
if self.databases_plugin.get_option('auto_connect_to_database'):
self.databases_plugin.connect_to_database()
+ self.updates_manager.start_updates_check(startup_check=True)
+
class ExceptHook(QObject):
"""
From 03ed91b154a6e75424d35734c9dcf58fe2b5c1d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Tue, 1 Aug 2023 22:02:24 -0400
Subject: [PATCH 08/27] Improve muted update logic
---
sardes/widgets/updates.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/sardes/widgets/updates.py b/sardes/widgets/updates.py
index 22194ee4..60b50298 100644
--- a/sardes/widgets/updates.py
+++ b/sardes/widgets/updates.py
@@ -70,15 +70,14 @@ def _receive_updates_check(self, releases: list[str], error: str):
update_available, latest_release = check_update_available(
__version__, releases)
+ muted_updates = CONF.get('main', 'muted_updates', [])
if self._startup_check:
if update_available is False:
return
-
- last_shown_update = CONF.get(
- 'main', 'last_shown_update', __version__)
- if check_version(latest_release, last_shown_update, '<='):
- return
+ for release in muted_updates:
+ if check_version(latest_release, release, '='):
+ return
if error is not None:
icn = QMessageBox.Warning
@@ -114,7 +113,9 @@ def _receive_updates_check(self, releases: list[str], error: str):
self.dialog_updates.exec_()
if self.dialog_updates.chkbox.isChecked():
- CONF.set('main', 'last_update_shown', latest_release)
+ muted_updates.append(latest_release)
+ muted_updates = list(set(muted_updates))
+ CONF.set('main', 'muted_updates', muted_updates)
class UpdatesDialog(QMessageBox):
From 43cd3ca61be7565d26a87c3defc2a6f4ff67b330 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Wed, 2 Aug 2023 22:35:12 -0400
Subject: [PATCH 09/27] Remove the 'v' from release tag
---
sardes/widgets/updates.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sardes/widgets/updates.py b/sardes/widgets/updates.py
index 60b50298..c48e9cad 100644
--- a/sardes/widgets/updates.py
+++ b/sardes/widgets/updates.py
@@ -174,7 +174,7 @@ def start(self):
"Unable to check for updates because of "
"an unexpected error.")
- releases = [item['tag_name'] for item in data]
+ releases = [item['tag_name'][1:] for item in data]
self._error = error
self._releases = releases
From adf28270d656834c340b73ee70d712403655fce7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Thu, 10 Aug 2023 11:33:48 -0400
Subject: [PATCH 10/27] Complete missing typehints and type checking
---
sardes/config/icons.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/sardes/config/icons.py b/sardes/config/icons.py
index 3fcac3f7..2fd87a35 100644
--- a/sardes/config/icons.py
+++ b/sardes/config/icons.py
@@ -6,7 +6,7 @@
# This file is part of SARDES.
# Licensed under the terms of the GNU General Public License.
# ----------------------------------------------------------------------------
-
+from __future__ import annotations
# ---- Standard imports
import os
@@ -331,7 +331,7 @@ def get_standard_icon(constant):
return style.standardIcon(constant)
-def get_standard_iconsize(constant):
+def get_standard_iconsize(constant: str) -> int:
"""
Return the standard size of various component of the gui.
@@ -342,3 +342,8 @@ def get_standard_iconsize(constant):
return style.pixelMetric(QStyle.PM_MessageBoxIconSize)
elif constant == 'small':
return style.pixelMetric(QStyle.PM_SmallIconSize)
+ else:
+ raise ValueError((
+ "Valid values for the 'constant' parameter are "
+ "'messagebox' or 'small', but '{}' was provided"
+ ).format(constant))
From 9d8aff3dbef4b38f8e19583bd2f7c58167012fa4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Thu, 10 Aug 2023 14:06:41 -0400
Subject: [PATCH 11/27] Complete missing typehint
---
sardes/config/icons.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sardes/config/icons.py b/sardes/config/icons.py
index 2fd87a35..69cd6fd4 100644
--- a/sardes/config/icons.py
+++ b/sardes/config/icons.py
@@ -319,7 +319,7 @@ def get_iconsize(size):
return QSize(*ICON_SIZES[size])
-def get_standard_icon(constant):
+def get_standard_icon(constant: str) -> QIcon:
"""
Return a QIcon of a standard pixmap.
From b2384f60168233f8e0218afcf468ef5b682db243 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Thu, 10 Aug 2023 14:07:30 -0400
Subject: [PATCH 12/27] Fix deprecated distutils.version usage
---
sardes/widgets/updates.py | 27 +++++++++------------------
1 file changed, 9 insertions(+), 18 deletions(-)
diff --git a/sardes/widgets/updates.py b/sardes/widgets/updates.py
index c48e9cad..e6a12857 100644
--- a/sardes/widgets/updates.py
+++ b/sardes/widgets/updates.py
@@ -23,7 +23,7 @@
# ---- Standard imports
import re
-from distutils.version import LooseVersion
+from packaging.version import Version
# ---- Third party imports
from qtpy.QtCore import QObject, Qt, QThread, Signal
@@ -76,7 +76,7 @@ def _receive_updates_check(self, releases: list[str], error: str):
if update_available is False:
return
for release in muted_updates:
- if check_version(latest_release, release, '='):
+ if check_version(latest_release, release, '=='):
return
if error is not None:
@@ -207,7 +207,7 @@ def check_update_available(version: str, releases: list[str]):
latest_release)
-def check_version(actver, version, cmp_op):
+def check_version(actver: str, version: str, cmp_op: str):
"""
Check version string of an active module against a required version.
@@ -221,26 +221,17 @@ def check_version(actver, version, cmp_op):
if isinstance(version, tuple):
version = '.'.join([str(i) for i in version])
- # Hacks needed so that LooseVersion understands that (for example)
- # version = '3.0.0' is in fact bigger than actver = '3.0.0rc1'
- if (is_stable_version(version) and not is_stable_version(actver) and
- actver.startswith(version) and version != actver):
- version = version + 'zz'
- elif (is_stable_version(actver) and not is_stable_version(version) and
- version.startswith(actver) and version != actver):
- actver = actver + 'zz'
-
try:
if cmp_op == '>':
- return LooseVersion(actver) > LooseVersion(version)
+ return Version(actver) > Version(version)
elif cmp_op == '>=':
- return LooseVersion(actver) >= LooseVersion(version)
- elif cmp_op == '=':
- return LooseVersion(actver) == LooseVersion(version)
+ return Version(actver) >= Version(version)
+ elif cmp_op == '==':
+ return Version(actver) == Version(version)
elif cmp_op == '<':
- return LooseVersion(actver) < LooseVersion(version)
+ return Version(actver) < Version(version)
elif cmp_op == '<=':
- return LooseVersion(actver) <= LooseVersion(version)
+ return Version(actver) <= Version(version)
else:
return False
except TypeError:
From 03a64ff48dab21ad4aba0bbd66d03d1ebbb6c1fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Thu, 10 Aug 2023 14:08:03 -0400
Subject: [PATCH 13/27] Use custom more meaningfull icons in messagebox
---
sardes/widgets/updates.py | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/sardes/widgets/updates.py b/sardes/widgets/updates.py
index e6a12857..0a2fb804 100644
--- a/sardes/widgets/updates.py
+++ b/sardes/widgets/updates.py
@@ -34,7 +34,8 @@
from sardes import (
__version__, __releases_url__, __releases_api__,
__namever__, __project_url__)
-from sardes.config.icons import get_icon
+from sardes.config.icons import (
+ get_icon, get_standard_iconsize, get_standard_icon)
from sardes.config.locale import _
from sardes.config.main import CONF
@@ -80,17 +81,18 @@ def _receive_updates_check(self, releases: list[str], error: str):
return
if error is not None:
- icn = QMessageBox.Warning
msg = error
+ icon = get_standard_icon('SP_MessageBoxWarning')
else:
- icn = QMessageBox.Information
if update_available:
+ icon = get_icon('update_blue')
msg = _(
"Sardes {} is available!
"
"This new version can be downloaded from our "
"Releases page.
"
).format(latest_release, __releases_url__)
else:
+ icon = get_icon('commit_changes')
url_m = __project_url__ + "/milestones"
url_t = __project_url__ + "/issues"
msg = _(
@@ -109,7 +111,8 @@ def _receive_updates_check(self, releases: list[str], error: str):
self.dialog_updates.chkbox.setVisible(self._startup_check)
self.dialog_updates.setText(msg)
- self.dialog_updates.setIcon(icn)
+ self.dialog_updates.setIconPixmap(
+ icon.pixmap(get_standard_iconsize('messagebox')))
self.dialog_updates.exec_()
if self.dialog_updates.chkbox.isChecked():
@@ -155,6 +158,7 @@ def start(self):
"""Main method of the WorkerUpdates worker."""
error = None
releases = []
+
try:
page = requests.get(__releases_api__)
data = page.json()
From 9ece1f59659051ff37d7447c936d3bcce64ec326 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Thu, 10 Aug 2023 14:29:27 -0400
Subject: [PATCH 14/27] Update file header
---
sardes/widgets/updates.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/sardes/widgets/updates.py b/sardes/widgets/updates.py
index 0a2fb804..87a0d82a 100644
--- a/sardes/widgets/updates.py
+++ b/sardes/widgets/updates.py
@@ -11,13 +11,12 @@
#
# Some parts of this file is a derivative work of the Spyder project.
# Licensed under the terms of the MIT License.
-# https://github.com/spyder-ide/spyder/master/spyder/workers/updates.py
-# https://github.com/spyder-ide/spyder/blob/master/spyder/utils/programs.py
#
# Copyright (C) 2013 The IPython Development Team
# https://github.com/ipython/ipython
#
-# See gwhat/__init__.py for more details.
+# Some parts of this file is a derivative work of the IPython project.
+# Licensed under the terms of the BSD License.
# -----------------------------------------------------------------------------
from __future__ import annotations
From a062d36c9e6bad8ca86e7344ba8bafdc0c183297 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Thu, 10 Aug 2023 16:16:31 -0400
Subject: [PATCH 15/27] SardesPlugin: make sure show_plugin is protected when
dockwindow is None
---
sardes/api/plugins.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/sardes/api/plugins.py b/sardes/api/plugins.py
index df1625c1..72eee304 100644
--- a/sardes/api/plugins.py
+++ b/sardes/api/plugins.py
@@ -529,10 +529,11 @@ def show_plugin(self):
This method is called by the mainwindow after it is shown.
"""
- is_visible = self.get_option('is_visible', False)
- is_docked = self.get_option('is_docked', True)
- if is_visible and not is_docked:
- self.dockwindow.undock()
+ if self.dockwindow is not None:
+ is_visible = self.get_option('is_visible', False)
+ is_docked = self.get_option('is_docked', True)
+ if is_visible and not is_docked:
+ self.dockwindow.undock()
def close_plugin(self):
"""
From 6b7252faac1c0ed0470cd9e4c0c4cafd32c53a4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Fri, 11 Aug 2023 09:29:06 -0400
Subject: [PATCH 16/27] Fix wrong docstring
---
sardes/app/mainwindow.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/sardes/app/mainwindow.py b/sardes/app/mainwindow.py
index 7e302a89..059f825a 100644
--- a/sardes/app/mainwindow.py
+++ b/sardes/app/mainwindow.py
@@ -613,7 +613,8 @@ def setup_internal_plugins(self):
def show(self):
"""
- Extend Qt method to call show_plugin on each installed plugin.
+ Extend base class method to perform specific actions of certain
+ plugins after the mainwindow has been shown.
"""
super().show()
From ff9f0e22f90f6e972e8f3c89e11158e5e5b25e59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Fri, 11 Aug 2023 09:34:00 -0400
Subject: [PATCH 17/27] Move updates module to app
---
sardes/app/mainwindow.py | 2 +-
sardes/{widgets => app}/updates.py | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename sardes/{widgets => app}/updates.py (100%)
diff --git a/sardes/app/mainwindow.py b/sardes/app/mainwindow.py
index 059f825a..6bcaea2d 100644
--- a/sardes/app/mainwindow.py
+++ b/sardes/app/mainwindow.py
@@ -100,7 +100,7 @@ def __init__(self, splash=None, sys_capture_manager=None):
print("Table models manager set up succesfully.")
# Setup the update manager.
- from sardes.widgets.updates import UpdatesManager
+ from sardes.app.updates import UpdatesManager
self.updates_manager = UpdatesManager(parent=self)
self.setup()
diff --git a/sardes/widgets/updates.py b/sardes/app/updates.py
similarity index 100%
rename from sardes/widgets/updates.py
rename to sardes/app/updates.py
From dc9f6f6dbe598b3fa3bf303fa0c02ba5fa2457ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Fri, 11 Aug 2023 10:48:57 -0400
Subject: [PATCH 18/27] Rework code to make testing easier
---
sardes/app/updates.py | 62 ++++++++++++++++++++++---------------------
1 file changed, 32 insertions(+), 30 deletions(-)
diff --git a/sardes/app/updates.py b/sardes/app/updates.py
index 87a0d82a..9d941b66 100644
--- a/sardes/app/updates.py
+++ b/sardes/app/updates.py
@@ -55,7 +55,8 @@ def __init__(self, parent=None):
self.worker_updates = WorkerUpdates()
self.worker_updates.moveToThread(self.thread_updates)
- self.worker_updates.sig_ready.connect(self._receive_updates_check)
+ self.worker_updates.sig_releases_fetched.connect(
+ self._receive_updates_check)
self.thread_updates.started.connect(self.worker_updates.start)
@@ -146,49 +147,50 @@ class WorkerUpdates(QObject):
"""
Worker that fetch available releases using the Github API.
"""
- sig_ready = Signal(object, object)
+ sig_releases_fetched = Signal(object, object)
def __init__(self):
super(WorkerUpdates, self).__init__()
- self._error = None
- self._releases = None
def start(self):
"""Main method of the WorkerUpdates worker."""
- error = None
- releases = []
-
- try:
- page = requests.get(__releases_api__)
- data = page.json()
- except requests.exceptions.HTTPError:
- self.error = _(
- "Unable to retrieve information because of an http error.")
- except requests.exceptions.ConnectionError:
- self.error = (
- "Unable to connect to the internet. Make "
- "sure that your connection is working properly.")
- except requests.exceptions.Timeout:
- self.error = (
- "Unable to retrieve information because the "
- "connection timed out.")
- except Exception:
- self.error = (
- "Unable to check for updates because of "
- "an unexpected error.")
+ releases, error = fetch_available_releases(__releases_api__)
+ self.sig_releases_fetched.emit(releases, error)
- releases = [item['tag_name'][1:] for item in data]
- self._error = error
- self._releases = releases
- self.sig_ready.emit(releases, error)
+def fetch_available_releases(url: str) -> (list[str], str):
+ """Retrieve the list of released versions available on GitHub."""
+ error = None
+ releases = []
+
+ try:
+ page = requests.get(__releases_api__)
+ data = page.json()
+ releases = [item['tag_name'][1:] for item in data]
+ except requests.exceptions.HTTPError:
+ error = _(
+ "Unable to retrieve information because of an http error.")
+ except requests.exceptions.ConnectionError:
+ error = _(
+ "Unable to connect to the internet. Make "
+ "sure that your connection is working properly.")
+ except requests.exceptions.Timeout:
+ error = _(
+ "Unable to retrieve information because the "
+ "connection timed out.")
+ except Exception:
+ error = _(
+ "Unable to check for updates because of "
+ "an unexpected error.")
+
+ return releases, error
def check_update_available(version: str, releases: list[str]):
"""
Checks if there is an update available.
- It takes as parameters the current version of GWHAT and a list of
+ It takes as parameters the current version of Sardes and a list of
valid cleaned releases in chronological order (what github api returns
by default). Example: ['2.3.4', '2.3.3' ...]
From c402d95d1e6c2e5558c3d41cfed2e57dfc52b286 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Fri, 11 Aug 2023 17:18:57 -0400
Subject: [PATCH 19/27] Remove not needed WorkerUpdates __init__
---
sardes/app/updates.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/sardes/app/updates.py b/sardes/app/updates.py
index 9d941b66..b348828e 100644
--- a/sardes/app/updates.py
+++ b/sardes/app/updates.py
@@ -149,9 +149,6 @@ class WorkerUpdates(QObject):
"""
sig_releases_fetched = Signal(object, object)
- def __init__(self):
- super(WorkerUpdates, self).__init__()
-
def start(self):
"""Main method of the WorkerUpdates worker."""
releases, error = fetch_available_releases(__releases_api__)
From b91a7af504b387f7c66678c55276ac3e1fb705bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Fri, 11 Aug 2023 17:19:42 -0400
Subject: [PATCH 20/27] Make sure latest release is always returned
---
sardes/app/updates.py | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/sardes/app/updates.py b/sardes/app/updates.py
index b348828e..d5632e42 100644
--- a/sardes/app/updates.py
+++ b/sardes/app/updates.py
@@ -183,7 +183,7 @@ def fetch_available_releases(url: str) -> (list[str], str):
return releases, error
-def check_update_available(version: str, releases: list[str]):
+def check_update_available(version: str, releases: list[str]) -> (bool, str):
"""
Checks if there is an update available.
@@ -201,12 +201,8 @@ def check_update_available(version: str, releases: list[str]):
# Remove non stable versions from the list.
releases = [r for r in releases if is_stable_version(r)]
- latest_release = releases[0]
- if version.endswith('dev'):
- return (False, latest_release)
- else:
- return (check_version(version, latest_release, '<'),
- latest_release)
+ latest_release = str(max([Version(r) for r in releases]))
+ return check_version(version, latest_release, '<'), latest_release
def check_version(actver: str, version: str, cmp_op: str):
From f06624f2432e0a726c0b9816fbec4875613bb033 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Fri, 11 Aug 2023 17:20:41 -0400
Subject: [PATCH 21/27] Simplify some variable names
---
sardes/app/updates.py | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/sardes/app/updates.py b/sardes/app/updates.py
index d5632e42..ad2f082f 100644
--- a/sardes/app/updates.py
+++ b/sardes/app/updates.py
@@ -48,26 +48,26 @@ class UpdatesManager(QObject):
def __init__(self, parent=None):
super().__init__()
- self.dialog_updates = UpdatesDialog(parent)
self._startup_check = False
+ self.dialog = UpdatesDialog(parent)
- self.thread_updates = QThread()
+ self.thread = QThread()
- self.worker_updates = WorkerUpdates()
- self.worker_updates.moveToThread(self.thread_updates)
- self.worker_updates.sig_releases_fetched.connect(
+ self.worker = WorkerUpdates()
+ self.worker.moveToThread(self.thread)
+ self.worker.sig_releases_fetched.connect(
self._receive_updates_check)
- self.thread_updates.started.connect(self.worker_updates.start)
+ self.thread.started.connect(self.worker.start)
def start_updates_check(self, startup_check: bool = False):
"""Check if updates are available."""
self._startup_check = startup_check
- self.thread_updates.start()
+ self.thread.start()
def _receive_updates_check(self, releases: list[str], error: str):
"""Receive results from an update check."""
- self.thread_updates.quit()
+ self.thread.quit()
update_available, latest_release = check_update_available(
__version__, releases)
From f3efce83e000880f5d4bbc3d8841c97a113d36e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Fri, 11 Aug 2023 17:21:34 -0400
Subject: [PATCH 22/27] Update updates.py
---
sardes/app/updates.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/sardes/app/updates.py b/sardes/app/updates.py
index ad2f082f..290c6048 100644
--- a/sardes/app/updates.py
+++ b/sardes/app/updates.py
@@ -108,14 +108,14 @@ def _receive_updates_check(self, releases: list[str], error: str):
if self._startup_check:
# Add some space between text and checkbox.
msg += "
"
- self.dialog_updates.chkbox.setVisible(self._startup_check)
+ self.dialog.chkbox.setVisible(self._startup_check)
- self.dialog_updates.setText(msg)
- self.dialog_updates.setIconPixmap(
+ self.dialog.setText(msg)
+ self.dialog.setIconPixmap(
icon.pixmap(get_standard_iconsize('messagebox')))
- self.dialog_updates.exec_()
+ self.dialog.exec_()
- if self.dialog_updates.chkbox.isChecked():
+ if self.dialog.chkbox.isChecked():
muted_updates.append(latest_release)
muted_updates = list(set(muted_updates))
CONF.set('main', 'muted_updates', muted_updates)
From b292226c34b38eca1f9e63a46d8c91d553cda721 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Sat, 12 Aug 2023 13:49:35 -0400
Subject: [PATCH 23/27] Create test_updates.py
---
sardes/app/tests/test_updates.py | 159 +++++++++++++++++++++++++++++++
1 file changed, 159 insertions(+)
create mode 100644 sardes/app/tests/test_updates.py
diff --git a/sardes/app/tests/test_updates.py b/sardes/app/tests/test_updates.py
new file mode 100644
index 00000000..89204662
--- /dev/null
+++ b/sardes/app/tests/test_updates.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright © GWHAT Project Contributors
+# https://github.com/jnsebgosselin/gwhat
+#
+# This file is part of GWHAT (Ground-Water Hydrograph Analysis Toolbox).
+# Licensed under the terms of the GNU General Public License.
+# -----------------------------------------------------------------------------
+
+# ---- Standard imports
+import os
+os.environ['SARDES_PYTEST'] = 'True'
+
+# ---- Third parties imports
+import pytest
+
+# ---- Local imports
+from sardes.app.updates import UpdatesManager, QMessageBox
+from sardes.config.main import CONF
+
+
+# =============================================================================
+# ---- Fixtures
+# =============================================================================
+@pytest.fixture
+def updates_manager(qtbot):
+ CONF.reset_to_defaults()
+ updates_manager = UpdatesManager()
+
+ updates_manager.dialog.setModal(False)
+ assert updates_manager.dialog.isVisible() is False
+
+ return updates_manager
+
+
+# =============================================================================
+# ---- Tests
+# =============================================================================
+def test_update_available(updates_manager, qtbot, mocker):
+ """
+ Assert that the worker to check for updates on the GitHub API is
+ working as expected when an update is available.
+ """
+ mocker.patch('sardes.app.updates.__version__', '1.1.0rc1')
+ mocker.patch(
+ 'sardes.app.updates.fetch_available_releases',
+ return_value=(['0.9.0', '1.1.0rc2', '1.0.0'], None)
+ )
+ msgbox_patcher = mocker.patch.object(
+ QMessageBox, 'exec_', return_value=True)
+
+ # Note that since the current version is not stable, the '1.1.0rc2
+ # update should be proposed to the user.
+
+ with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ updates_manager.start_updates_check()
+
+ assert msgbox_patcher.call_count == 1
+ assert updates_manager.dialog.chkbox.isVisible() is False
+ assert 'Sardes 1.1.0rc2 is available!' in updates_manager.dialog.text()
+ updates_manager.dialog.close()
+
+ # Test that this is working also as expected during STARTUP.
+ with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ updates_manager.dialog.chkbox.setChecked(True)
+ updates_manager.start_updates_check(startup_check=True)
+
+ assert msgbox_patcher.call_count == 2
+ assert updates_manager.dialog.chkbox.isVisible() is True
+ assert 'Sardes 1.1.0rc2 is available!' in updates_manager.dialog.text()
+ updates_manager.dialog.close()
+
+ # Assert the update is muted as expected because the checkbox was checked
+ # by the user on last show.
+ with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ updates_manager.start_updates_check(startup_check=True)
+
+ assert msgbox_patcher.call_count == 2
+
+
+def test_no_update_available(updates_manager, qtbot, mocker):
+ """
+ Assert that the worker to check for updates on the GitHub API is
+ working as expected when no update is available.
+ """
+ mocker.patch('sardes.app.updates.__version__', '1.1.0')
+ mocker.patch(
+ 'sardes.app.updates.fetch_available_releases',
+ return_value=(['0.9.0', '1.1.0rc2', '1.0.0'], None)
+ )
+
+ # Note that since the current version is stable, the '1.1.0rc2 update
+ # should not be proposed to the user.
+
+
+
+# assert updates_manager.dialog.isVisible() is True
+# assert updates_manager.dialog.chkbox.isVisible() is False
+# assert 'is up to date' in updates_manager.dialog.text()
+# updates_manager.dialog.close()
+
+# # Assert the updates manager is working as expected when an update
+# # is available.
+
+
+# mocker.patch('sardes.app.updates.__version__', '1.1.0rc1')
+
+# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+# updates_manager.start_updates_check()
+
+
+# # Assert the updates manager is working as expected when there is
+# # an error.
+# fetch_patcher.return_value = (['0.9.0', '1.1.0rc2', '1.0.0'], 'Some error')
+
+# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+# updates_manager.start_updates_check()
+
+# assert updates_manager.dialog.isVisible() is True
+# assert updates_manager.dialog.chkbox.isVisible() is False
+# assert 'Some error' in updates_manager.dialog.text()
+# updates_manager.dialog.close()
+
+
+# def test_updates_manager_startup(updates_manager, qtbot, mocker):
+# """
+# Assert that the worker to check for updates on the GitHub API is
+# working as expected when on Sardes startup.
+# """
+# mocker.patch('sardes.app.updates.__version__', '1.0.0')
+
+# # Assert the updates manager is working as expected when up-to-date.
+# fetch_patcher = mocker.patch(
+# 'sardes.app.updates.fetch_available_releases',
+# return_value=(['0.9.0', '1.0.0'], None)
+# )
+
+# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+# updates_manager.start_updates_check(startup_check=True)
+
+# assert updates_manager.dialog.isVisible() is False
+
+# # Assert the updates manager is working as expected when an error occured.
+# fetch_patcher.return_value = (['0.9.0', '1.0.0'], 'Some error')
+
+# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+# updates_manager.start_updates_check(startup_check=True)
+
+# assert updates_manager.dialog.isVisible() is False
+
+# # Assert the updates manager is working as expected when an update
+# # is available.
+# fetch_patcher.return_value = (['0.9.0', '1.1.0', '1.0.0'], None)
+
+
+
+
+if __name__ == "__main__":
+ pytest.main(['-x', __file__, '-v', '-rw'])
From f381cdebc3d729eaf4642ef060d59cb92d31ab58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Sat, 12 Aug 2023 14:21:02 -0400
Subject: [PATCH 24/27] Rework test_update_available for modal dialog
---
sardes/app/tests/test_updates.py | 34 ++++++++++++++------------------
1 file changed, 15 insertions(+), 19 deletions(-)
diff --git a/sardes/app/tests/test_updates.py b/sardes/app/tests/test_updates.py
index 89204662..418f1afc 100644
--- a/sardes/app/tests/test_updates.py
+++ b/sardes/app/tests/test_updates.py
@@ -13,6 +13,7 @@
# ---- Third parties imports
import pytest
+from qtpy.QtCore import QTimer
# ---- Local imports
from sardes.app.updates import UpdatesManager, QMessageBox
@@ -40,42 +41,37 @@ def test_update_available(updates_manager, qtbot, mocker):
"""
Assert that the worker to check for updates on the GitHub API is
working as expected when an update is available.
+
+ Note that since we are forcing the current version to a not stable version,
+ the '1.1.0rc2 update should be proposed to the user.
"""
mocker.patch('sardes.app.updates.__version__', '1.1.0rc1')
mocker.patch(
'sardes.app.updates.fetch_available_releases',
return_value=(['0.9.0', '1.1.0rc2', '1.0.0'], None)
)
- msgbox_patcher = mocker.patch.object(
- QMessageBox, 'exec_', return_value=True)
- # Note that since the current version is not stable, the '1.1.0rc2
- # update should be proposed to the user.
+ def handle_dialog(on_startup: bool):
+ assert updates_manager.dialog.isVisible() is True
+ assert updates_manager.dialog.chkbox.isVisible() == on_startup
+ assert 'Sardes 1.1.0rc2 is available!' in updates_manager.dialog.text()
+ updates_manager.dialog.close()
with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ QTimer.singleShot(300, lambda: handle_dialog(on_startup=False))
updates_manager.start_updates_check()
- assert msgbox_patcher.call_count == 1
- assert updates_manager.dialog.chkbox.isVisible() is False
- assert 'Sardes 1.1.0rc2 is available!' in updates_manager.dialog.text()
- updates_manager.dialog.close()
-
- # Test that this is working also as expected during STARTUP.
+ # Test that this is working also as expected during STARTUP and mute
+ # the message on next startups.
with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
updates_manager.dialog.chkbox.setChecked(True)
+ QTimer.singleShot(300, lambda: handle_dialog(on_startup=True))
updates_manager.start_updates_check(startup_check=True)
- assert msgbox_patcher.call_count == 2
- assert updates_manager.dialog.chkbox.isVisible() is True
- assert 'Sardes 1.1.0rc2 is available!' in updates_manager.dialog.text()
- updates_manager.dialog.close()
-
- # Assert the update is muted as expected because the checkbox was checked
- # by the user on last show.
+ # Assert the update is muted as expected.
with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
updates_manager.start_updates_check(startup_check=True)
-
- assert msgbox_patcher.call_count == 2
+ assert updates_manager.dialog.isVisible() is False
def test_no_update_available(updates_manager, qtbot, mocker):
From 75f3dd37ddcbd1560ef657cab632d375f5a1a020 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Sat, 12 Aug 2023 14:23:26 -0400
Subject: [PATCH 25/27] Complete test_no_update_available
---
sardes/app/tests/test_updates.py | 31 +++++++++++++++----------------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/sardes/app/tests/test_updates.py b/sardes/app/tests/test_updates.py
index 418f1afc..35b31b17 100644
--- a/sardes/app/tests/test_updates.py
+++ b/sardes/app/tests/test_updates.py
@@ -78,6 +78,9 @@ def test_no_update_available(updates_manager, qtbot, mocker):
"""
Assert that the worker to check for updates on the GitHub API is
working as expected when no update is available.
+
+ Note that since we are forcing the current version to a stable version,
+ the '1.1.0rc2 update should be proposed to the user.
"""
mocker.patch('sardes.app.updates.__version__', '1.1.0')
mocker.patch(
@@ -85,24 +88,20 @@ def test_no_update_available(updates_manager, qtbot, mocker):
return_value=(['0.9.0', '1.1.0rc2', '1.0.0'], None)
)
- # Note that since the current version is stable, the '1.1.0rc2 update
- # should not be proposed to the user.
-
-
-
-# assert updates_manager.dialog.isVisible() is True
-# assert updates_manager.dialog.chkbox.isVisible() is False
-# assert 'is up to date' in updates_manager.dialog.text()
-# updates_manager.dialog.close()
-
-# # Assert the updates manager is working as expected when an update
-# # is available.
-
+ def handle_dialog(on_startup: bool):
+ assert updates_manager.dialog.isVisible() is True
+ assert updates_manager.dialog.chkbox.isVisible() == on_startup
+ assert 'is up to date' in updates_manager.dialog.text()
+ updates_manager.dialog.close()
-# mocker.patch('sardes.app.updates.__version__', '1.1.0rc1')
+ with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ QTimer.singleShot(300, lambda: handle_dialog(on_startup=False))
+ updates_manager.start_updates_check()
-# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
-# updates_manager.start_updates_check()
+ # Test that the 'up-to-date' message is not shown during STARTUP.
+ with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ updates_manager.start_updates_check(startup_check=True)
+ assert updates_manager.dialog.isVisible() is False
# # Assert the updates manager is working as expected when there is
From 1c455b6fece21bb724003bcb8136c1fcdfba5ff7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Sat, 12 Aug 2023 14:59:27 -0400
Subject: [PATCH 26/27] Complete test_update_error
---
sardes/app/tests/test_updates.py | 65 +++++++++++---------------------
sardes/app/updates.py | 8 +++-
2 files changed, 29 insertions(+), 44 deletions(-)
diff --git a/sardes/app/tests/test_updates.py b/sardes/app/tests/test_updates.py
index 35b31b17..f9830010 100644
--- a/sardes/app/tests/test_updates.py
+++ b/sardes/app/tests/test_updates.py
@@ -16,7 +16,7 @@
from qtpy.QtCore import QTimer
# ---- Local imports
-from sardes.app.updates import UpdatesManager, QMessageBox
+from sardes.app.updates import UpdatesManager
from sardes.config.main import CONF
@@ -28,7 +28,6 @@ def updates_manager(qtbot):
CONF.reset_to_defaults()
updates_manager = UpdatesManager()
- updates_manager.dialog.setModal(False)
assert updates_manager.dialog.isVisible() is False
return updates_manager
@@ -104,50 +103,30 @@ def handle_dialog(on_startup: bool):
assert updates_manager.dialog.isVisible() is False
-# # Assert the updates manager is working as expected when there is
-# # an error.
-# fetch_patcher.return_value = (['0.9.0', '1.1.0rc2', '1.0.0'], 'Some error')
-
-# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
-# updates_manager.start_updates_check()
-
-# assert updates_manager.dialog.isVisible() is True
-# assert updates_manager.dialog.chkbox.isVisible() is False
-# assert 'Some error' in updates_manager.dialog.text()
-# updates_manager.dialog.close()
-
-
-# def test_updates_manager_startup(updates_manager, qtbot, mocker):
-# """
-# Assert that the worker to check for updates on the GitHub API is
-# working as expected when on Sardes startup.
-# """
-# mocker.patch('sardes.app.updates.__version__', '1.0.0')
-
-# # Assert the updates manager is working as expected when up-to-date.
-# fetch_patcher = mocker.patch(
-# 'sardes.app.updates.fetch_available_releases',
-# return_value=(['0.9.0', '1.0.0'], None)
-# )
-
-# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
-# updates_manager.start_updates_check(startup_check=True)
-
-# assert updates_manager.dialog.isVisible() is False
-
-# # Assert the updates manager is working as expected when an error occured.
-# fetch_patcher.return_value = (['0.9.0', '1.0.0'], 'Some error')
-
-# with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
-# updates_manager.start_updates_check(startup_check=True)
-
-# assert updates_manager.dialog.isVisible() is False
+def test_update_error(updates_manager, qtbot, mocker):
+ """
+ Assert that the worker to check for updates on the GitHub API is
+ working as expected when there is an error.
+ """
+ mocker.patch(
+ 'sardes.app.updates.fetch_available_releases',
+ return_value=(['0.9.0', '1.1.0rc2', '1.0.0'], 'some error')
+ )
-# # Assert the updates manager is working as expected when an update
-# # is available.
-# fetch_patcher.return_value = (['0.9.0', '1.1.0', '1.0.0'], None)
+ def handle_dialog(on_startup: bool):
+ assert updates_manager.dialog.isVisible() is True
+ assert updates_manager.dialog.chkbox.isVisible() == on_startup
+ assert 'some error' in updates_manager.dialog.text()
+ updates_manager.dialog.close()
+ with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ QTimer.singleShot(300, lambda: handle_dialog(on_startup=False))
+ updates_manager.start_updates_check()
+ # Test that the error message is not shown during STARTUP.
+ with qtbot.waitSignal(updates_manager.worker.sig_releases_fetched):
+ updates_manager.start_updates_check(startup_check=True)
+ assert updates_manager.dialog.isVisible() is False
if __name__ == "__main__":
diff --git a/sardes/app/updates.py b/sardes/app/updates.py
index 290c6048..493eb5d4 100644
--- a/sardes/app/updates.py
+++ b/sardes/app/updates.py
@@ -47,6 +47,7 @@ class UpdatesManager(QObject):
def __init__(self, parent=None):
super().__init__()
+ self._is_checking_for_updates = False
self._startup_check = False
self.dialog = UpdatesDialog(parent)
@@ -62,6 +63,7 @@ def __init__(self, parent=None):
def start_updates_check(self, startup_check: bool = False):
"""Check if updates are available."""
+ self._is_checking_for_updates = True
self._startup_check = startup_check
self.thread.start()
@@ -74,10 +76,12 @@ def _receive_updates_check(self, releases: list[str], error: str):
muted_updates = CONF.get('main', 'muted_updates', [])
if self._startup_check:
- if update_available is False:
+ if update_available is False or error is not None:
+ self._is_checking_for_updates = False
return
for release in muted_updates:
if check_version(latest_release, release, '=='):
+ self._is_checking_for_updates = False
return
if error is not None:
@@ -120,6 +124,8 @@ def _receive_updates_check(self, releases: list[str], error: str):
muted_updates = list(set(muted_updates))
CONF.set('main', 'muted_updates', muted_updates)
+ self._is_checking_for_updates = False
+
class UpdatesDialog(QMessageBox):
"""
From fc7ba546083c8c618989b010b7c224527ce3d333 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?=
Date: Sat, 12 Aug 2023 15:03:23 -0400
Subject: [PATCH 27/27] Cleanup code
---
sardes/app/tests/test_updates.py | 5 +++--
sardes/app/updates.py | 6 ------
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/sardes/app/tests/test_updates.py b/sardes/app/tests/test_updates.py
index f9830010..8c05f0de 100644
--- a/sardes/app/tests/test_updates.py
+++ b/sardes/app/tests/test_updates.py
@@ -27,10 +27,11 @@
def updates_manager(qtbot):
CONF.reset_to_defaults()
updates_manager = UpdatesManager()
-
assert updates_manager.dialog.isVisible() is False
+ yield updates_manager
- return updates_manager
+ # To avoid: Thread: Destroyed while thread is still running.
+ qtbot.wait(300)
# =============================================================================
diff --git a/sardes/app/updates.py b/sardes/app/updates.py
index 493eb5d4..ee8c09e1 100644
--- a/sardes/app/updates.py
+++ b/sardes/app/updates.py
@@ -47,7 +47,6 @@ class UpdatesManager(QObject):
def __init__(self, parent=None):
super().__init__()
- self._is_checking_for_updates = False
self._startup_check = False
self.dialog = UpdatesDialog(parent)
@@ -63,7 +62,6 @@ def __init__(self, parent=None):
def start_updates_check(self, startup_check: bool = False):
"""Check if updates are available."""
- self._is_checking_for_updates = True
self._startup_check = startup_check
self.thread.start()
@@ -77,11 +75,9 @@ def _receive_updates_check(self, releases: list[str], error: str):
if self._startup_check:
if update_available is False or error is not None:
- self._is_checking_for_updates = False
return
for release in muted_updates:
if check_version(latest_release, release, '=='):
- self._is_checking_for_updates = False
return
if error is not None:
@@ -124,8 +120,6 @@ def _receive_updates_check(self, releases: list[str], error: str):
muted_updates = list(set(muted_updates))
CONF.set('main', 'muted_updates', muted_updates)
- self._is_checking_for_updates = False
-
class UpdatesDialog(QMessageBox):
"""