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.
- napari_plugin_manager/__init__.py +0 -0
- napari_plugin_manager/_tests/__init__.py +0 -0
- napari_plugin_manager/_tests/conftest.py +63 -0
- napari_plugin_manager/_tests/test_installer_process.py +338 -0
- napari_plugin_manager/_tests/test_npe2api.py +51 -0
- napari_plugin_manager/_tests/test_qt_plugin_dialog.py +563 -0
- napari_plugin_manager/_tests/test_utils.py +27 -0
- napari_plugin_manager/_version.py +16 -0
- napari_plugin_manager/npe2api.py +131 -0
- napari_plugin_manager/qt_package_installer.py +660 -0
- napari_plugin_manager/qt_plugin_dialog.py +1495 -0
- napari_plugin_manager/qt_widgets.py +14 -0
- napari_plugin_manager/styles.qss +352 -0
- napari_plugin_manager/utils.py +22 -0
- napari_plugin_manager-0.1.0.dist-info/LICENSE +29 -0
- napari_plugin_manager-0.1.0.dist-info/METADATA +255 -0
- napari_plugin_manager-0.1.0.dist-info/RECORD +19 -0
- napari_plugin_manager-0.1.0.dist-info/WHEEL +5 -0
- napari_plugin_manager-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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()
|