From ec64ab40a5db33ae8d56ebed0587a4cad6c22469 Mon Sep 17 00:00:00 2001 From: Waylan Limberg Date: Tue, 6 Apr 2021 11:51:45 -0400 Subject: [PATCH] Replace pkg_resources with importlib_metadata This is using the new "selection interface" ( by calling `entry_points` with the `group` parameter) that is being introduced in `importlib.metadata` of Python 3.10 (currently in alpha). While the backport (`importlib_metadata`) introduced the same change in version 3.6, various improvements have been made up through the current release (3.10). Therefore we require `importlib_metadata>=3.10` for all supported versions of Python (3.6-3.9). --- mkdocs/plugins.py | 6 +++--- mkdocs/tests/plugin_tests.py | 2 +- mkdocs/tests/utils/utils_tests.py | 32 +++++++++++++++---------------- mkdocs/utils/__init__.py | 25 ++++++++++++------------ requirements/project-min.txt | 1 + requirements/project.txt | 1 + setup.py | 3 ++- 7 files changed, 36 insertions(+), 34 deletions(-) diff --git a/mkdocs/plugins.py b/mkdocs/plugins.py index 5b480186..5623f89e 100644 --- a/mkdocs/plugins.py +++ b/mkdocs/plugins.py @@ -4,8 +4,8 @@ Implements the plugin API for MkDocs. """ -import pkg_resources import logging +import importlib_metadata from collections import OrderedDict from mkdocs.config.base import Config @@ -22,9 +22,9 @@ EVENTS = ( def get_plugins(): - """ Return a dict of all installed Plugins by name. """ + """ Return a dict of all installed Plugins as {name: EntryPoint}. """ - plugins = pkg_resources.iter_entry_points(group='mkdocs.plugins') + plugins = importlib_metadata.entry_points(group='mkdocs.plugins') return {plugin.name: plugin for plugin in plugins} diff --git a/mkdocs/tests/plugin_tests.py b/mkdocs/tests/plugin_tests.py index a92e7da9..8ae8136f 100644 --- a/mkdocs/tests/plugin_tests.py +++ b/mkdocs/tests/plugin_tests.py @@ -210,7 +210,7 @@ MockEntryPoint = mock.Mock() MockEntryPoint.configure_mock(**{'name': 'sample', 'load.return_value': DummyPlugin}) -@mock.patch('pkg_resources.iter_entry_points', return_value=[MockEntryPoint]) +@mock.patch('importlib_metadata.entry_points', return_value=[MockEntryPoint]) class TestPluginConfig(unittest.TestCase): def test_plugin_config_without_options(self, mock_class): diff --git a/mkdocs/tests/utils/utils_tests.py b/mkdocs/tests/utils/utils_tests.py index 629a0d4e..f8f10532 100644 --- a/mkdocs/tests/utils/utils_tests.py +++ b/mkdocs/tests/utils/utils_tests.py @@ -251,17 +251,17 @@ class UtilsTests(unittest.TestCase): sorted(utils.get_theme_names()), ['mkdocs', 'readthedocs']) - @mock.patch('pkg_resources.iter_entry_points', autospec=True) + @mock.patch('importlib_metadata.entry_points', autospec=True) def test_get_theme_dir(self, mock_iter): path = 'some/path' theme = mock.Mock() theme.name = 'mkdocs2' - theme.dist.key = 'mkdocs2' + theme.dist.name = 'mkdocs2' theme.load().__file__ = os.path.join(path, '__init__.py') - mock_iter.return_value = iter([theme]) + mock_iter.return_value = [theme] self.assertEqual(utils.get_theme_dir(theme.name), os.path.abspath(path)) @@ -269,53 +269,51 @@ class UtilsTests(unittest.TestCase): self.assertRaises(KeyError, utils.get_theme_dir, 'nonexistanttheme') - @mock.patch('pkg_resources.iter_entry_points', autospec=True) + @mock.patch('importlib_metadata.entry_points', autospec=True) def test_get_theme_dir_importerror(self, mock_iter): theme = mock.Mock() theme.name = 'mkdocs2' - theme.dist.key = 'mkdocs2' + theme.dist.name = 'mkdocs2' theme.load.side_effect = ImportError() - mock_iter.return_value = iter([theme]) + mock_iter.return_value = [theme] self.assertRaises(ImportError, utils.get_theme_dir, theme.name) - @mock.patch('pkg_resources.iter_entry_points', autospec=True) + @mock.patch('importlib_metadata.entry_points', autospec=True) def test_get_themes_warning(self, mock_iter): theme1 = mock.Mock() theme1.name = 'mkdocs2' - theme1.dist.key = 'mkdocs2' + theme1.dist.name = 'mkdocs2' theme1.load().__file__ = "some/path1" theme2 = mock.Mock() theme2.name = 'mkdocs2' - theme2.dist.key = 'mkdocs3' + theme2.dist.name = 'mkdocs3' theme2.load().__file__ = "some/path2" - mock_iter.return_value = iter([theme1, theme2]) + mock_iter.return_value = [theme1, theme2] self.assertEqual( sorted(utils.get_theme_names()), sorted(['mkdocs2', ])) - @mock.patch('pkg_resources.iter_entry_points', autospec=True) - @mock.patch('pkg_resources.get_entry_map', autospec=True) - def test_get_themes_error(self, mock_get, mock_iter): + @mock.patch('importlib_metadata.entry_points', autospec=True) + def test_get_themes_error(self, mock_iter): theme1 = mock.Mock() theme1.name = 'mkdocs' - theme1.dist.key = 'mkdocs' + theme1.dist.name = 'mkdocs' theme1.load().__file__ = "some/path1" theme2 = mock.Mock() theme2.name = 'mkdocs' - theme2.dist.key = 'mkdocs2' + theme2.dist.name = 'mkdocs2' theme2.load().__file__ = "some/path2" - mock_iter.return_value = iter([theme1, theme2]) - mock_get.return_value = {'mkdocs': theme1, } + mock_iter.return_value = [theme1, theme2] self.assertRaises(exceptions.ConfigurationError, utils.get_theme_names) diff --git a/mkdocs/utils/__init__.py b/mkdocs/utils/__init__.py index 5a8fe1e3..71df9f6a 100644 --- a/mkdocs/utils/__init__.py +++ b/mkdocs/utils/__init__.py @@ -8,13 +8,13 @@ and structure of the site and pages in the site. import logging import os -import pkg_resources import shutil import re import yaml import fnmatch import posixpath import functools +import importlib_metadata from datetime import datetime, timezone from urllib.parse import urlparse from yaml_env_tag import construct_env_tag @@ -316,23 +316,24 @@ def get_theme_dir(name): def get_themes(): - """ Return a dict of all installed themes as (name, entry point) pairs. """ + """ Return a dict of all installed themes as {name: EntryPoint}. """ themes = {} - builtins = pkg_resources.get_entry_map(dist='mkdocs', group='mkdocs.themes') + eps = importlib_metadata.entry_points(group='mkdocs.themes') + builtins = [ep.name for ep in eps if ep.dist.name == 'mkdocs'] - for theme in pkg_resources.iter_entry_points(group='mkdocs.themes'): + for theme in eps: - if theme.name in builtins and theme.dist.key != 'mkdocs': + if theme.name in builtins and theme.dist.name != 'mkdocs': raise exceptions.ConfigurationError( - "The theme {} is a builtin theme but {} provides a theme " - "with the same name".format(theme.name, theme.dist.key)) - + f"The theme '{theme.name}' is a builtin theme but the package '{theme.dist.name}' " + "attempts to provide a theme with the same name." + ) elif theme.name in themes: - multiple_packages = [themes[theme.name].dist.key, theme.dist.key] - log.warning("The theme %s is provided by the Python packages " - "'%s'. The one in %s will be used.", - theme.name, ','.join(multiple_packages), theme.dist.key) + log.warning( + f"A theme named '{theme.name}' is provided by the Python packages '{theme.dist.name}'" + f"and '{themes[theme.name].dist.name}'. The one in '{theme.dist.name}' will be used." + ) themes[theme.name] = theme diff --git a/requirements/project-min.txt b/requirements/project-min.txt index 80bc5f54..526feec6 100644 --- a/requirements/project-min.txt +++ b/requirements/project-min.txt @@ -8,3 +8,4 @@ mdx_gh_links==0.2 ghp-import==1.0 pyyaml_env_tag==0.1 mkdocs-redirects==1.0.1 +importlib_metadata==3.10.0 diff --git a/requirements/project.txt b/requirements/project.txt index 4717212d..7d1b5269 100644 --- a/requirements/project.txt +++ b/requirements/project.txt @@ -8,3 +8,4 @@ mdx_gh_links>=0.2 ghp-import>=1.0 pyyaml_env_tag>=0.1 mkdocs-redirects>=1.0.1 +importlib_metadata>=3.10 diff --git a/setup.py b/setup.py index 782fec0a..d468d52e 100755 --- a/setup.py +++ b/setup.py @@ -59,7 +59,8 @@ setup( 'PyYAML>=3.10', 'tornado>=5.0', 'ghp-import>=1.0', - 'pyyaml_env_tag>=0.1' + 'pyyaml_env_tag>=0.1', + 'importlib_metadata>=3.10' ], python_requires='>=3.6', entry_points={