napari-plugin-manager 0.1.0__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.
@@ -0,0 +1,131 @@
1
+ """
2
+ These convenience functions will be useful for searching pypi for packages
3
+ that match the plugin naming convention, and retrieving related metadata.
4
+ """
5
+
6
+ import json
7
+ from collections.abc import Iterator
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from functools import lru_cache
10
+ from typing import (
11
+ Optional,
12
+ TypedDict,
13
+ cast,
14
+ )
15
+ from urllib.error import HTTPError, URLError
16
+ from urllib.request import Request, urlopen
17
+
18
+ from napari.plugins.utils import normalized_name
19
+ from napari.utils.notifications import show_warning
20
+ from npe2 import PackageMetadata
21
+ from typing_extensions import NotRequired
22
+
23
+ PyPIname = str
24
+
25
+
26
+ @lru_cache
27
+ def _user_agent() -> str:
28
+ """Return a user agent string for use in http requests."""
29
+ import platform
30
+
31
+ from napari import __version__
32
+ from napari.utils import misc
33
+
34
+ if misc.running_as_constructor_app():
35
+ env = 'constructor'
36
+ elif misc.in_jupyter():
37
+ env = 'jupyter'
38
+ elif misc.in_ipython():
39
+ env = 'ipython'
40
+ else:
41
+ env = 'python'
42
+
43
+ parts = [
44
+ ('napari', __version__),
45
+ ('runtime', env),
46
+ (platform.python_implementation(), platform.python_version()),
47
+ (platform.system(), platform.release()),
48
+ ]
49
+ return ' '.join(f'{k}/{v}' for k, v in parts)
50
+
51
+
52
+ class _ShortSummaryDict(TypedDict):
53
+ """Objects returned at https://npe2api.vercel.app/api/extended_summary ."""
54
+
55
+ name: NotRequired[PyPIname]
56
+ version: str
57
+ summary: str
58
+ author: str
59
+ license: str
60
+ home_page: str
61
+
62
+
63
+ class SummaryDict(_ShortSummaryDict):
64
+ display_name: NotRequired[str]
65
+ pypi_versions: NotRequired[list[str]]
66
+ conda_versions: NotRequired[list[str]]
67
+
68
+
69
+ @lru_cache
70
+ def plugin_summaries() -> list[SummaryDict]:
71
+ """Return PackageMetadata object for all known napari plugins."""
72
+ url = 'https://npe2api.vercel.app/api/extended_summary'
73
+ with urlopen(Request(url, headers={'User-Agent': _user_agent()})) as resp:
74
+ return json.load(resp)
75
+
76
+
77
+ @lru_cache
78
+ def conda_map() -> dict[PyPIname, Optional[str]]:
79
+ """Return map of PyPI package name to conda_channel/package_name ()."""
80
+ url = 'https://npe2api.vercel.app/api/conda'
81
+ with urlopen(Request(url, headers={'User-Agent': _user_agent()})) as resp:
82
+ return json.load(resp)
83
+
84
+
85
+ def iter_napari_plugin_info() -> Iterator[tuple[PackageMetadata, bool, dict]]:
86
+ """Iterator of tuples of ProjectInfo, Conda availability for all napari plugins."""
87
+ try:
88
+ with ThreadPoolExecutor() as executor:
89
+ data = executor.submit(plugin_summaries)
90
+ _conda = executor.submit(conda_map)
91
+
92
+ conda = _conda.result()
93
+ data_set = data.result()
94
+ except (HTTPError, URLError):
95
+ show_warning(
96
+ 'Plugin manager: There seems to be an issue with network connectivity. '
97
+ 'Remote plugins cannot be installed, only local ones.\n'
98
+ )
99
+ return
100
+
101
+ conda_set = {normalized_name(x) for x in conda}
102
+ for info in data_set:
103
+ info_copy = dict(info)
104
+ info_copy.pop('display_name', None)
105
+ pypi_versions = info_copy.pop('pypi_versions')
106
+ conda_versions = info_copy.pop('conda_versions')
107
+ info_ = cast(_ShortSummaryDict, info_copy)
108
+
109
+ # TODO: use this better.
110
+ # this would require changing the api that qt_plugin_dialog expects to
111
+ # receive
112
+
113
+ # TODO: once the new version of npe2 is out, this can be refactored
114
+ # to all the metadata includes the conda and pypi versions.
115
+ extra_info = {
116
+ 'home_page': info_.get('home_page', ''),
117
+ 'display_name': info.get('display_name', ''),
118
+ 'pypi_versions': pypi_versions,
119
+ 'conda_versions': conda_versions,
120
+ }
121
+ info_['name'] = normalized_name(info_['name'])
122
+ meta = PackageMetadata(**info_) # type:ignore[call-arg]
123
+
124
+ yield meta, (info_['name'] in conda_set), extra_info
125
+
126
+
127
+ def cache_clear():
128
+ """Clear the cache for all cached functions in this module."""
129
+ plugin_summaries.cache_clear()
130
+ conda_map.cache_clear()
131
+ _user_agent.cache_clear()