Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise a warning if multiple plugins exist with same name #333

Merged
merged 5 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#337](https://github.com/equinor/webviz-config/pull/337) - New generic plugin to
generate a [Pivot table](https://en.wikipedia.org/wiki/Pivot_table) based on
[Dash Pivottable](https://github.com/plotly/dash-pivottable).
- [#333](https://github.com/equinor/webviz-config/pull/333) - Warning is now raised
if more than one plugin project provides a plugin with the same name.

## [0.2.2] - 2020-11-16

Expand Down
60 changes: 60 additions & 0 deletions tests/test_plugin_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import warnings

import mock

from webviz_config.plugins._utils import load_webviz_plugins_with_metadata


class DistMock:
# pylint: disable=too-few-public-methods
def __init__(self, entry_points, name):
self.metadata = {"name": name}

self.entry_points = entry_points
self.version = "123"


plugin_entrypoint_mock1 = mock.Mock()
plugin_entrypoint_mock1.group = "webviz_config_plugins"
plugin_entrypoint_mock1.name = "SomePlugin1"

plugin_entrypoint_mock2 = mock.Mock()
plugin_entrypoint_mock2.group = "webviz_config_plugins"
plugin_entrypoint_mock2.name = "SomePlugin2"

dist_mock1 = DistMock([plugin_entrypoint_mock1], "dist_mock1")
dist_mock2 = DistMock([plugin_entrypoint_mock1], "dist_mock2")
dist_mock3 = DistMock([plugin_entrypoint_mock2], "dist_mock3")


def test_no_warning():
globals_mock = {}
with warnings.catch_warnings(record=True) as warn:
metadata = load_webviz_plugins_with_metadata(
[dist_mock1, dist_mock3], globals_mock
)
assert len(warn) == 0, "Too many warnings"

assert len(metadata) == 2, "Wrong number of items in metadata"
assert "SomePlugin1" in globals_mock
assert "SomePlugin2" in globals_mock


def test_warning_multiple():
globals_mock = {}
with warnings.catch_warnings(record=True) as warn:
metadata = load_webviz_plugins_with_metadata(
[dist_mock1, dist_mock2], globals_mock
)

assert len(warn) == 1
assert issubclass(warn[-1].category, RuntimeWarning)
assert str(warn[-1].message) == (
"Multiple versions of plugin with name SomePlugin1. "
"Already loaded from project dist_mock1. "
"Overwriting using plugin with from project dist_mock2"
)

assert len(metadata) == 1, "Wrong number of items in metadata"
assert metadata["SomePlugin1"]["dist_name"] == "dist_mock2", "Wrong dist name"
assert "SomePlugin1" in globals_mock
33 changes: 2 additions & 31 deletions webviz_config/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,16 @@
the utility itself.
"""

import inspect
from typing import Optional

try:
# Python 3.8+
# pylint: disable=ungrouped-imports
from importlib.metadata import distributions # type: ignore
from typing import TypedDict # type: ignore
except ModuleNotFoundError:
# Python < 3.8
from importlib_metadata import distributions # type: ignore
from typing_extensions import TypedDict # type: ignore

from ._utils import load_webviz_plugins_with_metadata, PluginDistInfo

class PluginDistInfo(TypedDict):
dist_name: str
dist_version: str
documentation_url: Optional[str]
download_url: Optional[str]
issue_url: Optional[str]


metadata = {}

for dist in distributions():
for entry_point in dist.entry_points:
if entry_point.group == "webviz_config_plugins":
project_urls = {
value.split(",")[0]: value.split(",")[1].strip()
for (key, value) in dist.metadata.items()
if key == "Project-URL"
}

metadata[entry_point.name] = {
"dist_name": dist.metadata["name"],
"dist_version": dist.version,
"documentation_url": project_urls.get("Documentation"),
"download_url": project_urls.get("Download"),
"tracker_url": project_urls.get("Tracker"),
}

globals()[entry_point.name] = entry_point.load()
metadata = load_webviz_plugins_with_metadata(distributions(), globals())
59 changes: 59 additions & 0 deletions webviz_config/plugins/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import warnings
from typing import Any, Dict, Iterable, Optional

try:
# Python 3.8+
# pylint: disable=ungrouped-imports
from typing import TypedDict # type: ignore
except ImportError:
# Python < 3.8
from typing_extensions import TypedDict # type: ignore


class PluginDistInfo(TypedDict):
dist_name: str
dist_version: str
documentation_url: Optional[str]
download_url: Optional[str]
tracker_url: Optional[str]


def load_webviz_plugins_with_metadata(
distributions: Iterable, loaded_plugins: Dict[str, Any]
) -> Dict[str, PluginDistInfo]:
"""Loads the given distributions, finds entry points corresponding to webviz-config
plugins, and put them into the mutable input dictionary loaded_plugins
(key is plugin name string, value is reference to plugin class).
Also returns a dictionary of plugin metadata.
"""

metadata: Dict[str, PluginDistInfo] = {}

for dist in distributions:
for entry_point in dist.entry_points:
if entry_point.group == "webviz_config_plugins":
project_urls = {
value.split(",")[0]: value.split(",")[1].strip()
for (key, value) in dist.metadata.items()
if key == "Project-URL"
}

if entry_point.name in metadata:
warnings.warn(
f"Multiple versions of plugin with name {entry_point.name}. "
f"Already loaded from project {metadata[entry_point.name]['dist_name']}. "
f"Overwriting using plugin with from project {dist.metadata['name']}",
RuntimeWarning,
)

metadata[entry_point.name] = {
"dist_name": dist.metadata["name"],
"dist_version": dist.version,
"documentation_url": project_urls.get("Documentation"),
"download_url": project_urls.get("Download"),
"tracker_url": project_urls.get("Tracker"),
}

loaded_plugins[entry_point.name] = entry_point.load()

return metadata