napari-plugin-manager 0.1.0a2__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,11 @@
1
+ import sys
2
+ from typing import TYPE_CHECKING
3
+
1
4
  import pytest
2
5
  from qtpy.QtWidgets import QDialog, QInputDialog, QMessageBox
3
6
 
7
+ from napari_plugin_manager.qt_package_installer import CondaInstallerTool
8
+
4
9
 
5
10
  @pytest.fixture(autouse=True)
6
11
  def _block_message_box(monkeypatch, request):
@@ -16,3 +21,43 @@ def _block_message_box(monkeypatch, request):
16
21
  # QDialogs can be allowed via a marker; only raise if not decorated
17
22
  if "enabledialog" not in request.keywords:
18
23
  monkeypatch.setattr(QDialog, "exec_", raise_on_call)
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from virtualenv.run import Session
28
+
29
+
30
+ @pytest.fixture
31
+ def tmp_virtualenv(tmp_path) -> 'Session':
32
+ virtualenv = pytest.importorskip('virtualenv')
33
+
34
+ cmd = [str(tmp_path), '--no-setuptools', '--no-wheel', '--activators', '']
35
+ return virtualenv.cli_run(cmd)
36
+
37
+
38
+ @pytest.fixture
39
+ def tmp_conda_env(tmp_path):
40
+ import subprocess
41
+
42
+ try:
43
+ subprocess.check_output(
44
+ [
45
+ CondaInstallerTool.executable(),
46
+ 'create',
47
+ '-yq',
48
+ '-p',
49
+ str(tmp_path),
50
+ '--override-channels',
51
+ '-c',
52
+ 'conda-forge',
53
+ f'python={sys.version_info.major}.{sys.version_info.minor}',
54
+ ],
55
+ stderr=subprocess.STDOUT,
56
+ text=True,
57
+ timeout=300,
58
+ )
59
+ except subprocess.CalledProcessError as exc:
60
+ print(exc.output)
61
+ raise
62
+
63
+ return tmp_path
@@ -1,5 +1,4 @@
1
1
  import re
2
- import sys
3
2
  import time
4
3
  from pathlib import Path
5
4
  from types import MethodType
@@ -11,6 +10,7 @@ from qtpy.QtCore import QProcessEnvironment
11
10
  from napari_plugin_manager.qt_package_installer import (
12
11
  AbstractInstallerTool,
13
12
  CondaInstallerTool,
13
+ InstallerActions,
14
14
  InstallerQueue,
15
15
  InstallerTools,
16
16
  PipInstallerTool,
@@ -20,40 +20,54 @@ if TYPE_CHECKING:
20
20
  from virtualenv.run import Session
21
21
 
22
22
 
23
- @pytest.fixture
24
- def tmp_virtualenv(tmp_path) -> 'Session':
25
- virtualenv = pytest.importorskip('virtualenv')
23
+ def _assert_exit_code_not_zero(
24
+ self, exit_code=None, exit_status=None, error=None
25
+ ):
26
+ errors = []
27
+ if exit_code == 0:
28
+ errors.append("- 'exit_code' should have been non-zero!")
29
+ if error is not None:
30
+ errors.append("- 'error' should have been None!")
31
+ if errors:
32
+ raise AssertionError("\n".join(errors))
33
+ return self._on_process_done_original(exit_code, exit_status, error)
34
+
35
+
36
+ def _assert_error_used(self, exit_code=None, exit_status=None, error=None):
37
+ errors = []
38
+ if error is None:
39
+ errors.append("- 'error' should have been populated!")
40
+ if exit_code is not None:
41
+ errors.append("- 'exit_code' should not have been populated!")
42
+ if errors:
43
+ raise AssertionError("\n".join(errors))
44
+ return self._on_process_done_original(exit_code, exit_status, error)
26
45
 
27
- cmd = [str(tmp_path), '--no-setuptools', '--no-wheel', '--activators', '']
28
- return virtualenv.cli_run(cmd)
29
46
 
47
+ class _NonExistingTool(AbstractInstallerTool):
48
+ def executable(self):
49
+ return f"this-tool-does-not-exist-{hash(time.time())}"
30
50
 
31
- @pytest.fixture
32
- def tmp_conda_env(tmp_path):
33
- import subprocess
51
+ def arguments(self):
52
+ return ()
34
53
 
35
- try:
36
- subprocess.check_output(
37
- [
38
- CondaInstallerTool.executable(),
39
- 'create',
40
- '-yq',
41
- '-p',
42
- str(tmp_path),
43
- '--override-channels',
44
- '-c',
45
- 'conda-forge',
46
- f'python={sys.version_info.major}.{sys.version_info.minor}',
47
- ],
48
- stderr=subprocess.STDOUT,
49
- text=True,
50
- timeout=300,
51
- )
52
- except subprocess.CalledProcessError as exc:
53
- print(exc.output)
54
- raise
54
+ def environment(self, env=None):
55
+ return QProcessEnvironment.systemEnvironment()
55
56
 
56
- return tmp_path
57
+
58
+ def test_not_implemented_methods():
59
+ tool = AbstractInstallerTool('install', ['requests'])
60
+ with pytest.raises(NotImplementedError):
61
+ tool.executable()
62
+
63
+ with pytest.raises(NotImplementedError):
64
+ tool.arguments()
65
+
66
+ with pytest.raises(NotImplementedError):
67
+ tool.environment()
68
+
69
+ with pytest.raises(NotImplementedError):
70
+ tool.available()
57
71
 
58
72
 
59
73
  def test_pip_installer_tasks(qtbot, tmp_virtualenv: 'Session', monkeypatch):
@@ -100,43 +114,28 @@ def test_pip_installer_tasks(qtbot, tmp_virtualenv: 'Session', monkeypatch):
100
114
  assert not (
101
115
  pth / 'pip_install_test'
102
116
  ).exists(), 'pip_install_test still installed'
103
-
104
117
  assert not installer.hasJobs()
105
118
 
119
+ # Test new signals
120
+ with qtbot.waitSignal(installer.processFinished, timeout=20000) as blocker:
121
+ installer.install(
122
+ tool=InstallerTools.PIP,
123
+ pkgs=['pydantic'],
124
+ )
125
+ process_finished_data = blocker.args[0]
126
+ assert process_finished_data['action'] == InstallerActions.INSTALL
127
+ assert process_finished_data['pkgs'] == ["pydantic"]
106
128
 
107
- def _assert_exit_code_not_zero(
108
- self, exit_code=None, exit_status=None, error=None
109
- ):
110
- errors = []
111
- if exit_code == 0:
112
- errors.append("- 'exit_code' should have been non-zero!")
113
- if error is not None:
114
- errors.append("- 'error' should have been None!")
115
- if errors:
116
- raise AssertionError("\n".join(errors))
117
- return self._on_process_done_original(exit_code, exit_status, error)
118
-
119
-
120
- class _NonExistingTool(AbstractInstallerTool):
121
- def executable(self):
122
- return f"this-tool-does-not-exist-{hash(time.time())}"
123
-
124
- def arguments(self):
125
- return ()
126
-
127
- def environment(self, env=None):
128
- return QProcessEnvironment.systemEnvironment()
129
-
130
-
131
- def _assert_error_used(self, exit_code=None, exit_status=None, error=None):
132
- errors = []
133
- if error is None:
134
- errors.append("- 'error' should have been populated!")
135
- if exit_code is not None:
136
- errors.append("- 'exit_code' should not have been populated!")
137
- if errors:
138
- raise AssertionError("\n".join(errors))
139
- return self._on_process_done_original(exit_code, exit_status, error)
129
+ # Test upgrade
130
+ with qtbot.waitSignal(installer.allFinished, timeout=20000):
131
+ installer.install(
132
+ tool=InstallerTools.PIP,
133
+ pkgs=['requests==2.30.0'],
134
+ )
135
+ installer.upgrade(
136
+ tool=InstallerTools.PIP,
137
+ pkgs=['requests'],
138
+ )
140
139
 
141
140
 
142
141
  def test_installer_failures(qtbot, tmp_virtualenv: 'Session', monkeypatch):
@@ -181,10 +180,24 @@ def test_installer_failures(qtbot, tmp_virtualenv: 'Session', monkeypatch):
181
180
  )
182
181
 
183
182
 
183
+ def test_cancel_incorrect_job_id(qtbot, tmp_virtualenv: 'Session'):
184
+ installer = InstallerQueue()
185
+ with qtbot.waitSignal(installer.allFinished, timeout=20000):
186
+ job_id = installer.install(
187
+ tool=InstallerTools.PIP,
188
+ pkgs=['requests'],
189
+ )
190
+ with pytest.raises(ValueError):
191
+ installer.cancel(job_id + 1)
192
+
193
+
184
194
  @pytest.mark.skipif(
185
195
  not CondaInstallerTool.available(), reason="Conda is not available."
186
196
  )
187
197
  def test_conda_installer(qtbot, tmp_conda_env: Path):
198
+ conda_meta = tmp_conda_env / "conda-meta"
199
+ glob_pat = "typing-extensions-*.json"
200
+ glob_pat_2 = "pyzenhub-*.json"
188
201
  installer = InstallerQueue()
189
202
 
190
203
  with qtbot.waitSignal(installer.allFinished, timeout=600_000):
@@ -194,9 +207,6 @@ def test_conda_installer(qtbot, tmp_conda_env: Path):
194
207
  prefix=tmp_conda_env,
195
208
  )
196
209
 
197
- conda_meta = tmp_conda_env / "conda-meta"
198
- glob_pat = "typing-extensions-*.json"
199
-
200
210
  assert not installer.hasJobs()
201
211
  assert list(conda_meta.glob(glob_pat))
202
212
 
@@ -210,6 +220,93 @@ def test_conda_installer(qtbot, tmp_conda_env: Path):
210
220
  assert not installer.hasJobs()
211
221
  assert not list(conda_meta.glob(glob_pat))
212
222
 
223
+ # Check canceling all works
224
+ with qtbot.waitSignal(installer.allFinished, timeout=600_000):
225
+ installer.install(
226
+ tool=InstallerTools.CONDA,
227
+ pkgs=['typing-extensions'],
228
+ prefix=tmp_conda_env,
229
+ )
230
+ installer.install(
231
+ tool=InstallerTools.CONDA,
232
+ pkgs=['pyzenhub'],
233
+ prefix=tmp_conda_env,
234
+ )
235
+ assert installer.currentJobs() == 2
236
+ installer.cancel_all()
237
+
238
+ assert not installer.hasJobs()
239
+ assert not list(conda_meta.glob(glob_pat))
240
+ assert not list(conda_meta.glob(glob_pat_2))
241
+
242
+ # Check canceling current job works (1st in queue)
243
+ with qtbot.waitSignal(installer.allFinished, timeout=600_000):
244
+ job_id_1 = installer.install(
245
+ tool=InstallerTools.CONDA,
246
+ pkgs=['typing-extensions'],
247
+ prefix=tmp_conda_env,
248
+ )
249
+ job_id_2 = installer.install(
250
+ tool=InstallerTools.CONDA,
251
+ pkgs=['pyzenhub'],
252
+ prefix=tmp_conda_env,
253
+ )
254
+ assert installer.currentJobs() == 2
255
+ installer.cancel(job_id_1)
256
+ assert installer.currentJobs() == 1
257
+
258
+ assert not installer.hasJobs()
259
+
260
+ # Check canceling queued job works (somewhere besides 1st position in queue)
261
+ with qtbot.waitSignal(installer.allFinished, timeout=600_000):
262
+ job_id_1 = installer.install(
263
+ tool=InstallerTools.CONDA,
264
+ pkgs=['typing-extensions'],
265
+ prefix=tmp_conda_env,
266
+ )
267
+ job_id_2 = installer.install(
268
+ tool=InstallerTools.CONDA,
269
+ pkgs=['pyzenhub'],
270
+ prefix=tmp_conda_env,
271
+ )
272
+ assert installer.currentJobs() == 2
273
+ installer.cancel(job_id_2)
274
+ assert installer.currentJobs() == 1
275
+
276
+ assert not installer.hasJobs()
277
+
278
+
279
+ def test_installer_error(qtbot, tmp_virtualenv: 'Session', monkeypatch):
280
+ installer = InstallerQueue()
281
+ monkeypatch.setattr(
282
+ PipInstallerTool, "executable", lambda *a: 'not-a-real-executable'
283
+ )
284
+ with qtbot.waitSignal(installer.allFinished, timeout=600_000):
285
+ installer.install(
286
+ tool=InstallerTools.PIP,
287
+ pkgs=['some-package-that-does-not-exist'],
288
+ )
289
+
290
+
291
+ @pytest.mark.skipif(
292
+ not CondaInstallerTool.available(), reason="Conda is not available."
293
+ )
294
+ def test_conda_installer_wait_for_finished(qtbot, tmp_conda_env: Path):
295
+ installer = InstallerQueue()
296
+
297
+ with qtbot.waitSignal(installer.allFinished, timeout=600_000):
298
+ installer.install(
299
+ tool=InstallerTools.CONDA,
300
+ pkgs=['requests'],
301
+ prefix=tmp_conda_env,
302
+ )
303
+ installer.install(
304
+ tool=InstallerTools.CONDA,
305
+ pkgs=['pyzenhub'],
306
+ prefix=tmp_conda_env,
307
+ )
308
+ installer.waitForFinished(20000)
309
+
213
310
 
214
311
  def test_constraints_are_in_sync():
215
312
  conda_constraints = sorted(CondaInstallerTool.constraints())
@@ -224,3 +321,18 @@ def test_constraints_are_in_sync():
224
321
  conda_name = name_re.match(conda_constraint).group(1)
225
322
  pip_name = name_re.match(pip_constraint).group(1)
226
323
  assert conda_name == pip_name
324
+
325
+
326
+ def test_executables():
327
+ assert CondaInstallerTool.executable()
328
+ assert PipInstallerTool.executable()
329
+
330
+
331
+ def test_available():
332
+ assert str(CondaInstallerTool.available())
333
+ assert PipInstallerTool.available()
334
+
335
+
336
+ def test_unrecognized_tool():
337
+ with pytest.raises(ValueError):
338
+ InstallerQueue().install(tool='shrug', pkgs=[])
@@ -0,0 +1,54 @@
1
+ from flaky import flaky
2
+
3
+ from napari_plugin_manager.npe2api import (
4
+ _user_agent,
5
+ cache_clear,
6
+ conda_map,
7
+ iter_napari_plugin_info,
8
+ plugin_summaries,
9
+ )
10
+
11
+
12
+ def test_user_agent():
13
+ assert _user_agent()
14
+
15
+
16
+ @flaky(max_runs=3, min_passes=2)
17
+ def test_plugin_summaries():
18
+ keys = [
19
+ "name",
20
+ "version",
21
+ "display_name",
22
+ "summary",
23
+ "author",
24
+ "license",
25
+ "home_page",
26
+ "pypi_versions",
27
+ "conda_versions",
28
+ ]
29
+ data = plugin_summaries()
30
+ test_data = dict(data[0])
31
+ for key in keys:
32
+ assert key in test_data
33
+ test_data.pop(key)
34
+
35
+ assert not test_data
36
+
37
+
38
+ def test_conda_map():
39
+ pkgs = ["napari-svg"]
40
+ data = conda_map()
41
+ for pkg in pkgs:
42
+ assert pkg in data
43
+
44
+
45
+ def test_iter_napari_plugin_info():
46
+ data = iter_napari_plugin_info()
47
+ for item in data:
48
+ assert item
49
+
50
+
51
+ def test_clear_cache():
52
+ assert _user_agent.cache_info().hits >= 1
53
+ cache_clear()
54
+ assert _user_agent.cache_info().hits == 0