From 20671794a51292920113cd346e046ae8c8978cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= <goanpeca@gmail.com> Date: Thu, 14 Nov 2024 23:52:07 -0500 Subject: [PATCH] Add napari version update checker --- napari_plugin_manager/qt_check_updates.py | 134 ++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 napari_plugin_manager/qt_check_updates.py diff --git a/napari_plugin_manager/qt_check_updates.py b/napari_plugin_manager/qt_check_updates.py new file mode 100644 index 0000000..9c4365f --- /dev/null +++ b/napari_plugin_manager/qt_check_updates.py @@ -0,0 +1,134 @@ +import json +import os +import sys +from concurrent.futures import ThreadPoolExecutor +from contextlib import suppress +from datetime import date +from functools import lru_cache +from urllib.error import HTTPError, URLError +from urllib.request import urlopen + +import packaging +import packaging.version +from napari import __version__ +from napari._qt.qthreading import create_worker +from napari.utils.notifications import show_warning +from qtpy.QtCore import QObject +from qtpy.QtWidgets import QMessageBox +from superqt import ensure_main_thread + +IGNORE_DAYS = 21 +IGNORE_FILE = "ignore.txt" + + +@lru_cache +def github_tags(): + url = 'https://api.github.com/repos/napari/napari/tags' + with urlopen(url) as r: + data = json.load(r) + + versions = [] + for item in data: + version = item.get('name', None) + if version: + if version.startswith('v'): + version = version[1:] + + versions.append(version) + + return list(reversed(versions)) + + +@lru_cache +def conda_forge_releases(): + url = 'https://api.anaconda.org/package/conda-forge/napari/' + with urlopen(url) as r: + data = json.load(r) + versions = data.get('versions', []) + return versions + + +def get_latest_version(): + """Check latest version between tags and conda forge.""" + try: + with ThreadPoolExecutor() as executor: + tags = executor.submit(github_tags) + cf = executor.submit(conda_forge_releases) + + gh_tags = tags.result() + cf_versions = cf.result() + except (HTTPError, URLError): + show_warning( + 'Plugin manager: There seems to be an issue with network connectivity. ' + ) + return + + latest_version = packaging.version.parse(cf_versions[-1]) + latest_tag = packaging.version.parse(gh_tags[-1]) + if latest_version > latest_tag: + yield latest_version + else: + yield latest_tag + + +class UpdateChecker(QObject): + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self._current_version = packaging.version.parse(__version__) + self._latest_version = None + self._worker = None + self._base_folder = sys.prefix + + def check(self): + + if os.path.exists(os.path.join(self._base_folder, IGNORE_FILE)): + with ( + open( + os.path.join(self._base_folder, IGNORE_FILE), + encoding="utf-8", + ) as f_p, + suppress(ValueError), + ): + old_date = date.fromisoformat(f_p.read()) + if (date.today() - old_date).days < IGNORE_DAYS: + return + + os.remove(os.path.join(self._base_folder, IGNORE_FILE)) + + self._worker = create_worker(get_latest_version) + self._worker.yielded.connect(self.show_version_info) + self._worker.start() + + @ensure_main_thread + def show_version_info(self, latest_version): + my_version = self._current_version + remote_version = latest_version + if remote_version > my_version: + message = QMessageBox( + QMessageBox.Icon.Information, + "New release", + f"You use outdated version of napari. " + f"Your version is {my_version} and current is {remote_version}.", + QMessageBox.StandardButton.Ok + | QMessageBox.StandardButton.Ignore, + ) + + if message.exec_() == QMessageBox.StandardButton.Ignore: + os.makedirs(self._base_folder, exist_ok=True) + with open( + os.path.join(self._base_folder, IGNORE_FILE), + "w", + encoding="utf-8", + ) as f_p: + f_p.write(date.today().isoformat()) + + +if __name__ == '__main__': + from qtpy.QtWidgets import QApplication + + app = QApplication([]) + checker = UpdateChecker() + checker.check() + sys.exit(app.exec_())