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.
Files changed (42) hide show
  1. npe2/_command_registry.py +6 -5
  2. npe2/_dynamic_plugin.py +25 -27
  3. npe2/_inspection/_compile.py +9 -8
  4. npe2/_inspection/_fetch.py +18 -30
  5. npe2/_inspection/_from_npe1.py +26 -32
  6. npe2/_inspection/_setuputils.py +14 -14
  7. npe2/_inspection/_visitors.py +26 -21
  8. npe2/_plugin_manager.py +45 -57
  9. npe2/_pydantic_util.py +53 -0
  10. npe2/_pytest_plugin.py +3 -4
  11. npe2/_setuptools_plugin.py +9 -9
  12. npe2/cli.py +25 -21
  13. npe2/implements.py +13 -10
  14. npe2/implements.pyi +3 -2
  15. npe2/io_utils.py +40 -44
  16. npe2/manifest/_bases.py +15 -14
  17. npe2/manifest/_npe1_adapter.py +3 -3
  18. npe2/manifest/_package_metadata.py +40 -47
  19. npe2/manifest/contributions/_commands.py +16 -14
  20. npe2/manifest/contributions/_configuration.py +22 -20
  21. npe2/manifest/contributions/_contributions.py +13 -14
  22. npe2/manifest/contributions/_icon.py +3 -5
  23. npe2/manifest/contributions/_json_schema.py +86 -89
  24. npe2/manifest/contributions/_keybindings.py +5 -6
  25. npe2/manifest/contributions/_menus.py +11 -9
  26. npe2/manifest/contributions/_readers.py +10 -8
  27. npe2/manifest/contributions/_sample_data.py +16 -15
  28. npe2/manifest/contributions/_submenu.py +2 -4
  29. npe2/manifest/contributions/_themes.py +18 -22
  30. npe2/manifest/contributions/_widgets.py +6 -5
  31. npe2/manifest/contributions/_writers.py +22 -18
  32. npe2/manifest/schema.py +82 -70
  33. npe2/manifest/utils.py +24 -28
  34. npe2/plugin_manager.py +17 -14
  35. npe2/types.py +16 -19
  36. {npe2-0.7.9rc0.dist-info → npe2-0.8.0rc0.dist-info}/METADATA +13 -7
  37. npe2-0.8.0rc0.dist-info/RECORD +49 -0
  38. {npe2-0.7.9rc0.dist-info → npe2-0.8.0rc0.dist-info}/WHEEL +1 -1
  39. npe2/_pydantic_compat.py +0 -54
  40. npe2-0.7.9rc0.dist-info/RECORD +0 -49
  41. {npe2-0.7.9rc0.dist-info → npe2-0.8.0rc0.dist-info}/entry_points.txt +0 -0
  42. {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, Callable, Dict, Optional, Union
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: Optional[Callable] = None
23
- python_name: Optional[PythonName] = None
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: Dict[str, CommandHandler] = {}
54
+ self._commands: dict[str, CommandHandler] = {}
54
55
 
55
- def register(self, id: str, command: Union[Callable, str]) -> PDisposable:
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 npe2._pydantic_compat import BaseModel, ValidationError
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
- CONTRIB_NAMES = {v.type_: k for k, v in ContributionPoints.__fields__.items()}
37
- for key in list(CONTRIB_NAMES):
38
- if getattr(key, "__origin__", "") == Union:
39
- v = CONTRIB_NAMES.pop(key)
40
- for t in key.__args__:
41
- CONTRIB_NAMES[t] = v
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: Optional[PluginManager] = None,
69
- manifest: Optional[PluginManifest] = None,
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: Optional[PluginManager]) -> None:
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: Dict[str, Callable] = {
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: Optional[str] = None,
137
- plugin_manager: Optional[PluginManager] = None,
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: Type[C]) -> None:
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: Optional[Literal[None]] = None, **kwargs
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.__fields__
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) -> List[C]:
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) -> List[CommandContribution]:
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 = []
@@ -1,5 +1,6 @@
1
+ from collections.abc import Iterator, Sequence
1
2
  from pathlib import Path
2
- from typing import Iterator, List, Sequence, Tuple, Union, cast
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: Union[str, Path] = ".") -> List[Path]:
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: Union[str, Path] = ".") -> str:
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: Union[str, Path],
22
- dest: Union[str, Path, None] = None,
22
+ src_dir: str | Path,
23
+ dest: str | Path | None = None,
23
24
  packages: Sequence[str] = (),
24
25
  plugin_name: str = "",
25
- template: Union[str, Path, None] = None,
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: List[contributions.ContributionPoints] = []
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[Tuple[Path, str]]:
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
@@ -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 contextlib import contextmanager
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: Union[str, Path]) -> Path:
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: Union[str, Path]) -> PluginManifest:
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) -> Optional[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: Optional[str] = None
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: Optional[str] = None, packagetype: Optional[str] = None
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: List[dict] = data["releases"][version]
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
- tar.extractall(td)
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: Optional[str] = None
400
- ) -> ContextManager[Path]:
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: Optional[str] = None
408
- ) -> ContextManager[Path]:
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)
@@ -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: Optional[ModuleType] = None,
52
- plugin_name: Optional[str] = None,
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: Optional[str] = None
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() -> List[PackageInfo]:
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: List[PackageInfo] = []
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: Union[str, metadata.Distribution, None] = None,
103
- module: Optional[Any] = None,
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 : Union[str, metadata.Distribution, None]
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: List[str] = [module]
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: List[PluginManifest] = []
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: DefaultDict[str, list] = DefaultDict(list)
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 = Dict[str, Union[str, Tuple, List]]
215
- d: Dict[str, ThemeDict] = impl.function()
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: Dict[str, Union[dict, str, Callable]] = impl.function()
237
+ samples: dict[str, dict | str | Callable] = impl.function()
244
238
  for idx, (key, sample) in enumerate(samples.items()):
245
- _sample: Union[str, Callable]
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: Union[Callable, List[Callable]] = impl.function()
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 = Union[Callable, Tuple[Callable, dict]]
307
- items: Union[WidgetCallable, List[WidgetCallable]] = impl.function()
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: Optional[Callable] = None, hook_idx: Optional[int] = None
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: Optional[str] = None
450
- mod_name: Optional[str] = None
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: Optional[str] = None) -> Path:
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: Union[Path, str], mf_name: str = "napari.yaml", dry_run=False
532
- ) -> Tuple[PluginManifest, Path]:
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
 
@@ -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, Dict, List, Optional, Union
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: Optional[Path] = None
15
+ src_root: Path | None = None
16
16
  package_name: str = ""
17
- entry_points: List[EntryPoint] = field(default_factory=list)
18
- setup_cfg: Optional[Path] = None
19
- setup_py: Optional[Path] = None
20
- pyproject_toml: Optional[Path] = None
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) -> Optional[EntryPoint]:
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) -> Optional[EntryPoint]:
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: Union[Path, str]) -> PackageInfo:
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: Dict[str, Any] = {}
98
- self._setup_kwargs: Dict[str, Any] = {}
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: Optional[ast.expr]) -> Any:
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: Optional[Any] = None) -> Any:
130
+ def get(self, key: str, default: Any | None = None) -> Any:
131
131
  return self._setup_kwargs.get(key, default)