Skip to content

Commit 35fdc18

Browse files
authored
Merge pull request #3 from goanpeca/enh/updates
Add more typing, separate tests and prepare package for release
2 parents 82d8702 + 8515b73 commit 35fdc18

File tree

4 files changed

+147
-96
lines changed

4 files changed

+147
-96
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The `napari-update-checker` is a plugin that checks for newer versions of napari
1616

1717
![Screenshot of the napari-update-checker interface, showcasing the plugin](https://raw.githubusercontent.com/napari/update-checker/refs/heads/main/images/description.png)
1818

19-
`napari-update-checler` knows how to detect if napari was installed using `conda` or `pip` or if it was installed using the application bundle to provide the correct documentation on how to update napari to the latest version.
19+
`napari-update-checker` knows how to detect if napari was installed using `conda` or `pip` or if it was installed using the application bundle to provide the correct documentation on how to update napari to the latest version.
2020

2121
## Widget
2222

@@ -31,7 +31,8 @@ software.
3131

3232
If you encounter any problems, please [file an issue] along with a detailed description.
3333

34-
[napari]: https://github.com/napari/napari
3534
[file an issue]: https://github.com/napari/update-checker/issues
3635
[conda-forge]: https://anaconda.org/conda-forge/napari
3736
[PyPI]: https://pypi.org/napari
37+
[BSD-3]: http://opensource.org/licenses/BSD-3-Clause
38+
[napari]: https://github.com/napari/napari
Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,58 @@
1-
from napari_update_checker.utils import conda_forge_releases, github_tags
1+
import sys
2+
import tempfile
3+
from pathlib import Path
4+
from urllib.error import HTTPError, URLError
5+
6+
from napari_update_checker.utils import (
7+
conda_forge_releases,
8+
get_latest_version,
9+
github_tags,
10+
is_conda_environment,
11+
is_version_installed,
12+
)
213

314

415
def test_github_tags():
5-
data = github_tags()
6-
assert '0.5.0a1' in data
16+
try:
17+
data = github_tags()
18+
assert data[0] == '0.5.0a1' # Oldest version available
19+
assert len(data) >= 30
20+
except (HTTPError, URLError):
21+
pass
722

823

924
def test_conda_forge_releases():
10-
data = conda_forge_releases()
11-
assert '0.4.19.post1' in data
25+
try:
26+
data = conda_forge_releases()
27+
assert data[0] == '0.2.12' # Oldest version available
28+
assert len(data) >= 35
29+
except (HTTPError, URLError):
30+
pass
31+
32+
33+
def test_get_latest_version():
34+
result = get_latest_version(github=None)
35+
assert result
36+
result = get_latest_version(github=True)
37+
assert result
38+
result = get_latest_version(github=False)
39+
assert result
40+
41+
42+
def test_is_conda_environment():
43+
conda_envs = tempfile.mkdtemp(prefix='envs')
44+
env = Path(conda_envs) / 'env-name'
45+
meta = env / 'conda-meta'
46+
meta.mkdir(parents=True)
47+
assert is_conda_environment(env)
48+
assert not is_conda_environment(meta)
49+
50+
51+
def test_is_version_installed(monkeypatch):
52+
conda_envs = tempfile.mkdtemp(prefix='envs')
53+
env = Path(conda_envs) / 'boom-1.0.0'
54+
monkeypatch.setattr(sys, 'prefix', env)
55+
meta = env / 'conda-meta'
56+
meta.mkdir(parents=True)
57+
assert is_version_installed('1.0.0', pkg_name='boom')
58+
assert not is_version_installed('2.0.0')
Lines changed: 47 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
import json
21
import os
32
import sys
4-
from concurrent.futures import ThreadPoolExecutor
53
from contextlib import suppress
64
from datetime import date
7-
from functools import lru_cache
8-
from urllib.error import HTTPError, URLError
9-
from urllib.request import urlopen
105

11-
import packaging
126
import packaging.version
137
from napari import __version__
148
from napari._qt.qthreading import create_worker
159
from napari.utils.misc import running_as_constructor_app
16-
from napari.utils.notifications import show_warning
1710
from qtpy.QtCore import QTimer
1811
from qtpy.QtWidgets import (
1912
QLabel,
@@ -24,59 +17,14 @@
2417
)
2518
from superqt import ensure_main_thread
2619

20+
from napari_update_checker.utils import (
21+
get_latest_version,
22+
is_version_installed,
23+
)
24+
2725
ON_BUNDLE = running_as_constructor_app()
2826
IGNORE_DAYS = 21
29-
IGNORE_FILE = "ignore.txt"
30-
31-
32-
@lru_cache
33-
def github_tags():
34-
url = 'https://api.github.com/repos/napari/napari/tags'
35-
with urlopen(url) as r:
36-
data = json.load(r)
37-
38-
versions = []
39-
for item in data:
40-
version = item.get('name', None)
41-
if version:
42-
if version.startswith('v'):
43-
version = version[1:]
44-
45-
versions.append(version)
46-
47-
return list(reversed(versions))
48-
49-
50-
@lru_cache
51-
def conda_forge_releases():
52-
url = 'https://api.anaconda.org/package/conda-forge/napari/'
53-
with urlopen(url) as r:
54-
data = json.load(r)
55-
versions = data.get('versions', [])
56-
return versions
57-
58-
59-
def get_latest_version():
60-
"""Check latest version between tags and conda forge."""
61-
try:
62-
with ThreadPoolExecutor() as executor:
63-
tags = executor.submit(github_tags)
64-
cf = executor.submit(conda_forge_releases)
65-
66-
gh_tags = tags.result()
67-
cf_versions = cf.result()
68-
except (HTTPError, URLError):
69-
show_warning(
70-
'Plugin manager: There seems to be an issue with network connectivity. '
71-
)
72-
return
73-
74-
latest_version = packaging.version.parse(cf_versions[-1])
75-
latest_tag = packaging.version.parse(gh_tags[-1])
76-
if latest_version > latest_tag:
77-
yield latest_version
78-
else:
79-
yield latest_tag
27+
IGNORE_FILE = "napari-update-ignore.txt"
8028

8129

8230
class UpdateChecker(QWidget):
@@ -88,6 +36,7 @@ class UpdateChecker(QWidget):
8836
def __init__(self, parent=None):
8937
super().__init__(parent=parent)
9038
self._current_version = packaging.version.parse(__version__)
39+
self._is_dev = '.dev' in __version__
9140
self._latest_version = None
9241
self._worker = None
9342
self._base_folder = sys.prefix
@@ -113,7 +62,6 @@ def _check(self):
11362
self._timer.start()
11463

11564
def _check_time(self):
116-
# print(os.path.join(self._base_folder, IGNORE_FILE))
11765
if os.path.exists(os.path.join(self._base_folder, IGNORE_FILE)):
11866
with (
11967
open(
@@ -138,46 +86,57 @@ def check(self):
13886
self._worker.start()
13987

14088
@ensure_main_thread
141-
def show_version_info(self, latest_version):
89+
def show_version_info(self, latest_version: packaging.version.Version):
14290
my_version = self._current_version
14391
remote_version = latest_version
144-
if remote_version > my_version:
145-
url = self.URL_BUNDLE if ON_BUNDLE else self.URL_PACKAGE
92+
93+
if self._is_dev:
14694
msg = (
147-
f"You use outdated version of napari.<br><br>"
95+
f"You using napari in development mode.<br><br>"
14896
f"Installed version: {my_version}<br>"
149-
f"Current version: {remote_version}<br><br>"
150-
"For more information on how to update <br>"
151-
f'visit the <a href="{url}">online documentation</a><br><br>'
97+
f"Current released version: {remote_version}<br><br>"
15298
)
15399
self.label.setText(msg)
154-
if not self._snoozed:
155-
message = QMessageBox(
156-
QMessageBox.Icon.Information,
157-
"New release",
158-
msg,
159-
QMessageBox.StandardButton.Ok
160-
| QMessageBox.StandardButton.Ignore,
161-
)
162-
if message.exec_() == QMessageBox.StandardButton.Ignore:
163-
os.makedirs(self._base_folder, exist_ok=True)
164-
with open(
165-
os.path.join(self._base_folder, IGNORE_FILE),
166-
"w",
167-
encoding="utf-8",
168-
) as f_p:
169-
f_p.write(date.today().isoformat())
170100
else:
171-
msg = (
172-
f"You are using the latest version of napari!<br><br>"
173-
f"Installed version: {my_version}<br><br>"
174-
)
175-
self.label.setText(msg)
101+
if remote_version > my_version and not is_version_installed(
102+
str(remote_version)
103+
):
104+
url = self.URL_BUNDLE if ON_BUNDLE else self.URL_PACKAGE
105+
msg = (
106+
f"You use outdated version of napari.<br><br>"
107+
f"Installed version: {my_version}<br>"
108+
f"Current version: {remote_version}<br><br>"
109+
"For more information on how to update <br>"
110+
f'visit the <a href="{url}">online documentation</a><br><br>'
111+
)
112+
self.label.setText(msg)
113+
if not self._snoozed:
114+
message = QMessageBox(
115+
QMessageBox.Icon.Information,
116+
"New release",
117+
msg,
118+
QMessageBox.StandardButton.Ok
119+
| QMessageBox.StandardButton.Ignore,
120+
)
121+
if message.exec_() == QMessageBox.StandardButton.Ignore: # type: ignore
122+
os.makedirs(self._base_folder, exist_ok=True)
123+
with open(
124+
os.path.join(self._base_folder, IGNORE_FILE),
125+
"w",
126+
encoding="utf-8",
127+
) as f_p:
128+
f_p.write(date.today().isoformat())
129+
else:
130+
msg = (
131+
f"You are using the latest version of napari!<br><br>"
132+
f"Installed version: {my_version}<br><br>"
133+
)
134+
self.label.setText(msg)
176135

177136

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

181140
app = QApplication([])
182141
checker = UpdateChecker()
183-
sys.exit(app.exec_())
142+
sys.exit(app.exec_()) # type: ignore

napari_update_checker/utils.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
import json
2+
import sys
3+
from concurrent.futures import ThreadPoolExecutor
24
from functools import lru_cache
5+
from pathlib import Path
6+
from typing import Optional
7+
from urllib.error import HTTPError, URLError
38
from urllib.request import urlopen
49

10+
import packaging
11+
import packaging.version
12+
from napari.utils.misc import running_as_constructor_app
13+
from napari.utils.notifications import show_warning
14+
15+
ON_BUNDLE = running_as_constructor_app()
16+
517

618
@lru_cache
719
def github_tags(url: str = 'https://api.github.com/repos/napari/napari/tags'):
@@ -16,7 +28,6 @@ def github_tags(url: str = 'https://api.github.com/repos/napari/napari/tags'):
1628
version = version[1:]
1729

1830
versions.append(version)
19-
2031
return list(reversed(versions))
2132

2233

@@ -28,3 +39,36 @@ def conda_forge_releases(
2839
data = json.load(r)
2940
versions = data.get('versions', [])
3041
return versions
42+
43+
44+
def get_latest_version(github: Optional[bool] = None):
45+
"""Check latest version between tags and conda forge depending on type of napari install."""
46+
if github is None:
47+
versions_func = conda_forge_releases if ON_BUNDLE else github_tags
48+
else:
49+
versions_func = github_tags if github is True else conda_forge_releases
50+
51+
versions = []
52+
try:
53+
with ThreadPoolExecutor() as executor:
54+
future = executor.submit(versions_func)
55+
56+
versions = future.result()
57+
except (HTTPError, URLError):
58+
show_warning(
59+
'Update checker: There seems to be an issue with network connectivity. '
60+
)
61+
return None
62+
63+
if versions:
64+
yield packaging.version.parse(versions[-1])
65+
66+
67+
def is_conda_environment(path):
68+
return (Path(path) / 'conda-meta').exists()
69+
70+
71+
def is_version_installed(version, pkg_name='napari'):
72+
envs_folder = Path(sys.prefix)
73+
env = envs_folder.parent / f'{pkg_name}-{version}'
74+
return env.exists() and is_conda_environment(env)

0 commit comments

Comments
 (0)