Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add napari version update checker #114

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions napari_plugin_manager/napari.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: napari-plugin-manager
display_name: napari plugin manager
contributions:
commands:
- id: napari-plugin-manager.UpdateChecker
python_name: napari_plugin_manager.qt_update_checker:UpdateChecker
title: napari-plugin-manager.UpdateChecker

widgets:
- command: napari-plugin-manager.UpdateChecker
display_name: Check updates

175 changes: 175 additions & 0 deletions napari_plugin_manager/qt_update_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
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

Check warning on line 9 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L1-L9

Added lines #L1 - L9 were not covered by tests

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 QTimer
from qtpy.QtWidgets import (

Check warning on line 17 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L11-L17

Added lines #L11 - L17 were not covered by tests
QLabel,
QMessageBox,
QPushButton,
QVBoxLayout,
QWidget,
)
from superqt import ensure_main_thread

Check warning on line 24 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L24

Added line #L24 was not covered by tests

from napari_plugin_manager.qt_plugin_dialog import ON_BUNDLE

Check warning on line 26 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L26

Added line #L26 was not covered by tests

IGNORE_DAYS = 21
IGNORE_FILE = "ignore.txt"

Check warning on line 29 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L28-L29

Added lines #L28 - L29 were not covered by tests


@lru_cache
def github_tags():
url = 'https://api.github.com/repos/napari/napari/tags'
with urlopen(url) as r:
data = json.load(r)

Check warning on line 36 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L32-L36

Added lines #L32 - L36 were not covered by tests

versions = []
for item in data:
version = item.get('name', None)
if version:
if version.startswith('v'):
version = version[1:]

Check warning on line 43 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L38-L43

Added lines #L38 - L43 were not covered by tests

versions.append(version)

Check warning on line 45 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L45

Added line #L45 was not covered by tests

return list(reversed(versions))

Check warning on line 47 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L47

Added line #L47 was not covered by tests


@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

Check warning on line 56 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L50-L56

Added lines #L50 - L56 were not covered by tests


def get_latest_version():

Check warning on line 59 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L59

Added line #L59 was not covered by tests
"""Check latest version between tags and conda forge."""
try:
with ThreadPoolExecutor() as executor:
tags = executor.submit(github_tags)
cf = executor.submit(conda_forge_releases)

Check warning on line 64 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L61-L64

Added lines #L61 - L64 were not covered by tests

gh_tags = tags.result()
cf_versions = cf.result()
except (HTTPError, URLError):
show_warning(

Check warning on line 69 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L66-L69

Added lines #L66 - L69 were not covered by tests
'Plugin manager: There seems to be an issue with network connectivity. '
)
return

Check warning on line 72 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L72

Added line #L72 was not covered by tests

latest_version = packaging.version.parse(cf_versions[-1])
latest_tag = packaging.version.parse(gh_tags[-1])
if latest_version > latest_tag:
yield latest_version

Check warning on line 77 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L74-L77

Added lines #L74 - L77 were not covered by tests
else:
yield latest_tag

Check warning on line 79 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L79

Added line #L79 was not covered by tests


class UpdateChecker(QWidget):

Check warning on line 82 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L82

Added line #L82 was not covered by tests

FIRST_TIME = False
URL_PACKAGE = "https://napari.org/dev/tutorials/fundamentals/installation.html#install-as-python-package-recommended"
URL_BUNDLE = "https://napari.org/dev/tutorials/fundamentals/installation.html#install-as-a-bundled-app"

Check warning on line 86 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L84-L86

Added lines #L84 - L86 were not covered by tests

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

Check warning on line 93 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L88-L93

Added lines #L88 - L93 were not covered by tests

self.label = QLabel("Checking for updates...<br>")
self.check_updates_button = QPushButton("Check for updates")
self.check_updates_button.clicked.connect(self._check)

Check warning on line 97 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L95-L97

Added lines #L95 - L97 were not covered by tests

layout = QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.check_updates_button)
self.setLayout(layout)

Check warning on line 102 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L99-L102

Added lines #L99 - L102 were not covered by tests

self._timer = QTimer()
self._timer.setInterval(2000)
self._timer.timeout.connect(self.check)
self._timer.setSingleShot(True)
self._timer.start()

Check warning on line 108 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L104-L108

Added lines #L104 - L108 were not covered by tests

def _check(self):
self.label.setText("Checking for updates...\n")
self._timer.start()

Check warning on line 112 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L110-L112

Added lines #L110 - L112 were not covered by tests

def check(self):
if os.path.exists(os.path.join(self._base_folder, IGNORE_FILE)):
with (

Check warning on line 116 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L114-L116

Added lines #L114 - L116 were not covered by tests
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

Check warning on line 125 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L123-L125

Added lines #L123 - L125 were not covered by tests

os.remove(os.path.join(self._base_folder, IGNORE_FILE))

Check warning on line 127 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L127

Added line #L127 was not covered by tests

self._worker = create_worker(get_latest_version)
self._worker.yielded.connect(self.show_version_info)
self._worker.start()

Check warning on line 131 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L129-L131

Added lines #L129 - L131 were not covered by tests

@ensure_main_thread
def show_version_info(self, latest_version):
my_version = self._current_version
remote_version = latest_version
if remote_version > my_version:
url = self.URL_BUNDLE if ON_BUNDLE else self.URL_PACKAGE
msg = (

Check warning on line 139 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L133-L139

Added lines #L133 - L139 were not covered by tests
f"You use outdated version of napari.<br><br>"
f"Installed version: {my_version}<br>"
f"Current version: {remote_version}<br><br>"
"For more information on how to update <br>"
f'visit the <a href="{url}">online documentation</a><br><br>'
)
self.label.setText(msg)
message = QMessageBox(

Check warning on line 147 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L146-L147

Added lines #L146 - L147 were not covered by tests
QMessageBox.Icon.Information,
"New release",
msg,
QMessageBox.StandardButton.Ok
| QMessageBox.StandardButton.Ignore,
)
if message.exec_() == QMessageBox.StandardButton.Ignore:
os.makedirs(self._base_folder, exist_ok=True)
with open(

Check warning on line 156 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L154-L156

Added lines #L154 - L156 were not covered by tests
os.path.join(self._base_folder, IGNORE_FILE),
"w",
encoding="utf-8",
) as f_p:
f_p.write(date.today().isoformat())

Check warning on line 161 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L161

Added line #L161 was not covered by tests
else:
msg = (

Check warning on line 163 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L163

Added line #L163 was not covered by tests
f"You are using the latest version of napari!<br><br>"
f"Installed version: {my_version}<br><br>"
)
self.label.setText(msg)

Check warning on line 167 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L167

Added line #L167 was not covered by tests


if __name__ == '__main__':
from qtpy.QtWidgets import QApplication

Check warning on line 171 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L170-L171

Added lines #L170 - L171 were not covered by tests

app = QApplication([])
checker = UpdateChecker()
sys.exit(app.exec_())

Check warning on line 175 in napari_plugin_manager/qt_update_checker.py

View check run for this annotation

Codecov / codecov/patch

napari_plugin_manager/qt_update_checker.py#L173-L175

Added lines #L173 - L175 were not covered by tests
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,6 @@ check_untyped_defs = true
# disallow_any_generics = true
# no_implicit_reexport = true
# disallow_untyped_defs = true

[project.entry-points."napari.manifest"]
napari_plugin_manager = "napari_plugin_manager:napari.yaml"