Source code for jdaviz.configs.default.plugins.about.about
import requests
from packaging.version import Version
from traitlets import Bool, List, Unicode, observe
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import PluginTemplateMixin
from jdaviz.core.user_api import PluginUserApi
try:
from jdaviz import __version__
except ImportError: # pragma: no cover
__version__ = "unknown"
__all__ = ['About']
# Module-level cache so the PyPI check runs at most once per process.
_pypi_version_cache = {}
[docs]
@tray_registry('about', label="About",
category='core', sidebar='popup')
class About(PluginTemplateMixin):
"""
Show information about Jdaviz.
"""
template_file = __file__, "about.vue"
jdaviz_version = Unicode("unknown").tag(sync=True)
jdaviz_pypi = Unicode("unknown").tag(sync=True)
not_is_latest = Bool(False).tag(sync=True)
downstream_packages = List([]).tag(sync=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.jdaviz_version = __version__
self._pypi_fetched = False
# description displayed under plugin title in tray
self._plugin_description = 'Information about Jdaviz and links to documentation and resources.' # noqa
@observe('plugin_opened')
def _on_plugin_opened(self, *args):
"""
Fetch the latest PyPI version lazily on first open.
The network call is deferred from ``__init__`` so that app
startup never opens (and potentially leaks) SSL sockets.
"""
if self._pypi_fetched or not self.plugin_opened:
return
# If __version__ is unknown then there's no point to try to fetch the PyPI version,
# so skip it and avoid the network call now and in the future.
self._pypi_fetched = True
if __version__ == 'unknown':
return
_ver_pypi = latest_version_from_pypi('jdaviz')
if _ver_pypi:
self.jdaviz_pypi = _ver_pypi
self.not_is_latest = Version(__version__) < Version(_ver_pypi)
else: # pragma: no cover
self.jdaviz_pypi = 'unknown'
self.not_is_latest = False
[docs]
def register_downstream_package(self, name, version, abbreviation=None):
"""
Register a downstream package to be listed in the About panel.
Parameters
----------
name : str
The display name of the downstream package.
version : str
The installed version string.
abbreviation : str, optional
Short label shown as a badge on the version button in the app bar.
Defaults to the first two characters of ``name`` in lowercase.
"""
entry = {'name': name, 'version': version,
'abbreviation': abbreviation or name[:2].lower()}
if entry not in self.downstream_packages:
self.downstream_packages = self.downstream_packages + [entry]
self._app.state.downstream_packages = self._app.state.downstream_packages + [entry]
@property
def user_api(self):
if self.config != 'deconfigged':
return super().user_api
# Deconfigged needs to not show open_in_tray, but instead show_popup
expose = ['show_popup']
return PluginUserApi(self, expose=expose, in_tray=False)
def latest_version_from_pypi(package_name):
"""
Return the latest version string for a package on PyPI, or ``None``.
Results are cached per-process so repeated calls do not incur
additional network overhead.
Parameters
----------
package_name : str
The PyPI package name to look up.
Returns
-------
version : str or None
Latest version string, or ``None`` on failure.
"""
if package_name in _pypi_version_cache:
return _pypi_version_cache[package_name]
url = f'https://pypi.org/pypi/{package_name}/json'
version = None
try:
with requests.Session() as session:
r = session.get(url, timeout=60)
if r.ok:
d = r.json()
version = d['info']['version']
except Exception: # nosec # pragma: no cover
pass
_pypi_version_cache[package_name] = version
return version