diff --git a/CHANGELOG.md b/CHANGELOG.md index 0faa3659..7b43905c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/tests/test_plugin_init.py b/tests/test_plugin_init.py new file mode 100644 index 00000000..011df9a1 --- /dev/null +++ b/tests/test_plugin_init.py @@ -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 diff --git a/webviz_config/plugins/__init__.py b/webviz_config/plugins/__init__.py index fd3a4af5..b22bb5d8 100644 --- a/webviz_config/plugins/__init__.py +++ b/webviz_config/plugins/__init__.py @@ -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()) diff --git a/webviz_config/plugins/_utils.py b/webviz_config/plugins/_utils.py new file mode 100644 index 00000000..fbb19bfc --- /dev/null +++ b/webviz_config/plugins/_utils.py @@ -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