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/_inspection/_visitors.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import inspect
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from collections import defaultdict
|
|
4
5
|
from importlib.metadata import Distribution
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from types import ModuleType
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
8
9
|
|
|
9
10
|
from npe2.manifest import contributions
|
|
10
11
|
|
|
@@ -12,7 +13,7 @@ if TYPE_CHECKING:
|
|
|
12
13
|
from pydantic import BaseModel
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
CONTRIB_MAP:
|
|
16
|
+
CONTRIB_MAP: dict[str, tuple[type["BaseModel"], str]] = {
|
|
16
17
|
"writer": (contributions.WriterContribution, "writers"),
|
|
17
18
|
"reader": (contributions.ReaderContribution, "readers"),
|
|
18
19
|
"sample_data_generator": (contributions.SampleDataGenerator, "sample_data"),
|
|
@@ -43,7 +44,7 @@ class _DecoratorVisitor(ast.NodeVisitor, ABC):
|
|
|
43
44
|
def __init__(self, module_name: str, match: str) -> None:
|
|
44
45
|
self.module_name = module_name
|
|
45
46
|
self._match = match
|
|
46
|
-
self._names:
|
|
47
|
+
self._names: dict[str, str] = {}
|
|
47
48
|
|
|
48
49
|
def visit_Import(self, node: ast.Import) -> Any:
|
|
49
50
|
# https://docs.python.org/3/library/ast.html#ast.Import
|
|
@@ -68,7 +69,7 @@ class _DecoratorVisitor(ast.NodeVisitor, ABC):
|
|
|
68
69
|
def visit_ClassDef(self, node: ast.ClassDef) -> Any:
|
|
69
70
|
self._find_decorators(node)
|
|
70
71
|
|
|
71
|
-
def _find_decorators(self, node:
|
|
72
|
+
def _find_decorators(self, node: ast.ClassDef | ast.FunctionDef):
|
|
72
73
|
# for each in the decorator list ...
|
|
73
74
|
for call in node.decorator_list:
|
|
74
75
|
# https://docs.python.org/3/library/ast.html#ast.Call
|
|
@@ -116,14 +117,14 @@ class _DecoratorVisitor(ast.NodeVisitor, ABC):
|
|
|
116
117
|
self._process_decorated(call.func.id, node, kwargs)
|
|
117
118
|
return super().generic_visit(node)
|
|
118
119
|
|
|
119
|
-
def _keywords_to_kwargs(self, keywords:
|
|
120
|
+
def _keywords_to_kwargs(self, keywords: list[ast.keyword]) -> dict[str, Any]:
|
|
120
121
|
return {str(k.arg): ast.literal_eval(k.value) for k in keywords}
|
|
121
122
|
|
|
122
123
|
@abstractmethod
|
|
123
124
|
def _process_decorated(
|
|
124
125
|
self,
|
|
125
126
|
decorator_name: str,
|
|
126
|
-
node:
|
|
127
|
+
node: ast.ClassDef | ast.FunctionDef,
|
|
127
128
|
decorator_kwargs: dict,
|
|
128
129
|
):
|
|
129
130
|
"""Process a decorated function.
|
|
@@ -159,37 +160,37 @@ class NPE2PluginModuleVisitor(_DecoratorVisitor):
|
|
|
159
160
|
) -> None:
|
|
160
161
|
super().__init__(module_name, match)
|
|
161
162
|
self.plugin_name = plugin_name
|
|
162
|
-
self.contribution_points:
|
|
163
|
+
self.contribution_points: dict[str, list[dict]] = {}
|
|
163
164
|
|
|
164
165
|
def _process_decorated(
|
|
165
166
|
self,
|
|
166
167
|
decorator_name: str,
|
|
167
|
-
node:
|
|
168
|
+
node: ast.ClassDef | ast.FunctionDef,
|
|
168
169
|
decorator_kwargs: dict,
|
|
169
170
|
):
|
|
170
171
|
self._store_contrib(decorator_name, node.name, decorator_kwargs)
|
|
171
172
|
|
|
172
|
-
def _store_contrib(self, contrib_type: str, name: str, kwargs:
|
|
173
|
+
def _store_contrib(self, contrib_type: str, name: str, kwargs: dict[str, Any]):
|
|
173
174
|
from npe2.implements import CHECK_ARGS_PARAM # circ import
|
|
174
175
|
|
|
175
176
|
kwargs.pop(CHECK_ARGS_PARAM, None)
|
|
176
177
|
ContribClass, contrib_name = CONTRIB_MAP[contrib_type]
|
|
177
178
|
contrib = ContribClass(**self._store_command(name, kwargs))
|
|
178
|
-
existing:
|
|
179
|
-
existing.append(contrib.
|
|
179
|
+
existing: list[dict] = self.contribution_points.setdefault(contrib_name, [])
|
|
180
|
+
existing.append(contrib.model_dump(exclude_unset=True))
|
|
180
181
|
|
|
181
|
-
def _store_command(self, name: str, kwargs:
|
|
182
|
+
def _store_command(self, name: str, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
182
183
|
cmd_params = inspect.signature(contributions.CommandContribution).parameters
|
|
183
184
|
|
|
184
185
|
cmd_kwargs = {k: kwargs.pop(k) for k in list(kwargs) if k in cmd_params}
|
|
185
186
|
cmd_kwargs["python_name"] = self._qualified_pyname(name)
|
|
186
187
|
cmd = contributions.CommandContribution(**cmd_kwargs)
|
|
187
188
|
if cmd.id.startswith(self.plugin_name):
|
|
188
|
-
n = len(self.plugin_name)
|
|
189
|
+
n = len(self.plugin_name) + 1
|
|
189
190
|
cmd.id = cmd.id[n:]
|
|
190
191
|
cmd.id = f"{self.plugin_name}.{cmd.id.lstrip('.')}"
|
|
191
|
-
cmd_contribs:
|
|
192
|
-
cmd_contribs.append(cmd.
|
|
192
|
+
cmd_contribs: list[dict] = self.contribution_points.setdefault("commands", [])
|
|
193
|
+
cmd_contribs.append(cmd.model_dump(exclude_unset=True))
|
|
193
194
|
kwargs["command"] = cmd.id
|
|
194
195
|
return kwargs
|
|
195
196
|
|
|
@@ -203,13 +204,13 @@ class NPE1PluginModuleVisitor(_DecoratorVisitor):
|
|
|
203
204
|
def __init__(self, plugin_name: str, module_name: str) -> None:
|
|
204
205
|
super().__init__(module_name, "napari_plugin_engine.napari_hook_implementation")
|
|
205
206
|
self.plugin_name = plugin_name
|
|
206
|
-
self.contribution_points:
|
|
207
|
+
self.contribution_points: defaultdict[str, list] = defaultdict(list)
|
|
207
208
|
|
|
208
209
|
def _process_decorated(
|
|
209
210
|
self,
|
|
210
211
|
decorator_name: str,
|
|
211
|
-
node:
|
|
212
|
-
decorator_kwargs:
|
|
212
|
+
node: ast.ClassDef | ast.FunctionDef,
|
|
213
|
+
decorator_kwargs: dict[str, Any],
|
|
213
214
|
):
|
|
214
215
|
self.generic_visit(node) # do this to process any imports in the function
|
|
215
216
|
hookname = decorator_kwargs.get("specname", node.name)
|
|
@@ -270,7 +271,7 @@ class NPE1PluginModuleVisitor(_DecoratorVisitor):
|
|
|
270
271
|
)
|
|
271
272
|
|
|
272
273
|
contrib: contributions.SampleDataContribution
|
|
273
|
-
for key, val in zip(return_.value.keys, return_.value.values):
|
|
274
|
+
for key, val in zip(return_.value.keys, return_.value.values, strict=True):
|
|
274
275
|
if isinstance(val, ast.Dict):
|
|
275
276
|
raise NotImplementedError("npe1 sample dicts-of-dicts not supported")
|
|
276
277
|
|
|
@@ -372,7 +373,7 @@ class NPE1PluginModuleVisitor(_DecoratorVisitor):
|
|
|
372
373
|
|
|
373
374
|
|
|
374
375
|
def find_npe2_module_contributions(
|
|
375
|
-
path:
|
|
376
|
+
path: ModuleType | str | Path, plugin_name: str, module_name: str = ""
|
|
376
377
|
) -> contributions.ContributionPoints:
|
|
377
378
|
"""Visit an npe2 module and extract contribution points.
|
|
378
379
|
|
|
@@ -401,7 +402,11 @@ def find_npe2_module_contributions(
|
|
|
401
402
|
if "commands" in visitor.contribution_points:
|
|
402
403
|
compress = {tuple(i.items()) for i in visitor.contribution_points["commands"]}
|
|
403
404
|
visitor.contribution_points["commands"] = [dict(i) for i in compress]
|
|
404
|
-
|
|
405
|
+
res = contributions.ContributionPoints(**visitor.contribution_points)
|
|
406
|
+
for name in visitor.contribution_points:
|
|
407
|
+
for command in getattr(res, name):
|
|
408
|
+
command._plugin_name = plugin_name
|
|
409
|
+
return res
|
|
405
410
|
|
|
406
411
|
|
|
407
412
|
def find_npe1_module_contributions(
|
npe2/_plugin_manager.py
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import builtins
|
|
3
4
|
import contextlib
|
|
4
5
|
import os
|
|
5
6
|
import warnings
|
|
6
7
|
from collections import Counter, defaultdict
|
|
8
|
+
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence, Set
|
|
7
9
|
from fnmatch import fnmatch
|
|
8
10
|
from importlib import metadata
|
|
9
11
|
from logging import getLogger
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
from typing import (
|
|
12
14
|
TYPE_CHECKING,
|
|
13
|
-
AbstractSet,
|
|
14
15
|
Any,
|
|
15
|
-
Callable,
|
|
16
|
-
DefaultDict,
|
|
17
|
-
Dict,
|
|
18
|
-
Iterable,
|
|
19
|
-
Iterator,
|
|
20
|
-
List,
|
|
21
|
-
Mapping,
|
|
22
|
-
Optional,
|
|
23
|
-
Sequence,
|
|
24
|
-
Set,
|
|
25
|
-
Tuple,
|
|
26
|
-
Union,
|
|
27
16
|
)
|
|
28
17
|
from urllib import parse
|
|
29
18
|
|
|
@@ -47,11 +36,11 @@ if TYPE_CHECKING:
|
|
|
47
36
|
WidgetContribution,
|
|
48
37
|
)
|
|
49
38
|
|
|
50
|
-
IntStr =
|
|
51
|
-
|
|
52
|
-
DictIntStrAny =
|
|
39
|
+
IntStr = int | str
|
|
40
|
+
SetIntStr = Set[IntStr]
|
|
41
|
+
DictIntStrAny = dict[IntStr, Any]
|
|
53
42
|
MappingIntStrAny = Mapping[IntStr, Any]
|
|
54
|
-
InclusionSet =
|
|
43
|
+
InclusionSet = SetIntStr | MappingIntStrAny | None
|
|
55
44
|
DisposeFunction = Callable[[], None]
|
|
56
45
|
|
|
57
46
|
logger = getLogger(__name__)
|
|
@@ -62,13 +51,13 @@ PluginName = str # this is `PluginManifest.name`
|
|
|
62
51
|
|
|
63
52
|
class _ContributionsIndex:
|
|
64
53
|
def __init__(self) -> None:
|
|
65
|
-
self._indexed:
|
|
66
|
-
self._commands:
|
|
67
|
-
self._readers:
|
|
68
|
-
self._writers:
|
|
54
|
+
self._indexed: set[str] = set()
|
|
55
|
+
self._commands: dict[str, tuple[CommandContribution, PluginName]] = {}
|
|
56
|
+
self._readers: list[tuple[str, ReaderContribution]] = []
|
|
57
|
+
self._writers: list[tuple[LayerType, int, int, WriterContribution]] = []
|
|
69
58
|
|
|
70
59
|
# DEPRECATED: only here for napari <= 0.4.15 compat.
|
|
71
|
-
self._samples:
|
|
60
|
+
self._samples: defaultdict[str, list[SampleDataContribution]] = defaultdict(
|
|
72
61
|
list
|
|
73
62
|
)
|
|
74
63
|
|
|
@@ -126,7 +115,7 @@ class _ContributionsIndex:
|
|
|
126
115
|
def get_command(self, command_id: str) -> CommandContribution:
|
|
127
116
|
return self._commands[command_id][0]
|
|
128
117
|
|
|
129
|
-
def iter_compatible_readers(self, paths:
|
|
118
|
+
def iter_compatible_readers(self, paths: list[str]) -> Iterator[ReaderContribution]:
|
|
130
119
|
assert isinstance(paths, list)
|
|
131
120
|
if not paths:
|
|
132
121
|
return # pragma: no cover
|
|
@@ -170,7 +159,7 @@ class _ContributionsIndex:
|
|
|
170
159
|
# this to get candidate writers compatible with the requested count.
|
|
171
160
|
counts = Counter(layer_types)
|
|
172
161
|
|
|
173
|
-
def _get_candidates(lt: LayerType) ->
|
|
162
|
+
def _get_candidates(lt: LayerType) -> set[WriterContribution]:
|
|
174
163
|
return {
|
|
175
164
|
w
|
|
176
165
|
for layer, min_, max_, w in self._writers
|
|
@@ -185,7 +174,7 @@ class _ContributionsIndex:
|
|
|
185
174
|
else:
|
|
186
175
|
break
|
|
187
176
|
|
|
188
|
-
def _writer_key(writer: WriterContribution) ->
|
|
177
|
+
def _writer_key(writer: WriterContribution) -> tuple[bool, int]:
|
|
189
178
|
# 1. writers with no file extensions (like directory writers) go last
|
|
190
179
|
no_ext = len(writer.filename_extensions) == 0
|
|
191
180
|
|
|
@@ -222,21 +211,21 @@ class PluginManagerEvents(SignalGroup):
|
|
|
222
211
|
|
|
223
212
|
|
|
224
213
|
class PluginManager:
|
|
225
|
-
__instance:
|
|
214
|
+
__instance: PluginManager | None = None # a global instance
|
|
226
215
|
_contrib: _ContributionsIndex
|
|
227
216
|
events: PluginManagerEvents
|
|
228
217
|
|
|
229
218
|
def __init__(
|
|
230
|
-
self, *, disable: Iterable[str] = (), reg:
|
|
219
|
+
self, *, disable: Iterable[str] = (), reg: CommandRegistry | None = None
|
|
231
220
|
) -> None:
|
|
232
|
-
self._disabled_plugins:
|
|
221
|
+
self._disabled_plugins: set[PluginName] = set(disable)
|
|
233
222
|
self._command_registry = reg or CommandRegistry()
|
|
234
|
-
self._contexts:
|
|
223
|
+
self._contexts: dict[PluginName, PluginContext] = {}
|
|
235
224
|
self._contrib = _ContributionsIndex()
|
|
236
|
-
self._manifests:
|
|
225
|
+
self._manifests: dict[PluginName, PluginManifest] = {}
|
|
237
226
|
self.events = PluginManagerEvents(self)
|
|
238
|
-
self._npe1_adapters:
|
|
239
|
-
self._command_menu_map:
|
|
227
|
+
self._npe1_adapters: list[NPE1Adapter] = []
|
|
228
|
+
self._command_menu_map: dict[str, dict[str, dict[str, list[MenuCommand]]]] = (
|
|
240
229
|
# for each manifest, maps command IDs to menu IDs to list of MenuCommands
|
|
241
230
|
# belonging to that menu
|
|
242
231
|
# i.e. manifest -> command -> menu_id -> list[MenuCommand]
|
|
@@ -323,13 +312,13 @@ class PluginManager:
|
|
|
323
312
|
self._contrib.index_contributions(self._npe1_adapters.pop())
|
|
324
313
|
|
|
325
314
|
def register(
|
|
326
|
-
self, manifest_or_package:
|
|
315
|
+
self, manifest_or_package: PluginManifest | str, warn_disabled=True
|
|
327
316
|
) -> None:
|
|
328
317
|
"""Register a plugin manifest, path to manifest file, or a package name.
|
|
329
318
|
|
|
330
319
|
Parameters
|
|
331
320
|
----------
|
|
332
|
-
manifest_or_package :
|
|
321
|
+
manifest_or_package : PluginManifest | str
|
|
333
322
|
Either a PluginManifest instance or a string. If a string, should be either
|
|
334
323
|
the name of a plugin package, or a path to a plugin manifest file.
|
|
335
324
|
warn_disabled : bool, optional
|
|
@@ -507,9 +496,7 @@ class PluginManager:
|
|
|
507
496
|
raise KeyError(msg)
|
|
508
497
|
return self._manifests[key]
|
|
509
498
|
|
|
510
|
-
def iter_manifests(
|
|
511
|
-
self, disabled: Optional[bool] = None
|
|
512
|
-
) -> Iterator[PluginManifest]:
|
|
499
|
+
def iter_manifests(self, disabled: bool | None = None) -> Iterator[PluginManifest]:
|
|
513
500
|
"""Iterate through registered manifests.
|
|
514
501
|
|
|
515
502
|
Parameters
|
|
@@ -532,12 +519,12 @@ class PluginManager:
|
|
|
532
519
|
def dict(
|
|
533
520
|
self,
|
|
534
521
|
*,
|
|
535
|
-
include:
|
|
536
|
-
exclude:
|
|
537
|
-
) ->
|
|
522
|
+
include: InclusionSet | None = None,
|
|
523
|
+
exclude: InclusionSet | None = None,
|
|
524
|
+
) -> builtins.dict[str, Any]:
|
|
538
525
|
"""Return a dictionary with the state of the plugin manager.
|
|
539
526
|
|
|
540
|
-
`include` and `exclude` will be passed to each `PluginManifest.
|
|
527
|
+
`include` and `exclude` will be passed to each `PluginManifest.model_dump()`
|
|
541
528
|
See pydantic documentation for details:
|
|
542
529
|
https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
|
|
543
530
|
|
|
@@ -568,15 +555,16 @@ class PluginManager:
|
|
|
568
555
|
Dict[str, Any]
|
|
569
556
|
Dictionary with the state of the plugin manager. Keys will include
|
|
570
557
|
|
|
571
|
-
- `'plugins'`: dict of `{name: manifest.
|
|
558
|
+
- `'plugins'`: dict of `{name: manifest.model_dump()} for
|
|
559
|
+
discovered plugins
|
|
572
560
|
- `'disabled'`: set of disabled plugins
|
|
573
561
|
- `'activated'`: set of activated plugins
|
|
574
562
|
|
|
575
563
|
"""
|
|
576
564
|
# _include =
|
|
577
|
-
out:
|
|
565
|
+
out: dict[str, Any] = {
|
|
578
566
|
"plugins": {
|
|
579
|
-
mf.name: mf.
|
|
567
|
+
mf.name: mf.model_dump(
|
|
580
568
|
include=_expand_dotted_set(include),
|
|
581
569
|
exclude=_expand_dotted_set(exclude),
|
|
582
570
|
)
|
|
@@ -619,9 +607,9 @@ class PluginManager:
|
|
|
619
607
|
for mf in self.iter_manifests(disabled=disabled):
|
|
620
608
|
yield from mf.contributions.menus.get(menu_key, ())
|
|
621
609
|
|
|
622
|
-
def menus(self, disabled=False) ->
|
|
610
|
+
def menus(self, disabled=False) -> builtins.dict[str, list[MenuItem]]:
|
|
623
611
|
"""Return all registered menu_key -> List[MenuItems]."""
|
|
624
|
-
_menus:
|
|
612
|
+
_menus: defaultdict[str, list[MenuItem]] = defaultdict(list)
|
|
625
613
|
for mf in self.iter_manifests(disabled=disabled):
|
|
626
614
|
for key, menus in mf.contributions.menus.items():
|
|
627
615
|
_menus[key].extend(menus)
|
|
@@ -633,13 +621,13 @@ class PluginManager:
|
|
|
633
621
|
yield from mf.contributions.themes or ()
|
|
634
622
|
|
|
635
623
|
def iter_compatible_readers(
|
|
636
|
-
self, path:
|
|
624
|
+
self, path: PathLike | Sequence[str]
|
|
637
625
|
) -> Iterator[ReaderContribution]:
|
|
638
626
|
"""Iterate over ReaderContributions compatible with `path`.
|
|
639
627
|
|
|
640
628
|
Parameters
|
|
641
629
|
----------
|
|
642
|
-
path :
|
|
630
|
+
path : PathLike | Sequence[str]
|
|
643
631
|
Pathlike or list of pathlikes, with file(s) to read.
|
|
644
632
|
"""
|
|
645
633
|
if isinstance(path, (str, Path)):
|
|
@@ -666,15 +654,15 @@ class PluginManager:
|
|
|
666
654
|
|
|
667
655
|
def iter_sample_data(
|
|
668
656
|
self,
|
|
669
|
-
) -> Iterator[
|
|
657
|
+
) -> Iterator[tuple[PluginName, list[SampleDataContribution]]]:
|
|
670
658
|
"""Iterates over (plugin_name, [sample_contribs])."""
|
|
671
659
|
for mf in self.iter_manifests(disabled=False):
|
|
672
660
|
if mf.contributions.sample_data:
|
|
673
661
|
yield mf.name, mf.contributions.sample_data
|
|
674
662
|
|
|
675
663
|
def get_writer(
|
|
676
|
-
self, path: str, layer_types: Sequence[str], plugin_name:
|
|
677
|
-
) ->
|
|
664
|
+
self, path: str, layer_types: Sequence[str], plugin_name: str | None = None
|
|
665
|
+
) -> tuple[WriterContribution | None, str]:
|
|
678
666
|
"""Get Writer contribution appropriate for `path`, and `layer_types`.
|
|
679
667
|
|
|
680
668
|
When `path` has a file extension, find a compatible writer that has
|
|
@@ -715,7 +703,7 @@ class PluginManager:
|
|
|
715
703
|
# Nothing got found
|
|
716
704
|
return None, path
|
|
717
705
|
|
|
718
|
-
def get_shimmed_plugins(self) ->
|
|
706
|
+
def get_shimmed_plugins(self) -> list[str]:
|
|
719
707
|
"""Return a list of all shimmed plugin names."""
|
|
720
708
|
return [mf.name for mf in self.iter_manifests() if mf.npe1_shim]
|
|
721
709
|
|
|
@@ -726,14 +714,14 @@ class PluginContext:
|
|
|
726
714
|
# stores all created contexts (currently cleared by `PluginManager.deactivate`)
|
|
727
715
|
|
|
728
716
|
def __init__(
|
|
729
|
-
self, plugin_key: PluginName, reg:
|
|
717
|
+
self, plugin_key: PluginName, reg: CommandRegistry | None = None
|
|
730
718
|
) -> None:
|
|
731
719
|
self._activated = False
|
|
732
720
|
self.plugin_key = plugin_key
|
|
733
721
|
self._command_registry = reg or PluginManager.instance().commands
|
|
734
|
-
self._imports:
|
|
722
|
+
self._imports: set[str] = set() # modules that were imported by this plugin
|
|
735
723
|
# functions to call when deactivating
|
|
736
|
-
self._disposables:
|
|
724
|
+
self._disposables: set[DisposeFunction] = set()
|
|
737
725
|
|
|
738
726
|
def _dispose(self):
|
|
739
727
|
while self._disposables:
|
|
@@ -742,7 +730,7 @@ class PluginContext:
|
|
|
742
730
|
except Exception as e:
|
|
743
731
|
logger.warning(f"Error while disposing {self.plugin_key}; {e}")
|
|
744
732
|
|
|
745
|
-
def register_command(self, id: str, command:
|
|
733
|
+
def register_command(self, id: str, command: Callable | None = None):
|
|
746
734
|
"""Associate a callable with a command id."""
|
|
747
735
|
|
|
748
736
|
def _inner(command):
|
|
@@ -787,7 +775,7 @@ def _expand_dotted_set(inclusion_set: InclusionSet) -> InclusionSet:
|
|
|
787
775
|
):
|
|
788
776
|
return inclusion_set
|
|
789
777
|
|
|
790
|
-
result:
|
|
778
|
+
result: dict[IntStr, Any] = {}
|
|
791
779
|
# sort the strings based on the number of dots,
|
|
792
780
|
# so that higher level keys take precedence
|
|
793
781
|
# e.g. {'a.b', 'a.d.e', 'a'} -> {'a'}
|
npe2/_pydantic_util.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from types import NoneType, UnionType
|
|
2
|
+
from typing import Annotated, Dict, List, Union, get_args, get_origin # noqa
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def iter_inner_types(type_):
|
|
6
|
+
origin = get_origin(type_)
|
|
7
|
+
args = get_args(type_)
|
|
8
|
+
if origin in (list, List): # noqa
|
|
9
|
+
yield from iter_inner_types(args[0])
|
|
10
|
+
elif origin in (dict, Dict): # noqa
|
|
11
|
+
yield from iter_inner_types(args[1])
|
|
12
|
+
elif origin is Annotated:
|
|
13
|
+
yield from iter_inner_types(args[0])
|
|
14
|
+
elif origin in (UnionType, Union):
|
|
15
|
+
for arg in args:
|
|
16
|
+
yield from iter_inner_types(arg)
|
|
17
|
+
elif type_ is not NoneType:
|
|
18
|
+
yield type_
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_inner_type(type_):
|
|
22
|
+
"""Roughly replacing pydantic.v1 Field.type_"""
|
|
23
|
+
return Union[tuple(iter_inner_types(type_))] # noqa
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_outer_type(type_):
|
|
27
|
+
"""Roughly replacing pydantic.v1 Field.outer_type_"""
|
|
28
|
+
origin = get_origin(type_)
|
|
29
|
+
args = get_args(type_)
|
|
30
|
+
if origin in (UnionType, Union):
|
|
31
|
+
# filter args to remove optional None
|
|
32
|
+
args = tuple(filter(lambda t: t is not NoneType, get_args(type_)))
|
|
33
|
+
if len(args) == 1:
|
|
34
|
+
# it was just optional, pretend there was no None
|
|
35
|
+
return get_outer_type(args[0])
|
|
36
|
+
# It's an actual union of types, so there's no "outer type"
|
|
37
|
+
return None
|
|
38
|
+
if origin is not None:
|
|
39
|
+
return origin
|
|
40
|
+
return type_
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_list_type(type_):
|
|
44
|
+
"""Roughly replacing pydantic.v1 comparison to SHAPE_LIST"""
|
|
45
|
+
return get_outer_type(type_) is list
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__all__ = (
|
|
49
|
+
"get_inner_type",
|
|
50
|
+
"get_outer_type",
|
|
51
|
+
"is_list_type",
|
|
52
|
+
"iter_inner_types",
|
|
53
|
+
)
|
npe2/_pytest_plugin.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import warnings
|
|
3
|
-
from typing import Optional, Union
|
|
4
3
|
from unittest.mock import patch
|
|
5
4
|
|
|
6
5
|
import pytest
|
|
@@ -23,9 +22,9 @@ class TestPluginManager(PluginManager):
|
|
|
23
22
|
|
|
24
23
|
def tmp_plugin(
|
|
25
24
|
self,
|
|
26
|
-
manifest:
|
|
27
|
-
package:
|
|
28
|
-
name:
|
|
25
|
+
manifest: PluginManifest | str | None = None,
|
|
26
|
+
package: str | None = None,
|
|
27
|
+
name: str | None = None,
|
|
29
28
|
) -> DynamicPlugin:
|
|
30
29
|
"""Create a DynamicPlugin instance using this plugin manager.
|
|
31
30
|
|
npe2/_setuptools_plugin.py
CHANGED
|
@@ -13,16 +13,16 @@ import os
|
|
|
13
13
|
import re
|
|
14
14
|
import sys
|
|
15
15
|
import warnings
|
|
16
|
-
from typing import TYPE_CHECKING,
|
|
16
|
+
from typing import TYPE_CHECKING, cast
|
|
17
17
|
|
|
18
18
|
from setuptools import Distribution
|
|
19
19
|
from setuptools.command.build_py import build_py
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from distutils.cmd import Command
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import Any
|
|
24
24
|
|
|
25
|
-
PathT =
|
|
25
|
+
PathT = "os.PathLike[str]" | str
|
|
26
26
|
|
|
27
27
|
NPE2_ENTRY = "napari.manifest"
|
|
28
28
|
DEBUG = bool(os.environ.get("SETUPTOOLS_NPE2_DEBUG"))
|
|
@@ -54,7 +54,7 @@ def _read_dist_name_from_setup_cfg() -> str | None:
|
|
|
54
54
|
return parser.get("metadata", "name", fallback=None)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def _check_absolute_root(root: PathT, relative_to: PathT | None) -> str:
|
|
57
|
+
def _check_absolute_root(root: PathT, relative_to: PathT | None) -> str: # type: ignore
|
|
58
58
|
trace("abs root", repr(locals()))
|
|
59
59
|
if relative_to:
|
|
60
60
|
if (
|
|
@@ -85,9 +85,9 @@ class Configuration:
|
|
|
85
85
|
|
|
86
86
|
def __init__(
|
|
87
87
|
self,
|
|
88
|
-
relative_to: PathT | None = None,
|
|
89
|
-
root: PathT = ".",
|
|
90
|
-
write_to: PathT | None = None,
|
|
88
|
+
relative_to: PathT | None = None, # type: ignore
|
|
89
|
+
root: PathT = ".", # type: ignore
|
|
90
|
+
write_to: PathT | None = None, # type: ignore
|
|
91
91
|
write_to_template: str | None = None,
|
|
92
92
|
dist_name: str | None = None,
|
|
93
93
|
template: str | None = None,
|
|
@@ -109,7 +109,7 @@ class Configuration:
|
|
|
109
109
|
return self._root
|
|
110
110
|
|
|
111
111
|
@root.setter
|
|
112
|
-
def root(self, value: PathT) -> None:
|
|
112
|
+
def root(self, value: PathT) -> None: # type: ignore
|
|
113
113
|
self._absolute_root = _check_absolute_root(value, self._relative_to)
|
|
114
114
|
self._root = os.fspath(value)
|
|
115
115
|
trace("root", repr(self._absolute_root))
|
|
@@ -152,7 +152,7 @@ class Configuration:
|
|
|
152
152
|
return cls(dist_name=dist_name, **section, **kwargs)
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
def _mf_entry_from_dist(dist: Distribution) ->
|
|
155
|
+
def _mf_entry_from_dist(dist: Distribution) -> tuple[str, str] | None:
|
|
156
156
|
"""Return (module, attr) for a distribution's npe2 entry point."""
|
|
157
157
|
eps: dict = getattr(dist, "entry_points", {})
|
|
158
158
|
if napari_entrys := eps.get(NPE2_ENTRY, []):
|