npe2 0.7.9rc0__py3-none-any.whl → 0.8.0rc0__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.
- npe2/_command_registry.py +6 -5
- npe2/_dynamic_plugin.py +25 -27
- npe2/_inspection/_compile.py +9 -8
- npe2/_inspection/_fetch.py +18 -30
- npe2/_inspection/_from_npe1.py +26 -32
- npe2/_inspection/_setuputils.py +14 -14
- npe2/_inspection/_visitors.py +26 -21
- npe2/_plugin_manager.py +45 -57
- npe2/_pydantic_util.py +53 -0
- npe2/_pytest_plugin.py +3 -4
- npe2/_setuptools_plugin.py +9 -9
- npe2/cli.py +25 -21
- npe2/implements.py +13 -10
- npe2/implements.pyi +3 -2
- npe2/io_utils.py +40 -44
- npe2/manifest/_bases.py +15 -14
- npe2/manifest/_npe1_adapter.py +3 -3
- npe2/manifest/_package_metadata.py +40 -47
- npe2/manifest/contributions/_commands.py +16 -14
- npe2/manifest/contributions/_configuration.py +22 -20
- npe2/manifest/contributions/_contributions.py +13 -14
- npe2/manifest/contributions/_icon.py +3 -5
- npe2/manifest/contributions/_json_schema.py +86 -89
- npe2/manifest/contributions/_keybindings.py +5 -6
- npe2/manifest/contributions/_menus.py +11 -9
- npe2/manifest/contributions/_readers.py +10 -8
- npe2/manifest/contributions/_sample_data.py +16 -15
- npe2/manifest/contributions/_submenu.py +2 -4
- npe2/manifest/contributions/_themes.py +18 -22
- npe2/manifest/contributions/_widgets.py +6 -5
- npe2/manifest/contributions/_writers.py +22 -18
- npe2/manifest/schema.py +82 -70
- npe2/manifest/utils.py +24 -28
- npe2/plugin_manager.py +17 -14
- npe2/types.py +16 -19
- {npe2-0.7.9rc0.dist-info → npe2-0.8.0rc0.dist-info}/METADATA +13 -7
- npe2-0.8.0rc0.dist-info/RECORD +49 -0
- {npe2-0.7.9rc0.dist-info → npe2-0.8.0rc0.dist-info}/WHEEL +1 -1
- npe2/_pydantic_compat.py +0 -54
- npe2-0.7.9rc0.dist-info/RECORD +0 -49
- {npe2-0.7.9rc0.dist-info → npe2-0.8.0rc0.dist-info}/entry_points.txt +0 -0
- {npe2-0.7.9rc0.dist-info → npe2-0.8.0rc0.dist-info}/licenses/LICENSE +0 -0
npe2/_command_registry.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from functools import partial
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
6
7
|
|
|
7
8
|
from psygnal import Signal
|
|
8
9
|
|
|
@@ -19,8 +20,8 @@ if TYPE_CHECKING:
|
|
|
19
20
|
@dataclass
|
|
20
21
|
class CommandHandler:
|
|
21
22
|
id: str
|
|
22
|
-
function:
|
|
23
|
-
python_name:
|
|
23
|
+
function: Callable | None = None
|
|
24
|
+
python_name: PythonName | None = None
|
|
24
25
|
|
|
25
26
|
def resolve(self) -> Callable:
|
|
26
27
|
if self.function is not None:
|
|
@@ -50,9 +51,9 @@ class CommandRegistry:
|
|
|
50
51
|
command_unregistered = Signal(str)
|
|
51
52
|
|
|
52
53
|
def __init__(self) -> None:
|
|
53
|
-
self._commands:
|
|
54
|
+
self._commands: dict[str, CommandHandler] = {}
|
|
54
55
|
|
|
55
|
-
def register(self, id: str, command:
|
|
56
|
+
def register(self, id: str, command: Callable | str) -> PDisposable:
|
|
56
57
|
"""Register a command under `id`.
|
|
57
58
|
|
|
58
59
|
Parameters
|
npe2/_dynamic_plugin.py
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from typing import (
|
|
4
5
|
Any,
|
|
5
|
-
Callable,
|
|
6
|
-
Dict,
|
|
7
6
|
Generic,
|
|
8
|
-
List,
|
|
9
7
|
Literal,
|
|
10
|
-
Optional,
|
|
11
|
-
Type,
|
|
12
8
|
TypeVar,
|
|
13
|
-
Union,
|
|
14
9
|
overload,
|
|
15
10
|
)
|
|
16
11
|
|
|
17
|
-
from
|
|
12
|
+
from pydantic import BaseModel, ValidationError
|
|
13
|
+
|
|
14
|
+
from npe2._pydantic_util import iter_inner_types
|
|
18
15
|
|
|
19
16
|
from ._plugin_manager import PluginManager
|
|
20
17
|
from .manifest.contributions import (
|
|
@@ -33,12 +30,15 @@ T = TypeVar("T", bound=Callable[..., Any])
|
|
|
33
30
|
|
|
34
31
|
# a mapping of contribution type to string name in the ContributionPoints
|
|
35
32
|
# e.g. {ReaderContribution: 'readers'}
|
|
36
|
-
|
|
37
|
-
for
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
CONTRIB_ANNOTATIONS = {
|
|
34
|
+
v.annotation: k for k, v in ContributionPoints.model_fields.items()
|
|
35
|
+
}
|
|
36
|
+
CONTRIB_NAMES = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
for key, value in CONTRIB_ANNOTATIONS.items():
|
|
40
|
+
for type_ in iter_inner_types(key):
|
|
41
|
+
CONTRIB_NAMES[type_] = value
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class DynamicPlugin:
|
|
@@ -65,8 +65,8 @@ class DynamicPlugin:
|
|
|
65
65
|
def __init__(
|
|
66
66
|
self,
|
|
67
67
|
name: str = "temp-plugin",
|
|
68
|
-
plugin_manager:
|
|
69
|
-
manifest:
|
|
68
|
+
plugin_manager: PluginManager | None = None,
|
|
69
|
+
manifest: PluginManifest | None = None,
|
|
70
70
|
) -> None:
|
|
71
71
|
if isinstance(manifest, PluginManifest):
|
|
72
72
|
self.manifest = manifest
|
|
@@ -108,12 +108,12 @@ class DynamicPlugin:
|
|
|
108
108
|
return self._pm if self._pm is not None else PluginManager.instance()
|
|
109
109
|
|
|
110
110
|
@plugin_manager.setter
|
|
111
|
-
def plugin_manager(self, pm:
|
|
111
|
+
def plugin_manager(self, pm: PluginManager | None) -> None:
|
|
112
112
|
"""Set the plugin manager this plugin is registered in."""
|
|
113
113
|
if pm is self._pm: # pragma: no cover
|
|
114
114
|
return
|
|
115
115
|
|
|
116
|
-
my_cmds:
|
|
116
|
+
my_cmds: dict[str, Callable] = {
|
|
117
117
|
k: v.function
|
|
118
118
|
for k, v in self.plugin_manager.commands._commands.items()
|
|
119
119
|
if k.startswith(self.manifest.name) and v.function
|
|
@@ -133,8 +133,8 @@ class DynamicPlugin:
|
|
|
133
133
|
|
|
134
134
|
def spawn(
|
|
135
135
|
self,
|
|
136
|
-
name:
|
|
137
|
-
plugin_manager:
|
|
136
|
+
name: str | None = None,
|
|
137
|
+
plugin_manager: PluginManager | None = None,
|
|
138
138
|
register: bool = False,
|
|
139
139
|
) -> DynamicPlugin:
|
|
140
140
|
"""Create a new DynamicPlugin instance with the same plugin manager.
|
|
@@ -197,7 +197,7 @@ class ContributionDecorator(Generic[C]):
|
|
|
197
197
|
of a specific `contrib_type` to a temporary plugin.
|
|
198
198
|
"""
|
|
199
199
|
|
|
200
|
-
def __init__(self, plugin: DynamicPlugin, contrib_type:
|
|
200
|
+
def __init__(self, plugin: DynamicPlugin, contrib_type: type[C]) -> None:
|
|
201
201
|
self.plugin = plugin
|
|
202
202
|
self.contrib_type = contrib_type
|
|
203
203
|
self._contrib_name = CONTRIB_NAMES[self.contrib_type]
|
|
@@ -207,12 +207,10 @@ class ContributionDecorator(Generic[C]):
|
|
|
207
207
|
|
|
208
208
|
@overload
|
|
209
209
|
def __call__(
|
|
210
|
-
self, func:
|
|
210
|
+
self, func: Literal[None] | None = None, **kwargs
|
|
211
211
|
) -> Callable[[T], T]: ...
|
|
212
212
|
|
|
213
|
-
def __call__(
|
|
214
|
-
self, func: Optional[T] = None, **kwargs
|
|
215
|
-
) -> Union[T, Callable[[T], T]]:
|
|
213
|
+
def __call__(self, func: T | None = None, **kwargs) -> T | Callable[[T], T]:
|
|
216
214
|
"""Decorate function as providing this contrubtion type.
|
|
217
215
|
|
|
218
216
|
This is the actual decorator used when one calls, eg.
|
|
@@ -263,7 +261,7 @@ class ContributionDecorator(Generic[C]):
|
|
|
263
261
|
cmd_kwargs = {
|
|
264
262
|
k: kwargs.pop(k)
|
|
265
263
|
for k in list(kwargs)
|
|
266
|
-
if k in CommandContribution.
|
|
264
|
+
if k in CommandContribution.model_fields
|
|
267
265
|
}
|
|
268
266
|
cmd = CommandContribution(**cmd_kwargs)
|
|
269
267
|
self.commands.append(cmd)
|
|
@@ -277,14 +275,14 @@ class ContributionDecorator(Generic[C]):
|
|
|
277
275
|
return self.plugin.manifest
|
|
278
276
|
|
|
279
277
|
@property
|
|
280
|
-
def contribution_list(self) ->
|
|
278
|
+
def contribution_list(self) -> list[C]:
|
|
281
279
|
"""Return contributions of this type in the associated manifest."""
|
|
282
280
|
if not getattr(self._mf.contributions, self._contrib_name):
|
|
283
281
|
setattr(self._mf.contributions, self._contrib_name, [])
|
|
284
282
|
return getattr(self._mf.contributions, self._contrib_name)
|
|
285
283
|
|
|
286
284
|
@property
|
|
287
|
-
def commands(self) ->
|
|
285
|
+
def commands(self) -> list[CommandContribution]:
|
|
288
286
|
"""Return the CommandContributions in the associated manifest."""
|
|
289
287
|
if not self._mf.contributions.commands:
|
|
290
288
|
self._mf.contributions.commands = []
|
npe2/_inspection/_compile.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from collections.abc import Iterator, Sequence
|
|
1
2
|
from pathlib import Path
|
|
2
|
-
from typing import
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
4
5
|
from npe2.manifest import PluginManifest, contributions
|
|
5
6
|
from npe2.manifest.utils import merge_contributions, merge_manifests
|
|
@@ -8,21 +9,21 @@ from ._setuputils import get_package_dir_info
|
|
|
8
9
|
from ._visitors import find_npe2_module_contributions
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def find_packages(where:
|
|
12
|
+
def find_packages(where: str | Path = ".") -> list[Path]:
|
|
12
13
|
"""Return all folders that have an __init__.py file"""
|
|
13
14
|
return [p.parent for p in Path(where).resolve().rglob("**/__init__.py")]
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def get_package_name(where:
|
|
17
|
+
def get_package_name(where: str | Path = ".") -> str:
|
|
17
18
|
return get_package_dir_info(where).package_name
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def compile(
|
|
21
|
-
src_dir:
|
|
22
|
-
dest:
|
|
22
|
+
src_dir: str | Path,
|
|
23
|
+
dest: str | Path | None = None,
|
|
23
24
|
packages: Sequence[str] = (),
|
|
24
25
|
plugin_name: str = "",
|
|
25
|
-
template:
|
|
26
|
+
template: str | Path | None = None,
|
|
26
27
|
) -> PluginManifest:
|
|
27
28
|
"""Compile plugin manifest from `src_dir`, where is a top-level repo.
|
|
28
29
|
|
|
@@ -75,7 +76,7 @@ def compile(
|
|
|
75
76
|
if not plugin_name:
|
|
76
77
|
plugin_name = get_package_name(src_path)
|
|
77
78
|
|
|
78
|
-
contribs:
|
|
79
|
+
contribs: list[contributions.ContributionPoints] = []
|
|
79
80
|
for pkg_path in _packages:
|
|
80
81
|
top_mod = pkg_path.name
|
|
81
82
|
# TODO: add more tests with more complicated package structures
|
|
@@ -103,7 +104,7 @@ def compile(
|
|
|
103
104
|
return mf
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
def _iter_modules(path: Path) -> Iterator[
|
|
107
|
+
def _iter_modules(path: Path) -> Iterator[tuple[Path, str]]:
|
|
107
108
|
"""Return all python modules in path"""
|
|
108
109
|
for p in path.glob("*.py"):
|
|
109
110
|
yield p, "" if p.name == "__init__.py" else p.stem
|
npe2/_inspection/_fetch.py
CHANGED
|
@@ -4,21 +4,16 @@ import io
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import subprocess
|
|
7
|
+
import sys
|
|
7
8
|
import tempfile
|
|
8
|
-
from
|
|
9
|
+
from collections.abc import Iterator
|
|
10
|
+
from contextlib import AbstractContextManager, contextmanager
|
|
9
11
|
from functools import lru_cache
|
|
10
12
|
from importlib import metadata
|
|
11
13
|
from logging import getLogger
|
|
12
14
|
from pathlib import Path
|
|
13
15
|
from typing import (
|
|
14
16
|
TYPE_CHECKING,
|
|
15
|
-
Any,
|
|
16
|
-
ContextManager,
|
|
17
|
-
Dict,
|
|
18
|
-
Iterator,
|
|
19
|
-
List,
|
|
20
|
-
Optional,
|
|
21
|
-
Union,
|
|
22
17
|
)
|
|
23
18
|
from unittest.mock import patch
|
|
24
19
|
from urllib import error, request
|
|
@@ -36,7 +31,6 @@ NPE1_ENTRY_POINT = "napari.plugin"
|
|
|
36
31
|
NPE2_ENTRY_POINT = "napari.manifest"
|
|
37
32
|
__all__ = [
|
|
38
33
|
"fetch_manifest",
|
|
39
|
-
"get_hub_plugin",
|
|
40
34
|
"get_pypi_url",
|
|
41
35
|
]
|
|
42
36
|
|
|
@@ -118,7 +112,7 @@ def _guard_cwd() -> Iterator[None]:
|
|
|
118
112
|
os.chdir(current)
|
|
119
113
|
|
|
120
114
|
|
|
121
|
-
def _build_wheel(src:
|
|
115
|
+
def _build_wheel(src: str | Path) -> Path:
|
|
122
116
|
"""Build a wheel from a source directory and extract it into dest."""
|
|
123
117
|
from build.__main__ import build_package
|
|
124
118
|
|
|
@@ -148,7 +142,7 @@ def get_manifest_from_wheel(src: str) -> PluginManifest:
|
|
|
148
142
|
return _manifest_from_extracted_wheel(Path(td))
|
|
149
143
|
|
|
150
144
|
|
|
151
|
-
def _build_src_and_extract_manifest(src_dir:
|
|
145
|
+
def _build_src_and_extract_manifest(src_dir: str | Path) -> PluginManifest:
|
|
152
146
|
"""Build a wheel from a source directory and extract the manifest."""
|
|
153
147
|
return _manifest_from_extracted_wheel(_build_wheel(src_dir))
|
|
154
148
|
|
|
@@ -162,7 +156,7 @@ def _get_manifest_from_zip_url(url: str) -> PluginManifest:
|
|
|
162
156
|
"""
|
|
163
157
|
from npe2.manifest import PluginManifest
|
|
164
158
|
|
|
165
|
-
def find_manifest_file(root: Path) ->
|
|
159
|
+
def find_manifest_file(root: Path) -> Path | None:
|
|
166
160
|
"""Recursively find a napari manifest file."""
|
|
167
161
|
# Check current directory for manifest files
|
|
168
162
|
for filename in ["napari.yaml", "napari.yml"]:
|
|
@@ -251,9 +245,7 @@ def _get_manifest_from_git_url(url: str) -> PluginManifest:
|
|
|
251
245
|
return _build_src_and_extract_manifest(td)
|
|
252
246
|
|
|
253
247
|
|
|
254
|
-
def fetch_manifest(
|
|
255
|
-
package_or_url: str, version: Optional[str] = None
|
|
256
|
-
) -> PluginManifest:
|
|
248
|
+
def fetch_manifest(package_or_url: str, version: str | None = None) -> PluginManifest:
|
|
257
249
|
"""Fetch a manifest for a pypi package name or URL to a wheel or source.
|
|
258
250
|
|
|
259
251
|
Parameters
|
|
@@ -307,7 +299,7 @@ def fetch_manifest(
|
|
|
307
299
|
|
|
308
300
|
|
|
309
301
|
def _manifest_from_pypi_sdist(
|
|
310
|
-
package: str, version:
|
|
302
|
+
package: str, version: str | None = None
|
|
311
303
|
) -> PluginManifest:
|
|
312
304
|
"""Extract a manifest from a source distribution on pypi."""
|
|
313
305
|
with _tmp_pypi_sdist_download(package, version) as td:
|
|
@@ -322,7 +314,7 @@ def _pypi_info(package: str) -> dict:
|
|
|
322
314
|
|
|
323
315
|
|
|
324
316
|
def get_pypi_url(
|
|
325
|
-
package: str, version:
|
|
317
|
+
package: str, version: str | None = None, packagetype: str | None = None
|
|
326
318
|
) -> str:
|
|
327
319
|
"""Get URL for a package on PyPI.
|
|
328
320
|
|
|
@@ -358,7 +350,7 @@ def get_pypi_url(
|
|
|
358
350
|
if version:
|
|
359
351
|
version = version.lstrip("v")
|
|
360
352
|
try:
|
|
361
|
-
_releases:
|
|
353
|
+
_releases: list[dict] = data["releases"][version]
|
|
362
354
|
except KeyError as e: # pragma: no cover
|
|
363
355
|
raise ValueError(f"{package} does not have version {version}") from e
|
|
364
356
|
else:
|
|
@@ -391,28 +383,24 @@ def _tmp_targz_download(url: str) -> Iterator[Path]:
|
|
|
391
383
|
|
|
392
384
|
with tempfile.TemporaryDirectory() as td, request.urlopen(url) as f:
|
|
393
385
|
with tarfile.open(fileobj=f, mode="r:gz") as tar:
|
|
394
|
-
|
|
386
|
+
if sys.version_info >= (3, 11):
|
|
387
|
+
tar.extractall(td, filter="data")
|
|
388
|
+
else:
|
|
389
|
+
tar.extractall(td)
|
|
395
390
|
yield Path(td)
|
|
396
391
|
|
|
397
392
|
|
|
398
393
|
def _tmp_pypi_wheel_download(
|
|
399
|
-
package: str, version:
|
|
400
|
-
) ->
|
|
394
|
+
package: str, version: str | None = None
|
|
395
|
+
) -> AbstractContextManager[Path]:
|
|
401
396
|
url = get_pypi_url(package, version=version, packagetype="bdist_wheel")
|
|
402
397
|
logger.debug(f"downloading wheel for {package} {version or ''}")
|
|
403
398
|
return _tmp_zip_download(url)
|
|
404
399
|
|
|
405
400
|
|
|
406
401
|
def _tmp_pypi_sdist_download(
|
|
407
|
-
package: str, version:
|
|
408
|
-
) ->
|
|
402
|
+
package: str, version: str | None = None
|
|
403
|
+
) -> AbstractContextManager[Path]:
|
|
409
404
|
url = get_pypi_url(package, version=version, packagetype="sdist")
|
|
410
405
|
logger.debug(f"downloading sdist for {package} {version or ''}")
|
|
411
406
|
return _tmp_targz_download(url)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
@lru_cache
|
|
415
|
-
def get_hub_plugin(plugin_name: str) -> Dict[str, Any]:
|
|
416
|
-
"""Return hub information for a specific plugin."""
|
|
417
|
-
with request.urlopen(f"https://api.napari-hub.org/plugins/{plugin_name}") as r:
|
|
418
|
-
return json.load(r)
|
npe2/_inspection/_from_npe1.py
CHANGED
|
@@ -3,6 +3,8 @@ import inspect
|
|
|
3
3
|
import re
|
|
4
4
|
import sys
|
|
5
5
|
import warnings
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from collections.abc import Callable, Iterator
|
|
6
8
|
from configparser import ConfigParser
|
|
7
9
|
from functools import lru_cache, partial
|
|
8
10
|
from importlib import import_module, metadata
|
|
@@ -11,14 +13,6 @@ from pathlib import Path
|
|
|
11
13
|
from types import ModuleType
|
|
12
14
|
from typing import (
|
|
13
15
|
Any,
|
|
14
|
-
Callable,
|
|
15
|
-
DefaultDict,
|
|
16
|
-
Dict,
|
|
17
|
-
Iterator,
|
|
18
|
-
List,
|
|
19
|
-
Optional,
|
|
20
|
-
Tuple,
|
|
21
|
-
Union,
|
|
22
16
|
cast,
|
|
23
17
|
)
|
|
24
18
|
|
|
@@ -48,8 +42,8 @@ class HookImplementation:
|
|
|
48
42
|
def __init__(
|
|
49
43
|
self,
|
|
50
44
|
function: Callable,
|
|
51
|
-
plugin:
|
|
52
|
-
plugin_name:
|
|
45
|
+
plugin: ModuleType | None = None,
|
|
46
|
+
plugin_name: str | None = None,
|
|
53
47
|
**kwargs,
|
|
54
48
|
):
|
|
55
49
|
self.function = function
|
|
@@ -68,7 +62,7 @@ class HookImplementation:
|
|
|
68
62
|
|
|
69
63
|
|
|
70
64
|
def iter_hookimpls(
|
|
71
|
-
module: ModuleType, plugin_name:
|
|
65
|
+
module: ModuleType, plugin_name: str | None = None
|
|
72
66
|
) -> Iterator[HookImplementation]:
|
|
73
67
|
# yield all routines in module that have "{self.project_name}_impl" attr
|
|
74
68
|
for name in dir(module):
|
|
@@ -80,14 +74,14 @@ def iter_hookimpls(
|
|
|
80
74
|
|
|
81
75
|
|
|
82
76
|
@lru_cache
|
|
83
|
-
def plugin_packages() ->
|
|
77
|
+
def plugin_packages() -> list[PackageInfo]:
|
|
84
78
|
"""List of all packages with napari entry points.
|
|
85
79
|
|
|
86
80
|
This is useful to help resolve naming issues (due to the terrible confusion
|
|
87
81
|
around *what* a npe1 plugin name actually was).
|
|
88
82
|
"""
|
|
89
83
|
|
|
90
|
-
packages:
|
|
84
|
+
packages: list[PackageInfo] = []
|
|
91
85
|
for dist in metadata.distributions():
|
|
92
86
|
packages.extend(
|
|
93
87
|
PackageInfo(package_name=dist.metadata["Name"], entry_points=[ep])
|
|
@@ -99,8 +93,8 @@ def plugin_packages() -> List[PackageInfo]:
|
|
|
99
93
|
|
|
100
94
|
|
|
101
95
|
def manifest_from_npe1(
|
|
102
|
-
plugin:
|
|
103
|
-
module:
|
|
96
|
+
plugin: str | metadata.Distribution | None = None,
|
|
97
|
+
module: Any | None = None,
|
|
104
98
|
adapter=False,
|
|
105
99
|
) -> PluginManifest:
|
|
106
100
|
"""Return manifest object given npe1 plugin or package name.
|
|
@@ -109,7 +103,7 @@ def manifest_from_npe1(
|
|
|
109
103
|
|
|
110
104
|
Parameters
|
|
111
105
|
----------
|
|
112
|
-
plugin :
|
|
106
|
+
plugin : str | metadata.Distribution | None
|
|
113
107
|
Name of package/plugin to convert. Or a `metadata.Distribution` object.
|
|
114
108
|
If a string, this function should be prepared to accept both the name of the
|
|
115
109
|
package, and the name of an npe1 `napari.plugin` entry_point. by default None
|
|
@@ -122,7 +116,7 @@ def manifest_from_npe1(
|
|
|
122
116
|
python_names that are not supported natively by npe2. by default False
|
|
123
117
|
"""
|
|
124
118
|
if module is not None:
|
|
125
|
-
modules:
|
|
119
|
+
modules: list[str] = [module]
|
|
126
120
|
package_name = "dynamic"
|
|
127
121
|
plugin_name = getattr(module, "__name__", "dynamic_plugin")
|
|
128
122
|
elif isinstance(plugin, str):
|
|
@@ -152,7 +146,7 @@ def manifest_from_npe1(
|
|
|
152
146
|
else:
|
|
153
147
|
raise ValueError("one of plugin or module must be provided") # pragma: no cover
|
|
154
148
|
|
|
155
|
-
manifests:
|
|
149
|
+
manifests: list[PluginManifest] = []
|
|
156
150
|
for mod_name in modules:
|
|
157
151
|
logger.debug(
|
|
158
152
|
"Discovering contributions for npe1 plugin %r: module %r",
|
|
@@ -192,7 +186,7 @@ class HookImplParser:
|
|
|
192
186
|
"""
|
|
193
187
|
self.package = package
|
|
194
188
|
self.plugin_name = plugin_name
|
|
195
|
-
self.contributions:
|
|
189
|
+
self.contributions: defaultdict[str, list] = defaultdict(list)
|
|
196
190
|
self.adapter = adapter
|
|
197
191
|
|
|
198
192
|
def manifest(self) -> PluginManifest:
|
|
@@ -211,8 +205,8 @@ class HookImplParser:
|
|
|
211
205
|
)
|
|
212
206
|
|
|
213
207
|
def napari_experimental_provide_theme(self, impl: HookImplementation):
|
|
214
|
-
ThemeDict =
|
|
215
|
-
d:
|
|
208
|
+
ThemeDict = dict[str, str | tuple | list]
|
|
209
|
+
d: dict[str, ThemeDict] = impl.function()
|
|
216
210
|
for name, theme_dict in d.items():
|
|
217
211
|
colors = ThemeColors(**theme_dict)
|
|
218
212
|
clr = colors.background or colors.foreground
|
|
@@ -240,9 +234,9 @@ class HookImplParser:
|
|
|
240
234
|
def napari_provide_sample_data(self, impl: HookImplementation):
|
|
241
235
|
module = sys.modules[impl.function.__module__.split(".", 1)[0]]
|
|
242
236
|
|
|
243
|
-
samples:
|
|
237
|
+
samples: dict[str, dict | str | Callable] = impl.function()
|
|
244
238
|
for idx, (key, sample) in enumerate(samples.items()):
|
|
245
|
-
_sample:
|
|
239
|
+
_sample: str | Callable
|
|
246
240
|
if isinstance(sample, dict):
|
|
247
241
|
display_name = sample.get("display_name")
|
|
248
242
|
_sample = sample.get("data") # type: ignore
|
|
@@ -273,7 +267,7 @@ class HookImplParser:
|
|
|
273
267
|
self.contributions["sample_data"].append(s)
|
|
274
268
|
|
|
275
269
|
def napari_experimental_provide_function(self, impl: HookImplementation):
|
|
276
|
-
items:
|
|
270
|
+
items: Callable | list[Callable] = impl.function()
|
|
277
271
|
items = [items] if not isinstance(items, list) else items
|
|
278
272
|
|
|
279
273
|
for idx, item in enumerate(items):
|
|
@@ -303,8 +297,8 @@ class HookImplParser:
|
|
|
303
297
|
warnings.warn(msg, stacklevel=2)
|
|
304
298
|
|
|
305
299
|
def napari_experimental_provide_dock_widget(self, impl: HookImplementation):
|
|
306
|
-
WidgetCallable =
|
|
307
|
-
items:
|
|
300
|
+
WidgetCallable = Callable | tuple[Callable, dict]
|
|
301
|
+
items: WidgetCallable | list[WidgetCallable] = impl.function()
|
|
308
302
|
if not isinstance(items, list):
|
|
309
303
|
items = [items] # pragma: no cover
|
|
310
304
|
|
|
@@ -419,7 +413,7 @@ def _is_magicgui_magic_factory(obj):
|
|
|
419
413
|
|
|
420
414
|
|
|
421
415
|
def _python_name(
|
|
422
|
-
obj: Any, hook:
|
|
416
|
+
obj: Any, hook: Callable | None = None, hook_idx: int | None = None
|
|
423
417
|
) -> str:
|
|
424
418
|
"""Get resolvable python name for `obj` returned from an npe1 `hook` implentation.
|
|
425
419
|
|
|
@@ -446,8 +440,8 @@ def _python_name(
|
|
|
446
440
|
AttributeError
|
|
447
441
|
If a resolvable string cannot be found
|
|
448
442
|
"""
|
|
449
|
-
obj_name:
|
|
450
|
-
mod_name:
|
|
443
|
+
obj_name: str | None = None
|
|
444
|
+
mod_name: str | None = None
|
|
451
445
|
# first, check the global namespace of the module where the hook was declared
|
|
452
446
|
# if we find `obj` itself, we can just use it.
|
|
453
447
|
if hasattr(hook, "__module__"):
|
|
@@ -505,7 +499,7 @@ def _camel_to_spaces(val):
|
|
|
505
499
|
return _camel_to_spaces_pattern.sub(r" \1", val)
|
|
506
500
|
|
|
507
501
|
|
|
508
|
-
def get_top_module_path(package_name, top_module:
|
|
502
|
+
def get_top_module_path(package_name, top_module: str | None = None) -> Path:
|
|
509
503
|
dist = metadata.distribution(package_name)
|
|
510
504
|
if not top_module:
|
|
511
505
|
top_mods = (dist.read_text("top_level.txt") or "").strip().splitlines()
|
|
@@ -528,8 +522,8 @@ def get_top_module_path(package_name, top_module: Optional[str] = None) -> Path:
|
|
|
528
522
|
|
|
529
523
|
|
|
530
524
|
def convert_repository(
|
|
531
|
-
path:
|
|
532
|
-
) ->
|
|
525
|
+
path: Path | str, mf_name: str = "napari.yaml", dry_run=False
|
|
526
|
+
) -> tuple[PluginManifest, Path]:
|
|
533
527
|
"""Convert repository at `path` to new npe2 style."""
|
|
534
528
|
path = Path(path)
|
|
535
529
|
|
npe2/_inspection/_setuputils.py
CHANGED
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from functools import cached_property
|
|
5
5
|
from importlib.metadata import EntryPoint
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
NPE1_EP = "napari.plugin"
|
|
10
10
|
NPE2_EP = "napari.manifest"
|
|
@@ -12,23 +12,23 @@ NPE2_EP = "napari.manifest"
|
|
|
12
12
|
|
|
13
13
|
@dataclass
|
|
14
14
|
class PackageInfo:
|
|
15
|
-
src_root:
|
|
15
|
+
src_root: Path | None = None
|
|
16
16
|
package_name: str = ""
|
|
17
|
-
entry_points:
|
|
18
|
-
setup_cfg:
|
|
19
|
-
setup_py:
|
|
20
|
-
pyproject_toml:
|
|
17
|
+
entry_points: list[EntryPoint] = field(default_factory=list)
|
|
18
|
+
setup_cfg: Path | None = None
|
|
19
|
+
setup_py: Path | None = None
|
|
20
|
+
pyproject_toml: Path | None = None
|
|
21
21
|
|
|
22
22
|
# @property
|
|
23
23
|
# def packages(self) -> Optional[List[Path]]:
|
|
24
24
|
# return Path(self.top_module)
|
|
25
25
|
|
|
26
26
|
@cached_property
|
|
27
|
-
def _ep1(self) ->
|
|
27
|
+
def _ep1(self) -> EntryPoint | None:
|
|
28
28
|
return next((ep for ep in self.entry_points if ep.group == NPE1_EP), None)
|
|
29
29
|
|
|
30
30
|
@cached_property
|
|
31
|
-
def _ep2(self) ->
|
|
31
|
+
def _ep2(self) -> EntryPoint | None:
|
|
32
32
|
return next((ep for ep in self.entry_points if ep.group == NPE2_EP), None)
|
|
33
33
|
|
|
34
34
|
@property
|
|
@@ -48,7 +48,7 @@ class PackageInfo:
|
|
|
48
48
|
return "" # pragma: no cover
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def get_package_dir_info(path:
|
|
51
|
+
def get_package_dir_info(path: Path | str) -> PackageInfo:
|
|
52
52
|
"""Attempt to *statically* get plugin info from a package directory."""
|
|
53
53
|
path = Path(path).resolve()
|
|
54
54
|
if not path.is_dir(): # pragma: no cover
|
|
@@ -94,8 +94,8 @@ class _SetupVisitor(ast.NodeVisitor):
|
|
|
94
94
|
|
|
95
95
|
def __init__(self) -> None:
|
|
96
96
|
super().__init__()
|
|
97
|
-
self._names:
|
|
98
|
-
self._setup_kwargs:
|
|
97
|
+
self._names: dict[str, Any] = {}
|
|
98
|
+
self._setup_kwargs: dict[str, Any] = {}
|
|
99
99
|
|
|
100
100
|
def visit_Assign(self, node: ast.Assign) -> Any:
|
|
101
101
|
if len(node.targets) == 1:
|
|
@@ -110,7 +110,7 @@ class _SetupVisitor(ast.NodeVisitor):
|
|
|
110
110
|
value = self._get_val(k.value)
|
|
111
111
|
self._setup_kwargs[str(key)] = value
|
|
112
112
|
|
|
113
|
-
def _get_val(self, node:
|
|
113
|
+
def _get_val(self, node: ast.expr | None) -> Any:
|
|
114
114
|
if isinstance(node, ast.Constant):
|
|
115
115
|
return node.value
|
|
116
116
|
if isinstance(node, ast.Name):
|
|
@@ -120,12 +120,12 @@ class _SetupVisitor(ast.NodeVisitor):
|
|
|
120
120
|
if isinstance(node, ast.Dict):
|
|
121
121
|
keys = [self._get_val(k) for k in node.keys]
|
|
122
122
|
values = [self._get_val(k) for k in node.values]
|
|
123
|
-
return dict(zip(keys, values))
|
|
123
|
+
return dict(zip(keys, values, strict=True))
|
|
124
124
|
if isinstance(node, ast.List):
|
|
125
125
|
return [self._get_val(k) for k in node.elts]
|
|
126
126
|
if isinstance(node, ast.Tuple): # pragma: no cover
|
|
127
127
|
return tuple(self._get_val(k) for k in node.elts)
|
|
128
128
|
return str(node) # pragma: no cover
|
|
129
129
|
|
|
130
|
-
def get(self, key: str, default:
|
|
130
|
+
def get(self, key: str, default: Any | None = None) -> Any:
|
|
131
131
|
return self._setup_kwargs.get(key, default)
|