napari-plugin-manager 0.1.3__tar.gz → 0.1.4__tar.gz

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.
Files changed (53) hide show
  1. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/.github/workflows/test_and_deploy.yml +36 -5
  2. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/.pre-commit-config.yaml +4 -4
  3. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/PKG-INFO +16 -1
  4. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/README.md +14 -0
  5. napari_plugin_manager-0.1.4/images/direct-entry.png +0 -0
  6. napari_plugin_manager-0.1.4/napari_plugin_manager/_tests/test_base_installer_process.py +23 -0
  7. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/_tests/test_installer_process.py +69 -39
  8. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/_tests/test_npe2api.py +1 -1
  9. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +109 -48
  10. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/_version.py +2 -2
  11. napari_plugin_manager-0.1.3/napari_plugin_manager/qt_package_installer.py → napari_plugin_manager-0.1.4/napari_plugin_manager/base_qt_package_installer.py +15 -46
  12. napari_plugin_manager-0.1.3/napari_plugin_manager/qt_plugin_dialog.py → napari_plugin_manager-0.1.4/napari_plugin_manager/base_qt_plugin_dialog.py +562 -295
  13. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/npe2api.py +0 -1
  14. napari_plugin_manager-0.1.4/napari_plugin_manager/qt_package_installer.py +84 -0
  15. napari_plugin_manager-0.1.4/napari_plugin_manager/qt_plugin_dialog.py +297 -0
  16. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager.egg-info/PKG-INFO +16 -1
  17. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager.egg-info/SOURCES.txt +4 -0
  18. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager.egg-info/requires.txt +1 -0
  19. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/pyproject.toml +7 -1
  20. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/.github/dependabot.yml +0 -0
  21. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/.github/workflows/deploy_docs.yml +0 -0
  22. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/.gitignore +0 -0
  23. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/LICENSE +0 -0
  24. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/Makefile +0 -0
  25. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/RELEASE.md +0 -0
  26. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/__init__.py +0 -0
  27. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/_static/custom.css +0 -0
  28. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/_static/favicon/logo-noborder-180.png +0 -0
  29. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/_static/favicon/logo-silhouette-192.png +0 -0
  30. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/_static/favicon/logo-silhouette-dark-light.svg +0 -0
  31. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/_templates/navbar-project.html +0 -0
  32. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/_toc.yml +0 -0
  33. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/conf.py +0 -0
  34. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/developers/contributing.md +0 -0
  35. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/docs/index.md +0 -0
  36. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/images/description.png +0 -0
  37. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/images/filter.png +0 -0
  38. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/images/install.png +0 -0
  39. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/images/logo.png +0 -0
  40. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/images/status.png +0 -0
  41. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/images/uninstall.png +0 -0
  42. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/images/update.png +0 -0
  43. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/__init__.py +0 -0
  44. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/_tests/__init__.py +0 -0
  45. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/_tests/conftest.py +0 -0
  46. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/_tests/test_utils.py +0 -0
  47. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/qt_widgets.py +0 -0
  48. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/styles.qss +0 -0
  49. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager/utils.py +0 -0
  50. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager.egg-info/dependency_links.txt +0 -0
  51. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/napari_plugin_manager.egg-info/top_level.txt +0 -0
  52. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/setup.cfg +0 -0
  53. {napari_plugin_manager-0.1.3 → napari_plugin_manager-0.1.4}/tox.ini +0 -0
@@ -25,7 +25,7 @@ concurrency:
25
25
 
26
26
  jobs:
27
27
  test:
28
- name: ${{ matrix.platform }}, py${{ matrix.python-version }}, napari ${{ matrix.napari }}
28
+ name: ${{ matrix.platform }}, py${{ matrix.python-version }}, napari ${{ matrix.napari }}, ${{ matrix.tool }}
29
29
  runs-on: ${{ matrix.platform }}
30
30
  strategy:
31
31
  fail-fast: false
@@ -33,6 +33,7 @@ jobs:
33
33
  platform: [ubuntu-latest, windows-latest, macos-13]
34
34
  python-version: ["3.9", "3.10", "3.11"]
35
35
  napari: ["latest", "repo"]
36
+ tool: ["pip", "conda"]
36
37
  exclude:
37
38
  # TODO: Remove when we have a napari release with the plugin manager changes
38
39
  - napari: "latest"
@@ -43,11 +44,19 @@ jobs:
43
44
  steps:
44
45
  - uses: actions/checkout@v4
45
46
 
46
- - name: Set up Python ${{ matrix.python-version }}
47
+ - name: Set up Python ${{ matrix.python-version }} - pip
48
+ if: matrix.tool == 'pip'
47
49
  uses: actions/setup-python@v5
48
50
  with:
49
51
  python-version: ${{ matrix.python-version }}
50
52
 
53
+ - name: Set up Python ${{ matrix.python-version }} - conda
54
+ if: matrix.tool == 'conda'
55
+ uses: conda-incubator/setup-miniconda@v3
56
+ with:
57
+ miniforge-version: latest
58
+ python-version: ${{ matrix.python-version }}
59
+
51
60
  - uses: tlambert03/setup-qt-libs@v1
52
61
 
53
62
  # strategy borrowed from vispy for installing opengl libs on windows
@@ -58,14 +67,36 @@ jobs:
58
67
  powershell gl-ci-helpers/appveyor/install_opengl.ps1
59
68
  if (Test-Path -Path "C:\Windows\system32\opengl32.dll" -PathType Leaf) {Exit 0} else {Exit 1}
60
69
 
61
- - name: Install dependencies
70
+ - name: Install dependencies without tox-conda
71
+ if: matrix.tool == 'pip'
62
72
  run: |
63
73
  python -m pip install --upgrade pip
64
- pip install setuptools tox tox-gh-actions
74
+ python -m pip install setuptools tox tox-gh-actions
75
+
76
+ - name: Install dependencies including tox-conda
77
+ if: matrix.tool == 'conda'
78
+ shell: bash -el {0}
79
+ run: |
80
+ python -m pip install --upgrade pip
81
+ python -m pip install setuptools 'tox<4' tox-gh-actions tox-conda
82
+
83
+ - name: Test with tox - pip
84
+ if: matrix.tool == 'pip'
85
+ uses: aganders3/headless-gui@v2
86
+ with:
87
+ run: python -m tox -vv
88
+ env:
89
+ PYVISTA_OFF_SCREEN: True # required for opengl on windows
90
+ NAPARI: ${{ matrix.napari }}
91
+ FORCE_COLOR: 1
92
+ # PySide6 only functional with Python 3.10+
93
+ TOX_SKIP_ENV: ".*py39-PySide6.*"
65
94
 
66
- - name: Test with tox
95
+ - name: Test with tox - conda
96
+ if: matrix.tool == 'conda'
67
97
  uses: aganders3/headless-gui@v2
68
98
  with:
99
+ shell: bash -el {0}
69
100
  run: python -m tox -vv
70
101
  env:
71
102
  PYVISTA_OFF_SCREEN: True # required for opengl on windows
@@ -8,21 +8,21 @@ repos:
8
8
  hooks:
9
9
  - id: pycln
10
10
  - repo: https://github.com/psf/black-pre-commit-mirror
11
- rev: 24.8.0
11
+ rev: 24.10.0
12
12
  hooks:
13
13
  - id: black
14
14
  pass_filenames: true
15
15
  - repo: https://github.com/astral-sh/ruff-pre-commit
16
- rev: v0.6.3
16
+ rev: v0.8.1
17
17
  hooks:
18
18
  - id: ruff
19
19
  - repo: https://github.com/seddonym/import-linter
20
- rev: v2.0
20
+ rev: v2.1
21
21
  hooks:
22
22
  - id: import-linter
23
23
  stages: [manual]
24
24
  - repo: https://github.com/python-jsonschema/check-jsonschema
25
- rev: 0.29.2
25
+ rev: 0.30.0
26
26
  hooks:
27
27
  - id: check-github-workflows
28
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: napari-plugin-manager
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Install plugins for napari, in napari.
5
5
  Author-email: napari team <napari-steering-council@googlegroups.com>
6
6
  License: BSD 3-Clause License
@@ -62,6 +62,7 @@ Requires-Dist: npe2
62
62
  Requires-Dist: qtpy
63
63
  Requires-Dist: superqt
64
64
  Requires-Dist: pip
65
+ Requires-Dist: packaging
65
66
  Provides-Extra: dev
66
67
  Requires-Dist: PyQt5; extra == "dev"
67
68
  Requires-Dist: pre-commit; extra == "dev"
@@ -186,6 +187,20 @@ a restart to be properly configured.
186
187
 
187
188
  ![Screenshot of the napari-plugin-manager showing the process of installing a plugin](https://raw.githubusercontent.com/napari/napari-plugin-manager/main/images/install.png)
188
189
 
190
+ ### Installing a plugin via direct entry
191
+
192
+ You can also install a napari plugin or any other package via the direct entry option. The following steps
193
+ correspond to the options and buttons located at the **bottom of the dialog**.
194
+
195
+ 1. You can type either the name of the package, a url to the resource or drag and drop a compressed file
196
+ of a previously downloaded package.
197
+ 2. Select the tool (`conda` or `pip`) by clicking on the arrow dorpdown of the `Install` button.
198
+ 3. Start the installation process by clicking on the `Install` button.
199
+
200
+ You can cancel the process at any time by clicking the `Cancel all` button.
201
+
202
+ ![Screenshot of the napari-plugin-manager showing the direct entry options](https://raw.githubusercontent.com/napari/napari-plugin-manager/main/images/direct-entry.png)
203
+
189
204
  ### Uninstalling a plugin
190
205
 
191
206
  To uninstall a plugin:
@@ -104,6 +104,20 @@ a restart to be properly configured.
104
104
 
105
105
  ![Screenshot of the napari-plugin-manager showing the process of installing a plugin](https://raw.githubusercontent.com/napari/napari-plugin-manager/main/images/install.png)
106
106
 
107
+ ### Installing a plugin via direct entry
108
+
109
+ You can also install a napari plugin or any other package via the direct entry option. The following steps
110
+ correspond to the options and buttons located at the **bottom of the dialog**.
111
+
112
+ 1. You can type either the name of the package, a url to the resource or drag and drop a compressed file
113
+ of a previously downloaded package.
114
+ 2. Select the tool (`conda` or `pip`) by clicking on the arrow dorpdown of the `Install` button.
115
+ 3. Start the installation process by clicking on the `Install` button.
116
+
117
+ You can cancel the process at any time by clicking the `Cancel all` button.
118
+
119
+ ![Screenshot of the napari-plugin-manager showing the direct entry options](https://raw.githubusercontent.com/napari/napari-plugin-manager/main/images/direct-entry.png)
120
+
107
121
  ### Uninstalling a plugin
108
122
 
109
123
  To uninstall a plugin:
@@ -0,0 +1,23 @@
1
+ import pytest
2
+
3
+ from napari_plugin_manager.base_qt_package_installer import (
4
+ AbstractInstallerTool,
5
+ )
6
+
7
+
8
+ def test_not_implemented_methods():
9
+ tool = AbstractInstallerTool('install', ['requests'])
10
+ with pytest.raises(NotImplementedError):
11
+ tool.executable()
12
+
13
+ with pytest.raises(NotImplementedError):
14
+ tool.arguments()
15
+
16
+ with pytest.raises(NotImplementedError):
17
+ tool.environment()
18
+
19
+ with pytest.raises(NotImplementedError):
20
+ tool.constraints()
21
+
22
+ with pytest.raises(NotImplementedError):
23
+ tool.available()
@@ -1,4 +1,6 @@
1
+ import logging
1
2
  import re
3
+ import sys
2
4
  import time
3
5
  from pathlib import Path
4
6
  from types import MethodType
@@ -7,13 +9,16 @@ from typing import TYPE_CHECKING
7
9
  import pytest
8
10
  from qtpy.QtCore import QProcessEnvironment
9
11
 
10
- from napari_plugin_manager.qt_package_installer import (
12
+ import napari_plugin_manager.base_qt_package_installer as bqpi
13
+ from napari_plugin_manager.base_qt_package_installer import (
11
14
  AbstractInstallerTool,
12
- CondaInstallerTool,
13
15
  InstallerActions,
14
- InstallerQueue,
15
16
  InstallerTools,
16
- PipInstallerTool,
17
+ )
18
+ from napari_plugin_manager.qt_package_installer import (
19
+ NapariCondaInstallerTool,
20
+ NapariInstallerQueue,
21
+ NapariPipInstallerTool,
17
22
  )
18
23
 
19
24
  if TYPE_CHECKING:
@@ -55,25 +60,20 @@ class _NonExistingTool(AbstractInstallerTool):
55
60
  return QProcessEnvironment.systemEnvironment()
56
61
 
57
62
 
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()
71
-
72
-
73
- def test_pip_installer_tasks(qtbot, tmp_virtualenv: 'Session', monkeypatch):
74
- installer = InstallerQueue()
63
+ def test_pip_installer_tasks(
64
+ qtbot, tmp_virtualenv: 'Session', monkeypatch, caplog
65
+ ):
66
+ caplog.set_level(logging.DEBUG, logger=bqpi.__name__)
67
+ installer = NapariInstallerQueue()
75
68
  monkeypatch.setattr(
76
- PipInstallerTool, "executable", lambda *a: tmp_virtualenv.creator.exe
69
+ NapariPipInstallerTool,
70
+ "executable",
71
+ lambda *a: tmp_virtualenv.creator.exe,
72
+ )
73
+ monkeypatch.setattr(
74
+ NapariPipInstallerTool,
75
+ "origins",
76
+ ("https://pypi.org/simple",),
77
77
  )
78
78
  with qtbot.waitSignal(installer.allFinished, timeout=20000):
79
79
  installer.install(
@@ -138,10 +138,34 @@ def test_pip_installer_tasks(qtbot, tmp_virtualenv: 'Session', monkeypatch):
138
138
  )
139
139
 
140
140
 
141
+ def test_pip_installer_invalid_action(tmp_virtualenv: 'Session', monkeypatch):
142
+ installer = NapariInstallerQueue()
143
+ monkeypatch.setattr(
144
+ NapariPipInstallerTool,
145
+ "executable",
146
+ lambda *a: tmp_virtualenv.creator.exe,
147
+ )
148
+ invalid_action = 'Invalid Action'
149
+ with pytest.raises(
150
+ ValueError, match=f"Action '{invalid_action}' not supported!"
151
+ ):
152
+ item = installer._build_queue_item(
153
+ tool=InstallerTools.PIP,
154
+ action=invalid_action,
155
+ pkgs=['pip-install-test'],
156
+ prefix=None,
157
+ origins=(),
158
+ process=installer._create_process(),
159
+ )
160
+ installer._queue_item(item)
161
+
162
+
141
163
  def test_installer_failures(qtbot, tmp_virtualenv: 'Session', monkeypatch):
142
- installer = InstallerQueue()
164
+ installer = NapariInstallerQueue()
143
165
  monkeypatch.setattr(
144
- PipInstallerTool, "executable", lambda *a: tmp_virtualenv.creator.exe
166
+ NapariPipInstallerTool,
167
+ "executable",
168
+ lambda *a: tmp_virtualenv.creator.exe,
145
169
  )
146
170
 
147
171
  # CHECK 1) Errors should trigger finished and allFinished too
@@ -181,7 +205,7 @@ def test_installer_failures(qtbot, tmp_virtualenv: 'Session', monkeypatch):
181
205
 
182
206
 
183
207
  def test_cancel_incorrect_job_id(qtbot, tmp_virtualenv: 'Session'):
184
- installer = InstallerQueue()
208
+ installer = NapariInstallerQueue()
185
209
  with qtbot.waitSignal(installer.allFinished, timeout=20000):
186
210
  job_id = installer.install(
187
211
  tool=InstallerTools.PIP,
@@ -192,13 +216,17 @@ def test_cancel_incorrect_job_id(qtbot, tmp_virtualenv: 'Session'):
192
216
 
193
217
 
194
218
  @pytest.mark.skipif(
195
- not CondaInstallerTool.available(), reason="Conda is not available."
219
+ not NapariCondaInstallerTool.available(), reason="Conda is not available."
196
220
  )
197
- def test_conda_installer(qtbot, tmp_conda_env: Path):
221
+ def test_conda_installer(qtbot, caplog, monkeypatch, tmp_conda_env: Path):
222
+ if sys.platform == "darwin":
223
+ # check handled for `PYTHONEXECUTABLE` env definition on macOS
224
+ monkeypatch.setenv("PYTHONEXECUTABLE", sys.executable)
225
+ caplog.set_level(logging.DEBUG, logger=bqpi.__name__)
198
226
  conda_meta = tmp_conda_env / "conda-meta"
199
227
  glob_pat = "typing-extensions-*.json"
200
228
  glob_pat_2 = "pyzenhub-*.json"
201
- installer = InstallerQueue()
229
+ installer = NapariInstallerQueue()
202
230
 
203
231
  with qtbot.waitSignal(installer.allFinished, timeout=600_000):
204
232
  installer.install(
@@ -277,9 +305,11 @@ def test_conda_installer(qtbot, tmp_conda_env: Path):
277
305
 
278
306
 
279
307
  def test_installer_error(qtbot, tmp_virtualenv: 'Session', monkeypatch):
280
- installer = InstallerQueue()
308
+ installer = NapariInstallerQueue()
281
309
  monkeypatch.setattr(
282
- PipInstallerTool, "executable", lambda *a: 'not-a-real-executable'
310
+ NapariPipInstallerTool,
311
+ "executable",
312
+ lambda *a: 'not-a-real-executable',
283
313
  )
284
314
  with qtbot.waitSignal(installer.allFinished, timeout=600_000):
285
315
  installer.install(
@@ -289,10 +319,10 @@ def test_installer_error(qtbot, tmp_virtualenv: 'Session', monkeypatch):
289
319
 
290
320
 
291
321
  @pytest.mark.skipif(
292
- not CondaInstallerTool.available(), reason="Conda is not available."
322
+ not NapariCondaInstallerTool.available(), reason="Conda is not available."
293
323
  )
294
324
  def test_conda_installer_wait_for_finished(qtbot, tmp_conda_env: Path):
295
- installer = InstallerQueue()
325
+ installer = NapariInstallerQueue()
296
326
 
297
327
  with qtbot.waitSignal(installer.allFinished, timeout=600_000):
298
328
  installer.install(
@@ -309,8 +339,8 @@ def test_conda_installer_wait_for_finished(qtbot, tmp_conda_env: Path):
309
339
 
310
340
 
311
341
  def test_constraints_are_in_sync():
312
- conda_constraints = sorted(CondaInstallerTool.constraints())
313
- pip_constraints = sorted(PipInstallerTool.constraints())
342
+ conda_constraints = sorted(NapariCondaInstallerTool.constraints())
343
+ pip_constraints = sorted(NapariPipInstallerTool.constraints())
314
344
 
315
345
  assert len(conda_constraints) == len(pip_constraints)
316
346
 
@@ -324,15 +354,15 @@ def test_constraints_are_in_sync():
324
354
 
325
355
 
326
356
  def test_executables():
327
- assert CondaInstallerTool.executable()
328
- assert PipInstallerTool.executable()
357
+ assert NapariCondaInstallerTool.executable()
358
+ assert NapariPipInstallerTool.executable()
329
359
 
330
360
 
331
361
  def test_available():
332
- assert str(CondaInstallerTool.available())
333
- assert PipInstallerTool.available()
362
+ assert str(NapariCondaInstallerTool.available())
363
+ assert NapariPipInstallerTool.available()
334
364
 
335
365
 
336
366
  def test_unrecognized_tool():
337
367
  with pytest.raises(ValueError):
338
- InstallerQueue().install(tool='shrug', pkgs=[])
368
+ NapariInstallerQueue().install(tool='shrug', pkgs=[])
@@ -13,7 +13,7 @@ def test_user_agent():
13
13
  assert _user_agent()
14
14
 
15
15
 
16
- @flaky(max_runs=3, min_passes=2)
16
+ @flaky(max_runs=4, min_passes=2)
17
17
  def test_plugin_summaries():
18
18
  keys = [
19
19
  "name",