From 4e93357b96f755a81030cecbc3d92e44f58b3563 Mon Sep 17 00:00:00 2001 From: Thomas Rausch Date: Fri, 28 Jul 2023 19:23:39 +0200 Subject: [PATCH] add metadata package to resolve distribution information (#6) --- plugin/manager.py | 10 ++++++++++ plugin/metadata.py | 37 +++++++++++++++++++++++++++++++++++++ tests/test_metadata.py | 12 ++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 plugin/metadata.py create mode 100644 tests/test_metadata.py diff --git a/plugin/manager.py b/plugin/manager.py index 2919a38..360e7aa 100644 --- a/plugin/manager.py +++ b/plugin/manager.py @@ -11,6 +11,7 @@ PluginSpec, PluginSpecResolver, ) +from .metadata import resolve_distribution_information LOG = logging.getLogger(__name__) @@ -114,6 +115,15 @@ class PluginContainer(Generic[P]): init_error: Exception = None load_error: Exception = None + @property + def distribution(self): + """ + Uses metadata from importlib to resolve the distribution information for this plugin. + + :return: the importlib.metadata.Distribution object + """ + return resolve_distribution_information(self.plugin_spec) + class PluginManager(PluginLifecycleNotifierMixin, Generic[P]): """ diff --git a/plugin/metadata.py b/plugin/metadata.py new file mode 100644 index 0000000..4f3622d --- /dev/null +++ b/plugin/metadata.py @@ -0,0 +1,37 @@ +import inspect +from functools import lru_cache +from importlib import metadata +from typing import Mapping, Optional + +from .core import PluginSpec + + +@lru_cache() +def packages_distributions() -> Mapping[str, list[str]]: + """ + Cache wrapper around metadata.packages_distributions, which returns a mapping of top-level packages to + their distributions. + + :return: package to distribution mapping + """ + return metadata.packages_distributions() + + +def resolve_distribution_information(plugin_spec: PluginSpec) -> Optional[metadata.Distribution]: + """ + Resolves for a PluginSpec the python distribution package it comes from. Currently, this raises an + error for plugins that come from a namespace package (i.e., when a package is part of multiple + distributions). + + :param plugin_spec: the plugin spec to resolve + :return: the Distribution metadata if it exists + """ + package = inspect.getmodule(plugin_spec.factory).__name__ + root_package = package.split(".")[0] + distributions = packages_distributions().get(root_package) + if not distributions: + return None + if len(distributions) > 1: + raise ValueError("cannot deal with plugins that are part of namespace packages") + + return metadata.distribution(distributions[0]) diff --git a/tests/test_metadata.py b/tests/test_metadata.py new file mode 100644 index 0000000..d453d74 --- /dev/null +++ b/tests/test_metadata.py @@ -0,0 +1,12 @@ +from plugin import PluginSpec +from plugin.metadata import resolve_distribution_information + + +def test_resolve_distribution_information(): + import pytest + + # fake a plugin spec and use pytest as test object + fake_plugin_spec = PluginSpec("foo", "bar", pytest.fixture) + dist = resolve_distribution_information(fake_plugin_spec) + assert dist.name == "pytest" + assert dist.metadata["License"] == "MIT"