Repository: mkdocs/mkdocs

Issue:
No python version marker on importlib-metadata
### Discussed in https://github.com/mkdocs/mkdocs/discussions/2956

<div type='discussions-op-text'>

<sup>Originally posted by **pawamoy** August 31, 2022</sup>
The `importlib-metadata` dependency was bumped to `>=4.3` in https://github.com/mkdocs/mkdocs/commit/ec9b583b02376b909c10889fef780cdf2f25a23d.

We're now getting resolution conflicts with Flake8 because they set an upper bound on `importlib-metadata`: `<4.3`. From their answers in different issues and PRs (starting point here: https://github.com/PyCQA/flake8/issues/1503), it seems there has been quite some friction regarding this, and they likely won't change their upper bound.

Back to MkDocs: shouldn't the dependency have a marker like `python_version < '3.8'`?

Actual conflict we get (PDM output, URLs stripped):

```
Conflicts detected: 
  importlib-metadata>=4.3 (from <Candidate mkdocs 1.3.1)
  importlib-metadata; python_version < "3.8" (from <Candidate flake8-comprehensions 3.10.0)
  importlib-metadata; python_version < "3.8" (from <Candidate flake8-tidy-imports 4.8.0)
  importlib-metadata>=0.12; python_version < "3.8" (from <Candidate pytest 7.1.2)
  importlib-metadata>=3.6.0; python_version < "3.10" (from <Candidate pytest-randomly 3.12.0)
  importlib-metadata; python_version < "3.8" (from <Candidate wps-light 0.16.1)
  importlib-metadata<4.3,>=1.1.0; python_version < "3.8" (from <Candidate flake8 5.0.4)
```

We're using Python 3.8 so I believe adding the marker would help in our case at least. Unless the right marker to use is `python_version < '3.10'`.</div>


Discussion:
> shouldn't the dependency have a marker like python_version < '3.8'?

I would welcome a pull request that makes it actually so. Right now I think the import is unconditional. I think at the moment I don't have any more context than you do
btw https://github.com/pygments/pygments/pull/2155/files can be a useful example. though we shouldn't go quite as far with compatibility (no pkg_resources)

Resolving patch:
diff --git a/mkdocs/plugins.py b/mkdocs/plugins.py
--- a/mkdocs/plugins.py
+++ b/mkdocs/plugins.py
@@ -5,10 +5,15 @@
 from __future__ import annotations
 
 import logging
+import sys
 from collections import OrderedDict
 from typing import Any, Callable, Dict, Optional, Sequence, Tuple, TypeVar, overload
 
-import importlib_metadata
+if sys.version_info >= (3, 10):
+    from importlib.metadata import entry_points
+else:
+    from importlib_metadata import entry_points
+
 import jinja2.environment
 
 from mkdocs.config.base import BaseConfigOption, Config
@@ -23,7 +28,7 @@
 def get_plugins():
     """Return a dict of all installed Plugins as {name: EntryPoint}."""
 
-    plugins = importlib_metadata.entry_points(group='mkdocs.plugins')
+    plugins = entry_points(group='mkdocs.plugins')
 
     # Allow third-party plugins to override core plugins
     pluginmap = {}
diff --git a/mkdocs/utils/__init__.py b/mkdocs/utils/__init__.py
--- a/mkdocs/utils/__init__.py
+++ b/mkdocs/utils/__init__.py
@@ -12,13 +12,18 @@
 import posixpath
 import re
 import shutil
+import sys
 import warnings
 from collections import defaultdict
 from datetime import datetime, timezone
 from pathlib import PurePath
 from urllib.parse import urlsplit
 
-import importlib_metadata
+if sys.version_info >= (3, 10):
+    from importlib.metadata import entry_points
+else:
+    from importlib_metadata import entry_points
+
 import yaml
 from mergedeep import merge
 from yaml_env_tag import construct_env_tag
@@ -307,7 +312,7 @@ def get_themes():
     """Return a dict of all installed themes as {name: EntryPoint}."""
 
     themes = {}
-    eps = set(importlib_metadata.entry_points(group='mkdocs.themes'))
+    eps = set(entry_points(group='mkdocs.themes'))
     builtins = {ep.name for ep in eps if ep.dist.name == 'mkdocs'}
 
     for theme in eps:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -70,7 +70,7 @@ def get_packages(package):
         'watchdog>=2.0',
         'ghp-import>=1.0',
         'pyyaml_env_tag>=0.1',
-        'importlib_metadata>=4.3',
+        'importlib_metadata>=4.3; python_version < "3.10"',
         'packaging>=20.5',
         'mergedeep>=1.3.4'
     ],


Test patch:
diff --git a/mkdocs/tests/plugin_tests.py b/mkdocs/tests/plugin_tests.py
--- a/mkdocs/tests/plugin_tests.py
+++ b/mkdocs/tests/plugin_tests.py
@@ -239,7 +239,7 @@ def on_build_error(self, error, **kwargs):
 MockEntryPoint.configure_mock(**{'name': 'sample', 'load.return_value': DummyPlugin})
 
 
-@mock.patch('importlib_metadata.entry_points', return_value=[MockEntryPoint])
+@mock.patch('mkdocs.plugins.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
--- a/mkdocs/tests/utils/utils_tests.py
+++ b/mkdocs/tests/utils/utils_tests.py
@@ -293,7 +293,7 @@ def test_get_themes(self):
 
         self.assertEqual(sorted(utils.get_theme_names()), ['mkdocs', 'readthedocs'])
 
-    @mock.patch('importlib_metadata.entry_points', autospec=True)
+    @mock.patch('mkdocs.utils.entry_points', autospec=True)
     def test_get_theme_dir(self, mock_iter):
 
         path = 'some/path'
@@ -312,7 +312,7 @@ def test_get_theme_dir_keyerror(self):
         with self.assertRaises(KeyError):
             utils.get_theme_dir('nonexistanttheme')
 
-    @mock.patch('importlib_metadata.entry_points', autospec=True)
+    @mock.patch('mkdocs.utils.entry_points', autospec=True)
     def test_get_theme_dir_importerror(self, mock_iter):
 
         theme = mock.Mock()
@@ -325,7 +325,7 @@ def test_get_theme_dir_importerror(self, mock_iter):
         with self.assertRaises(ImportError):
             utils.get_theme_dir(theme.name)
 
-    @mock.patch('importlib_metadata.entry_points', autospec=True)
+    @mock.patch('mkdocs.utils.entry_points', autospec=True)
     def test_get_themes_warning(self, mock_iter):
 
         theme1 = mock.Mock()
@@ -342,7 +342,7 @@ def test_get_themes_warning(self, mock_iter):
 
         self.assertEqual(sorted(utils.get_theme_names()), sorted(['mkdocs2']))
 
-    @mock.patch('importlib_metadata.entry_points', autospec=True)
+    @mock.patch('mkdocs.utils.entry_points', autospec=True)
     def test_get_themes_error(self, mock_iter):
 
         theme1 = mock.Mock()