npe2 0.7.5rc0__tar.gz → 0.7.7__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.
- {npe2-0.7.5rc0 → npe2-0.7.7}/.github/dependabot.yml +5 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/.github/workflows/ci.yml +8 -8
- {npe2-0.7.5rc0 → npe2-0.7.7}/.github/workflows/test_conversion.yml +4 -4
- {npe2-0.7.5rc0 → npe2-0.7.7}/.github/workflows/update_changelog.yml +1 -1
- {npe2-0.7.5rc0 → npe2-0.7.7}/PKG-INFO +1 -1
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/example_plugin/some_module.py +4 -5
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/templates/_npe2_widgets_guide.md.jinja +15 -2
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_plugin_manager.py +21 -1
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_plugin_manager.py +30 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/.github/ISSUE_TEMPLATE.md +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/.github/workflows/test_all_plugins.yml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/.github_changelog_generator +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/.gitignore +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/.pre-commit-config.yaml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/CHANGELOG.md +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/LICENSE +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/Makefile +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/README.md +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/example_manifest.yaml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/example_plugin/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/render.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/templates/_npe2_contributions.md.jinja +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/templates/_npe2_manifest.md.jinja +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/templates/_npe2_readers_guide.md.jinja +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/templates/_npe2_sample_data_guide.md.jinja +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/_docs/templates/_npe2_writers_guide.md.jinja +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/codecov.yml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/docs/_config.yml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/docs/_toc.yml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/docs/index.md +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/docs/requirements.txt +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/pyproject.toml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/__main__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_command_registry.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_dynamic_plugin.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_inspection/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_inspection/_compile.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_inspection/_fetch.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_inspection/_from_npe1.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_inspection/_setuputils.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_inspection/_visitors.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_pydantic_compat.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_pytest_plugin.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/_setuptools_plugin.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/cli.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/implements.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/implements.pyi +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/io_utils.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/_bases.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/_npe1_adapter.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/_package_metadata.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/_validators.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_commands.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_configuration.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_contributions.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_icon.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_json_schema.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_keybindings.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_menus.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_readers.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_sample_data.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_submenu.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_themes.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_widgets.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/contributions/_writers.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/menus.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/package_metadata.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/schema.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/manifest/utils.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/plugin_manager.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/py.typed +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/src/npe2/types.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/conftest.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/fixtures/my-compiled-plugin/my_module/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/fixtures/my-compiled-plugin/my_module/_a.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/fixtures/my-compiled-plugin/my_module/_b.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/fixtures/my-compiled-plugin/setup.cfg +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/npe1-plugin/npe1-plugin-0.0.1.dist-info/METADATA +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/npe1-plugin/npe1-plugin-0.0.1.dist-info/RECORD +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/npe1-plugin/npe1-plugin-0.0.1.dist-info/entry_points.txt +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/npe1-plugin/npe1-plugin-0.0.1.dist-info/top_level.txt +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/npe1-plugin/npe1_module/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/npe1-plugin/setup.cfg +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/sample/_with_decorators.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/sample/my_plugin/__init__.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/sample/my_plugin/napari.yaml +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/sample/my_plugin-1.2.3.dist-info/METADATA +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/sample/my_plugin-1.2.3.dist-info/entry_points.txt +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/sample/my_plugin-1.2.3.dist-info/top_level.txt +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test__io_utils.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_all_plugins.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_cli.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_compile.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_config_contribution.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_contributions.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_conversion.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_docs.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_fetch.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_implements.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_manifest.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_npe1_adapter.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_package_meta.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_pm_module.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_pytest_plugin.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_setuptools_plugin.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_tmp_plugin.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_utils.py +0 -0
- {npe2-0.7.5rc0 → npe2-0.7.7}/tests/test_validations.py +0 -0
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
- uses: actions/checkout@v4
|
|
37
37
|
|
|
38
38
|
- name: Set up Python ${{ matrix.python-version }}
|
|
39
|
-
uses: actions/setup-python@
|
|
39
|
+
uses: actions/setup-python@v5
|
|
40
40
|
with:
|
|
41
41
|
python-version: ${{ matrix.python-version }}
|
|
42
42
|
|
|
@@ -70,7 +70,7 @@ jobs:
|
|
|
70
70
|
path: napari-from-github
|
|
71
71
|
fetch-depth: 0
|
|
72
72
|
|
|
73
|
-
- uses: actions/setup-python@
|
|
73
|
+
- uses: actions/setup-python@v5
|
|
74
74
|
with:
|
|
75
75
|
python-version: "3.10"
|
|
76
76
|
- name: Install
|
|
@@ -90,7 +90,7 @@ jobs:
|
|
|
90
90
|
runs-on: ubuntu-latest
|
|
91
91
|
steps:
|
|
92
92
|
- uses: actions/checkout@v4
|
|
93
|
-
- uses: actions/setup-python@
|
|
93
|
+
- uses: actions/setup-python@v5
|
|
94
94
|
with:
|
|
95
95
|
python-version: "3.x"
|
|
96
96
|
- name: Build schema
|
|
@@ -110,9 +110,9 @@ jobs:
|
|
|
110
110
|
if: always()
|
|
111
111
|
runs-on: ubuntu-latest
|
|
112
112
|
steps:
|
|
113
|
-
- uses: actions/checkout@
|
|
113
|
+
- uses: actions/checkout@v4
|
|
114
114
|
|
|
115
|
-
- uses: actions/setup-python@
|
|
115
|
+
- uses: actions/setup-python@v5
|
|
116
116
|
with:
|
|
117
117
|
python-version: "3.x"
|
|
118
118
|
cache-dependency-path: setup.cfg
|
|
@@ -138,7 +138,7 @@ jobs:
|
|
|
138
138
|
python -Im coverage report --format=markdown --skip-empty --skip-covered >> $GITHUB_STEP_SUMMARY
|
|
139
139
|
|
|
140
140
|
- name: Upload coverage data
|
|
141
|
-
uses: codecov/codecov-action@
|
|
141
|
+
uses: codecov/codecov-action@v4
|
|
142
142
|
with:
|
|
143
143
|
fail_ci_if_error: true
|
|
144
144
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -155,7 +155,7 @@ jobs:
|
|
|
155
155
|
- uses: actions/checkout@v4
|
|
156
156
|
|
|
157
157
|
- name: Set up Python
|
|
158
|
-
uses: actions/setup-python@
|
|
158
|
+
uses: actions/setup-python@v5
|
|
159
159
|
with:
|
|
160
160
|
python-version: "3.x"
|
|
161
161
|
|
|
@@ -180,7 +180,7 @@ jobs:
|
|
|
180
180
|
TWINE_USERNAME: __token__
|
|
181
181
|
TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }}
|
|
182
182
|
|
|
183
|
-
- uses: softprops/action-gh-release@
|
|
183
|
+
- uses: softprops/action-gh-release@v2
|
|
184
184
|
if: startsWith(github.ref, 'refs/tags/')
|
|
185
185
|
with:
|
|
186
186
|
generate_release_notes: true
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
|
30
30
|
|
|
31
31
|
steps:
|
|
32
32
|
- uses: tlambert03/setup-qt-libs@v1
|
|
33
|
-
- uses: actions/setup-python@
|
|
33
|
+
- uses: actions/setup-python@v5
|
|
34
34
|
with:
|
|
35
35
|
python-version: 3.9
|
|
36
36
|
|
|
@@ -61,7 +61,7 @@ jobs:
|
|
|
61
61
|
|
|
62
62
|
- name: Test Conversion
|
|
63
63
|
id: test-without-napari
|
|
64
|
-
uses: aganders3/headless-gui@
|
|
64
|
+
uses: aganders3/headless-gui@v2
|
|
65
65
|
continue-on-error: true
|
|
66
66
|
with:
|
|
67
67
|
run: npe2 convert ./plugin_repo
|
|
@@ -73,13 +73,13 @@ jobs:
|
|
|
73
73
|
- name: Test Conversion again with napari
|
|
74
74
|
id: test-with-napari
|
|
75
75
|
if: ${{ steps.test-without-napari.outcome == 'failure' }}
|
|
76
|
-
uses: aganders3/headless-gui@
|
|
76
|
+
uses: aganders3/headless-gui@v2
|
|
77
77
|
with:
|
|
78
78
|
run: npe2 convert ./plugin_repo
|
|
79
79
|
|
|
80
80
|
- name: Test Conversion again with napari
|
|
81
81
|
if: ${{ steps.test-without-napari.outcome == 'failure' && steps.test-with-napari.outcome == 'failure' }}
|
|
82
|
-
uses: aganders3/headless-gui@
|
|
82
|
+
uses: aganders3/headless-gui@v2
|
|
83
83
|
with:
|
|
84
84
|
# try without modifying directory
|
|
85
85
|
run: npe2 convert -n ${{ matrix.plugin }}
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
20
20
|
repo: napari/npe2
|
|
21
21
|
- name: Create Pull Request
|
|
22
|
-
uses: peter-evans/create-pull-request@
|
|
22
|
+
uses: peter-evans/create-pull-request@v6
|
|
23
23
|
with:
|
|
24
24
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
25
25
|
commit-message: Automatic changelog update
|
|
@@ -5,11 +5,10 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
|
5
5
|
from magicgui import magic_factory
|
|
6
6
|
from qtpy.QtWidgets import QWidget
|
|
7
7
|
|
|
8
|
-
from npe2.types import LayerData, PathOrPaths, ReaderFunction
|
|
9
|
-
|
|
10
8
|
if TYPE_CHECKING:
|
|
11
9
|
import napari.types
|
|
12
10
|
import napari.viewer
|
|
11
|
+
from npe2.types import LayerData, PathOrPaths, ReaderFunction
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def write_points(path: str, layer_data: Any, attributes: Dict[str, Any]) -> List[str]:
|
|
@@ -20,7 +19,7 @@ def write_points(path: str, layer_data: Any, attributes: Dict[str, Any]) -> List
|
|
|
20
19
|
return [path]
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
def get_reader(path: PathOrPaths) -> Optional[ReaderFunction]:
|
|
22
|
+
def get_reader(path: "PathOrPaths") -> Optional["ReaderFunction"]:
|
|
24
23
|
# If we recognize the format, we return the actual reader function
|
|
25
24
|
if isinstance(path, str) and path.endswith(".xyz"):
|
|
26
25
|
return xyz_file_reader
|
|
@@ -28,7 +27,7 @@ def get_reader(path: PathOrPaths) -> Optional[ReaderFunction]:
|
|
|
28
27
|
return None
|
|
29
28
|
|
|
30
29
|
|
|
31
|
-
def xyz_file_reader(path: PathOrPaths) -> List[LayerData]:
|
|
30
|
+
def xyz_file_reader(path: "PathOrPaths") -> List["LayerData"]:
|
|
32
31
|
data = ... # somehow read data from path
|
|
33
32
|
layer_attributes = {"name": "etc..."}
|
|
34
33
|
return [(data, layer_attributes)]
|
|
@@ -64,7 +63,7 @@ def threshold(
|
|
|
64
63
|
return (image > threshold).astype(int)
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
def create_fractal() -> List[LayerData]:
|
|
66
|
+
def create_fractal() -> List["LayerData"]:
|
|
68
67
|
"""An example of a Sample Data Function.
|
|
69
68
|
|
|
70
69
|
Note: Sample Data with URIs don't need python code.
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
(widgets-contribution-guide)=
|
|
2
2
|
## Widgets
|
|
3
3
|
|
|
4
|
-
Widget
|
|
5
|
-
elements (aka "widgets") to the user interface.
|
|
4
|
+
Widget plugin contributions allow developers to add novel graphical
|
|
5
|
+
elements (aka "widgets") to the user interface. For a full introduction to
|
|
6
|
+
creating `napari` widgets see [](creating-widgets).
|
|
7
|
+
|
|
8
|
+
Widgets can request
|
|
6
9
|
access to the viewer instance in which they are docked, enabling a broad
|
|
7
10
|
range of functionality: essentially, anything that can be done with the
|
|
8
11
|
napari `Viewer` and `Layer` APIs can be accomplished with widgets.
|
|
@@ -58,6 +61,16 @@ specification:
|
|
|
58
61
|
hook specification. In the new `npe2` pattern, one uses the `autogenerate`
|
|
59
62
|
field in the [WidgetContribution](contributions-widgets).
|
|
60
63
|
|
|
64
|
+
For more examples see [](creating-widgets) and
|
|
65
|
+
[GUI gallery examples](https://napari.org/stable/_tags/gui.html) (only a subset
|
|
66
|
+
involve widgets). Additionally,
|
|
67
|
+
[cookiecutter-napari-plugin](https://github.com/napari/cookiecutter-napari-plugin)
|
|
68
|
+
has more robust widget examples that you can adapt to your needs.
|
|
69
|
+
|
|
70
|
+
```{note}
|
|
71
|
+
Notice that `napari` type annotations are strings and not imported. This is to
|
|
72
|
+
avoid including `napari` as a plugin dependency when not strictly required.
|
|
73
|
+
```
|
|
61
74
|
|
|
62
75
|
### Widget example
|
|
63
76
|
|
|
@@ -4,7 +4,7 @@ import contextlib
|
|
|
4
4
|
import os
|
|
5
5
|
import urllib
|
|
6
6
|
import warnings
|
|
7
|
-
from collections import Counter
|
|
7
|
+
from collections import Counter, defaultdict
|
|
8
8
|
from fnmatch import fnmatch
|
|
9
9
|
from importlib import metadata
|
|
10
10
|
from logging import getLogger
|
|
@@ -38,6 +38,7 @@ from .types import PathLike, PythonName
|
|
|
38
38
|
if TYPE_CHECKING:
|
|
39
39
|
from .manifest.contributions import (
|
|
40
40
|
CommandContribution,
|
|
41
|
+
MenuCommand,
|
|
41
42
|
MenuItem,
|
|
42
43
|
ReaderContribution,
|
|
43
44
|
SampleDataContribution,
|
|
@@ -235,6 +236,9 @@ class PluginManager:
|
|
|
235
236
|
self._manifests: Dict[PluginName, PluginManifest] = {}
|
|
236
237
|
self.events = PluginManagerEvents(self)
|
|
237
238
|
self._npe1_adapters: List[NPE1Adapter] = []
|
|
239
|
+
self._command_menu_map: Dict[
|
|
240
|
+
str, Dict[str, Dict[str, List[MenuCommand]]]
|
|
241
|
+
] = defaultdict(dict)
|
|
238
242
|
|
|
239
243
|
# up to napari 0.4.15, discovery happened in the init here
|
|
240
244
|
# so if we're running on an older version of napari, we need to discover
|
|
@@ -358,14 +362,28 @@ class PluginManager:
|
|
|
358
362
|
self._npe1_adapters.append(manifest)
|
|
359
363
|
else:
|
|
360
364
|
self._contrib.index_contributions(manifest)
|
|
365
|
+
self._populate_command_menu_map(manifest)
|
|
361
366
|
self.events.plugins_registered.emit({manifest})
|
|
362
367
|
|
|
368
|
+
def _populate_command_menu_map(self, manifest: PluginManifest):
|
|
369
|
+
# map of manifest -> command -> menu_id -> list[items]
|
|
370
|
+
self._command_menu_map[manifest.name] = defaultdict(lambda: defaultdict(list))
|
|
371
|
+
menu_map = self._command_menu_map[manifest.name] # just for conciseness below
|
|
372
|
+
for menu_id, menu_items in manifest.contributions.menus.items() or ():
|
|
373
|
+
# command IDs are keys in map
|
|
374
|
+
# each value is a dict menu_id: list of MenuCommands
|
|
375
|
+
# for the command and menu
|
|
376
|
+
for item in menu_items:
|
|
377
|
+
if (command_id := getattr(item, "command", None)) is not None:
|
|
378
|
+
menu_map[command_id][menu_id].append(item)
|
|
379
|
+
|
|
363
380
|
def unregister(self, key: PluginName):
|
|
364
381
|
"""Unregister plugin named `key`."""
|
|
365
382
|
if key not in self._manifests:
|
|
366
383
|
raise ValueError(f"No registered plugin named {key!r}") # pragma: no cover
|
|
367
384
|
self.deactivate(key)
|
|
368
385
|
self._contrib.remove_contributions(key)
|
|
386
|
+
self._command_menu_map.pop(key)
|
|
369
387
|
self._manifests.pop(key)
|
|
370
388
|
|
|
371
389
|
def activate(self, key: PluginName) -> PluginContext:
|
|
@@ -448,6 +466,7 @@ class PluginManager:
|
|
|
448
466
|
mf = self._manifests.get(plugin_name)
|
|
449
467
|
if mf is not None:
|
|
450
468
|
self._contrib.index_contributions(mf)
|
|
469
|
+
self._populate_command_menu_map(mf)
|
|
451
470
|
self.events.enablement_changed({plugin_name}, {})
|
|
452
471
|
|
|
453
472
|
def disable(self, plugin_name: PluginName) -> None:
|
|
@@ -467,6 +486,7 @@ class PluginManager:
|
|
|
467
486
|
|
|
468
487
|
self._disabled_plugins.add(plugin_name)
|
|
469
488
|
self._contrib.remove_contributions(plugin_name)
|
|
489
|
+
self._command_menu_map.pop(plugin_name, None)
|
|
470
490
|
self.events.enablement_changed({}, {plugin_name})
|
|
471
491
|
|
|
472
492
|
def is_disabled(self, plugin_name: str) -> bool:
|
|
@@ -236,3 +236,33 @@ def test_plugin_context_dispose_error(caplog):
|
|
|
236
236
|
pm.get_context("test").register_disposable(dummy_error)
|
|
237
237
|
pm.deactivate("test")
|
|
238
238
|
assert caplog.records[0].msg == "Error while disposing test; This is an error"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_command_menu_map(uses_sample_plugin, plugin_manager: PluginManager):
|
|
242
|
+
"""Test that the command menu map is correctly populated."""
|
|
243
|
+
pm = PluginManager.instance()
|
|
244
|
+
assert SAMPLE_PLUGIN_NAME in pm._manifests
|
|
245
|
+
assert SAMPLE_PLUGIN_NAME in pm._command_menu_map
|
|
246
|
+
|
|
247
|
+
# contains correct commands
|
|
248
|
+
command_menu_map = pm._command_menu_map[SAMPLE_PLUGIN_NAME]
|
|
249
|
+
assert "my-plugin.hello_world" in command_menu_map
|
|
250
|
+
assert "my-plugin.another_command" in command_menu_map
|
|
251
|
+
|
|
252
|
+
# commands point to correct menus
|
|
253
|
+
assert len(cmd_menu := command_menu_map["my-plugin.hello_world"]) == 1
|
|
254
|
+
assert "/napari/layer_context" in cmd_menu
|
|
255
|
+
assert len(cmd_menu := command_menu_map["my-plugin.another_command"]) == 1
|
|
256
|
+
assert "mysubmenu" in cmd_menu
|
|
257
|
+
|
|
258
|
+
# enable/disable
|
|
259
|
+
pm.disable(SAMPLE_PLUGIN_NAME)
|
|
260
|
+
assert SAMPLE_PLUGIN_NAME not in pm._command_menu_map
|
|
261
|
+
pm.enable(SAMPLE_PLUGIN_NAME)
|
|
262
|
+
assert SAMPLE_PLUGIN_NAME in pm._command_menu_map
|
|
263
|
+
|
|
264
|
+
# register/unregister
|
|
265
|
+
pm.unregister(SAMPLE_PLUGIN_NAME)
|
|
266
|
+
assert SAMPLE_PLUGIN_NAME not in pm._command_menu_map
|
|
267
|
+
pm.register(SAMPLE_PLUGIN_NAME)
|
|
268
|
+
assert SAMPLE_PLUGIN_NAME in pm._command_menu_map
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|