mirror of
https://gitlab.alpinelinux.org/alpine/aports.git
synced 2026-05-17 10:36:22 +02:00
Fix `ModuleNotFoundError: No module named 'pkg_resources'` error when building with setuptools 82. Ref: https://gitlab.alpinelinux.org/alpine/aports/-/issues/18025
489 lines
17 KiB
Diff
489 lines
17 KiB
Diff
Patch-Source: https://github.com/mopidy/mopidy/commit/136c6f435eafecb56d6b56d970db12c098cd88a2
|
|
Backported for 3.4.2.
|
|
---
|
|
diff --git a/mopidy/__init__.py b/mopidy/__init__.py
|
|
index ccb8082..0f9efd7 100644
|
|
--- a/mopidy/__init__.py
|
|
+++ b/mopidy/__init__.py
|
|
@@ -2,7 +2,7 @@ import platform
|
|
import sys
|
|
import warnings
|
|
|
|
-import pkg_resources
|
|
+from importlib.metadata import version
|
|
|
|
if not sys.version_info >= (3, 7):
|
|
sys.exit(
|
|
@@ -12,4 +12,4 @@ if not sys.version_info >= (3, 7):
|
|
|
|
warnings.filterwarnings("ignore", "could not open display")
|
|
|
|
-__version__ = pkg_resources.get_distribution("Mopidy").version
|
|
+__version__ = version("Mopidy")
|
|
diff --git a/mopidy/ext.py b/mopidy/ext.py
|
|
index a2f7799..46da78c 100644
|
|
--- a/mopidy/ext.py
|
|
+++ b/mopidy/ext.py
|
|
@@ -4,7 +4,7 @@ import logging
|
|
from collections.abc import Mapping
|
|
from typing import TYPE_CHECKING, NamedTuple
|
|
|
|
-import pkg_resources
|
|
+import importlib_metadata as metadata
|
|
|
|
from mopidy import config as config_lib
|
|
from mopidy import exceptions
|
|
@@ -17,6 +17,8 @@ if TYPE_CHECKING:
|
|
from mopidy.commands import Command
|
|
from mopidy.config import ConfigSchema
|
|
|
|
+ from typing_extensions import TypeAlias
|
|
+
|
|
Config = Dict[str, Dict[str, Any]]
|
|
|
|
|
|
@@ -73,6 +75,12 @@ class Extension:
|
|
schema["enabled"] = config_lib.Boolean()
|
|
return schema
|
|
|
|
+ @classmethod
|
|
+ def check_attr(cls) -> None:
|
|
+ """Check if ext_name exist."""
|
|
+ if not hasattr(cls, "ext_name") or cls.ext_name is None:
|
|
+ raise AttributeError(f"{cls} not an extension or ext_name missing!")
|
|
+
|
|
@classmethod
|
|
def get_cache_dir(cls, config: Config) -> Path:
|
|
"""Get or create cache directory for the extension.
|
|
@@ -82,8 +90,7 @@ class Extension:
|
|
:param config: the Mopidy config object
|
|
:return: pathlib.Path
|
|
"""
|
|
- if cls.ext_name is None:
|
|
- raise AssertionError
|
|
+ cls.check_attr()
|
|
cache_dir_path = (
|
|
path.expand_path(config["core"]["cache_dir"]) / cls.ext_name
|
|
)
|
|
@@ -97,8 +104,7 @@ class Extension:
|
|
:param config: the Mopidy config object
|
|
:return: pathlib.Path
|
|
"""
|
|
- if cls.ext_name is None:
|
|
- raise AssertionError
|
|
+ cls.check_attr()
|
|
config_dir_path = (
|
|
path.expand_path(config["core"]["config_dir"]) / cls.ext_name
|
|
)
|
|
@@ -114,8 +120,7 @@ class Extension:
|
|
:param config: the Mopidy config object
|
|
:returns: pathlib.Path
|
|
"""
|
|
- if cls.ext_name is None:
|
|
- raise AssertionError
|
|
+ cls.check_attr()
|
|
data_dir_path = (
|
|
path.expand_path(config["core"]["data_dir"]) / cls.ext_name
|
|
)
|
|
@@ -215,14 +220,12 @@ def load_extensions() -> List[ExtensionData]:
|
|
|
|
installed_extensions = []
|
|
|
|
- for entry_point in pkg_resources.iter_entry_points("mopidy.ext"):
|
|
+ for entry_point in metadata.entry_points(group="mopidy.ext"):
|
|
logger.debug("Loading entry point: %s", entry_point)
|
|
try:
|
|
- extension_class = entry_point.resolve()
|
|
- except Exception as e:
|
|
- logger.exception(
|
|
- f"Failed to load extension {entry_point.name}: {e}"
|
|
- )
|
|
+ extension_class = entry_point.load()
|
|
+ except Exception as exc:
|
|
+ logger.exception(f"Failed to load extension {entry_point.name}: {exc}")
|
|
continue
|
|
|
|
try:
|
|
@@ -286,28 +289,15 @@ def validate_extension_data(data: ExtensionData) -> bool:
|
|
return False
|
|
|
|
try:
|
|
- data.entry_point.require()
|
|
- except pkg_resources.DistributionNotFound as exc:
|
|
+ data.entry_point.load()
|
|
+ except ModuleNotFoundError as exc:
|
|
logger.info(
|
|
- "Disabled extension %s: Dependency %s not found",
|
|
+ "Disabled extension %s: Exception %s",
|
|
data.extension.ext_name,
|
|
exc,
|
|
)
|
|
- return False
|
|
- except pkg_resources.VersionConflict as exc:
|
|
- if len(exc.args) == 2:
|
|
- found, required = exc.args
|
|
- logger.info(
|
|
- "Disabled extension %s: %s required, but found %s at %s",
|
|
- data.extension.ext_name,
|
|
- required,
|
|
- found,
|
|
- found.location,
|
|
- )
|
|
- else:
|
|
- logger.info(
|
|
- "Disabled extension %s: %s", data.extension.ext_name, exc
|
|
- )
|
|
+ # Remark: There are no version check, so any version is accepted
|
|
+ # this is a difference to pkg_resources, and affect debugging.
|
|
return False
|
|
|
|
try:
|
|
diff --git a/mopidy/internal/deps.py b/mopidy/internal/deps.py
|
|
index c8b53a4..e4fb48b 100644
|
|
--- a/mopidy/internal/deps.py
|
|
+++ b/mopidy/internal/deps.py
|
|
@@ -1,9 +1,10 @@
|
|
import functools
|
|
import os
|
|
import platform
|
|
+import re
|
|
import sys
|
|
|
|
-import pkg_resources
|
|
+import importlib_metadata as metadata
|
|
|
|
from mopidy.internal import formatting
|
|
from mopidy.internal.gi import Gst, gi
|
|
@@ -12,9 +13,9 @@ from mopidy.internal.gi import Gst, gi
|
|
def format_dependency_list(adapters=None):
|
|
if adapters is None:
|
|
dist_names = {
|
|
- ep.dist.project_name
|
|
- for ep in pkg_resources.iter_entry_points("mopidy.ext")
|
|
- if ep.dist.project_name != "Mopidy"
|
|
+ ep.dist.name
|
|
+ for ep in metadata.entry_points(group="mopidy.ext")
|
|
+ if ep.dist.name != "Mopidy"
|
|
}
|
|
dist_infos = [
|
|
functools.partial(pkg_info, dist_name) for dist_name in dist_names
|
|
@@ -87,25 +88,32 @@ def pkg_info(
|
|
if project_name is None:
|
|
project_name = "Mopidy"
|
|
try:
|
|
- distribution = pkg_resources.get_distribution(project_name)
|
|
- extras = include_extras and distribution.extras or []
|
|
- if include_transitive_deps:
|
|
- dependencies = [
|
|
- pkg_info(
|
|
- d.project_name,
|
|
- include_transitive_deps=d.project_name != "Mopidy",
|
|
+ distribution = metadata.distribution(project_name)
|
|
+ if include_transitive_deps and distribution.requires:
|
|
+ dependencies = []
|
|
+ for raw in distribution.requires:
|
|
+ if "importlib_metadata" in raw or (
|
|
+ not include_extras and "extra" in raw
|
|
+ ):
|
|
+ continue
|
|
+ entry = re.match(
|
|
+ "[a-zA-Z0-9_']+", raw
|
|
+ ).group() # pyright: ignore[reportOptionalMemberAccess]
|
|
+ dependencies.append(
|
|
+ pkg_info(
|
|
+ entry,
|
|
+ include_transitive_deps=entry != "Mopidy",
|
|
+ )
|
|
)
|
|
- for d in distribution.requires(extras)
|
|
- ]
|
|
else:
|
|
dependencies = []
|
|
return {
|
|
"name": project_name,
|
|
"version": distribution.version,
|
|
- "path": distribution.location,
|
|
+ "path": str(distribution.locate_file(".")),
|
|
"dependencies": dependencies,
|
|
}
|
|
- except pkg_resources.ResolutionError:
|
|
+ except metadata.PackageNotFoundError:
|
|
return {
|
|
"name": project_name,
|
|
}
|
|
diff --git a/setup.cfg b/setup.cfg
|
|
index 808d6ef..010950f 100644
|
|
--- a/setup.cfg
|
|
+++ b/setup.cfg
|
|
@@ -41,6 +41,7 @@ install_requires =
|
|
requests >= 2.0
|
|
setuptools
|
|
tornado >= 4.4
|
|
+ importlib_metadata >= 4.6
|
|
|
|
|
|
[options.extras_require]
|
|
diff --git a/tests/internal/test_deps.py b/tests/internal/test_deps.py
|
|
index e30e95b..d2af326 100644
|
|
--- a/tests/internal/test_deps.py
|
|
+++ b/tests/internal/test_deps.py
|
|
@@ -1,9 +1,10 @@
|
|
import platform
|
|
import sys
|
|
+from pathlib import Path
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
-import pkg_resources
|
|
+import importlib_metadata as metadata
|
|
|
|
from mopidy.internal import deps
|
|
from mopidy.internal.gi import Gst, gi
|
|
@@ -79,25 +80,31 @@ class DepsTest(unittest.TestCase):
|
|
assert gi.__version__ in result["other"]
|
|
assert "Relevant elements:" in result["other"]
|
|
|
|
- @mock.patch("pkg_resources.get_distribution")
|
|
+ @mock.patch("importlib_metadata.distribution")
|
|
def test_pkg_info(self, get_distribution_mock):
|
|
- dist_setuptools = mock.Mock()
|
|
- dist_setuptools.project_name = "setuptools"
|
|
+ dist_setuptools = mock.MagicMock()
|
|
+ dist_setuptools.name = "setuptools"
|
|
dist_setuptools.version = "0.6"
|
|
- dist_setuptools.location = "/tmp/example/setuptools"
|
|
- dist_setuptools.requires.return_value = []
|
|
+ dist_setuptools.locate_file = mock.MagicMock(
|
|
+ return_value=Path("/tmp/example/setuptools/main.py")
|
|
+ )
|
|
+ dist_setuptools.requires = []
|
|
|
|
dist_pykka = mock.Mock()
|
|
- dist_pykka.project_name = "Pykka"
|
|
+ dist_pykka.name = "Pykka"
|
|
dist_pykka.version = "1.1"
|
|
- dist_pykka.location = "/tmp/example/pykka"
|
|
- dist_pykka.requires.return_value = [dist_setuptools]
|
|
+ dist_pykka.locate_file = mock.MagicMock(
|
|
+ return_value=Path("/tmp/example/pykka/main.py")
|
|
+ )
|
|
+ dist_pykka.requires = [f"{dist_setuptools.name}==0.6"]
|
|
|
|
dist_mopidy = mock.Mock()
|
|
- dist_mopidy.project_name = "Mopidy"
|
|
+ dist_mopidy.name = "Mopidy"
|
|
dist_mopidy.version = "0.13"
|
|
- dist_mopidy.location = "/tmp/example/mopidy"
|
|
- dist_mopidy.requires.return_value = [dist_pykka]
|
|
+ dist_mopidy.locate_file = mock.MagicMock(
|
|
+ return_value=Path("/tmp/example/mopidy/no_name.py")
|
|
+ )
|
|
+ dist_mopidy.requires = [f"{dist_pykka.name}==1.1"]
|
|
|
|
get_distribution_mock.side_effect = [
|
|
dist_mopidy,
|
|
@@ -119,9 +126,9 @@ class DepsTest(unittest.TestCase):
|
|
assert "setuptools" == dep_info_setuptools["name"]
|
|
assert "0.6" == dep_info_setuptools["version"]
|
|
|
|
- @mock.patch("pkg_resources.get_distribution")
|
|
+ @mock.patch("importlib_metadata.distribution")
|
|
def test_pkg_info_for_missing_dist(self, get_distribution_mock):
|
|
- get_distribution_mock.side_effect = pkg_resources.DistributionNotFound
|
|
+ get_distribution_mock.side_effect = metadata.PackageNotFoundError("test")
|
|
|
|
result = deps.pkg_info()
|
|
|
|
@@ -129,9 +136,10 @@ class DepsTest(unittest.TestCase):
|
|
assert "version" not in result
|
|
assert "path" not in result
|
|
|
|
- @mock.patch("pkg_resources.get_distribution")
|
|
+ @unittest.skip("Version control missing in metadata")
|
|
+ @mock.patch("importlib_metadata.distribution")
|
|
def test_pkg_info_for_wrong_dist_version(self, get_distribution_mock):
|
|
- get_distribution_mock.side_effect = pkg_resources.VersionConflict
|
|
+ # get_distribution_mock.side_effect = metadata.VersionConflict
|
|
|
|
result = deps.pkg_info()
|
|
|
|
diff --git a/tests/test_ext.py b/tests/test_ext.py
|
|
index afba70a..5903fc3 100644
|
|
--- a/tests/test_ext.py
|
|
+++ b/tests/test_ext.py
|
|
@@ -1,7 +1,7 @@
|
|
import pathlib
|
|
+from importlib import metadata
|
|
from unittest import mock
|
|
|
|
-import pkg_resources
|
|
import pytest
|
|
|
|
from mopidy import config, exceptions, ext
|
|
@@ -74,22 +74,23 @@ class TestExtension:
|
|
class TestLoadExtensions:
|
|
@pytest.fixture
|
|
def iter_entry_points_mock(self, request):
|
|
- patcher = mock.patch("pkg_resources.iter_entry_points")
|
|
+ patcher = mock.patch("importlib_metadata.entry_points")
|
|
iter_entry_points = patcher.start()
|
|
iter_entry_points.return_value = []
|
|
yield iter_entry_points
|
|
patcher.stop()
|
|
|
|
+ @pytest.fixture
|
|
+ def mock_entry_point(self, iter_entry_points_mock):
|
|
+ entry_point = mock.Mock()
|
|
+ entry_point.load = mock.Mock(return_value=DummyExtension)
|
|
+ iter_entry_points_mock.return_value = [entry_point]
|
|
+ return entry_point
|
|
+
|
|
def test_no_extensions(self, iter_entry_points_mock):
|
|
- iter_entry_points_mock.return_value = []
|
|
assert ext.load_extensions() == []
|
|
|
|
- def test_load_extensions(self, iter_entry_points_mock):
|
|
- mock_entry_point = mock.Mock()
|
|
- mock_entry_point.resolve.return_value = DummyExtension
|
|
-
|
|
- iter_entry_points_mock.return_value = [mock_entry_point]
|
|
-
|
|
+ def test_load_extensions(self, mock_entry_point):
|
|
expected = ext.ExtensionData(
|
|
any_testextension,
|
|
mock_entry_point,
|
|
@@ -100,66 +101,41 @@ class TestLoadExtensions:
|
|
|
|
assert ext.load_extensions() == [expected]
|
|
|
|
- def test_gets_wrong_class(self, iter_entry_points_mock):
|
|
+ def test_load_extensions_exception(self, mock_entry_point, caplog):
|
|
+ mock_entry_point.load.side_effect = Exception("test")
|
|
+ ext.load_extensions()
|
|
+ assert "Failed to load extension" in caplog.records[0].message
|
|
+
|
|
+ def test_gets_wrong_class(self, mock_entry_point):
|
|
class WrongClass:
|
|
pass
|
|
|
|
- mock_entry_point = mock.Mock()
|
|
- mock_entry_point.resolve.return_value = WrongClass
|
|
-
|
|
- iter_entry_points_mock.return_value = [mock_entry_point]
|
|
-
|
|
+ mock_entry_point.load.return_value = WrongClass
|
|
assert ext.load_extensions() == []
|
|
|
|
- def test_gets_instance(self, iter_entry_points_mock):
|
|
- mock_entry_point = mock.Mock()
|
|
- mock_entry_point.resolve.return_value = DummyExtension()
|
|
-
|
|
- iter_entry_points_mock.return_value = [mock_entry_point]
|
|
-
|
|
+ def test_gets_instance(self, mock_entry_point):
|
|
+ mock_entry_point.load.return_value = DummyExtension()
|
|
assert ext.load_extensions() == []
|
|
|
|
- def test_creating_instance_fails(self, iter_entry_points_mock):
|
|
- mock_extension = mock.Mock(spec=ext.Extension)
|
|
- mock_extension.side_effect = Exception
|
|
-
|
|
- mock_entry_point = mock.Mock()
|
|
- mock_entry_point.resolve.return_value = mock_extension
|
|
-
|
|
- iter_entry_points_mock.return_value = [mock_entry_point]
|
|
-
|
|
+ def test_creating_instance_fails(self, mock_entry_point):
|
|
+ mock_entry_point.load.return_value = mock.Mock(side_effect=Exception)
|
|
assert ext.load_extensions() == []
|
|
|
|
- def test_get_config_schema_fails(self, iter_entry_points_mock):
|
|
- mock_entry_point = mock.Mock()
|
|
- mock_entry_point.resolve.return_value = DummyExtension
|
|
-
|
|
- iter_entry_points_mock.return_value = [mock_entry_point]
|
|
-
|
|
+ def test_get_config_schema_fails(self, mock_entry_point):
|
|
with mock.patch.object(DummyExtension, "get_config_schema") as get:
|
|
get.side_effect = Exception
|
|
|
|
assert ext.load_extensions() == []
|
|
get.assert_called_once_with()
|
|
|
|
- def test_get_default_config_fails(self, iter_entry_points_mock):
|
|
- mock_entry_point = mock.Mock()
|
|
- mock_entry_point.resolve.return_value = DummyExtension
|
|
-
|
|
- iter_entry_points_mock.return_value = [mock_entry_point]
|
|
-
|
|
+ def test_get_default_config_fails(self, mock_entry_point):
|
|
with mock.patch.object(DummyExtension, "get_default_config") as get:
|
|
get.side_effect = Exception
|
|
|
|
assert ext.load_extensions() == []
|
|
get.assert_called_once_with()
|
|
|
|
- def test_get_command_fails(self, iter_entry_points_mock):
|
|
- mock_entry_point = mock.Mock()
|
|
- mock_entry_point.resolve.return_value = DummyExtension
|
|
-
|
|
- iter_entry_points_mock.return_value = [mock_entry_point]
|
|
-
|
|
+ def test_get_command_fails(self, mock_entry_point):
|
|
with mock.patch.object(DummyExtension, "get_command") as get:
|
|
get.side_effect = Exception
|
|
|
|
@@ -174,33 +150,39 @@ class TestValidateExtensionData:
|
|
|
|
entry_point = mock.Mock()
|
|
entry_point.name = extension.ext_name
|
|
-
|
|
- schema = extension.get_config_schema()
|
|
- defaults = extension.get_default_config()
|
|
- command = extension.get_command()
|
|
-
|
|
- return ext.ExtensionData(
|
|
- extension, entry_point, schema, defaults, command
|
|
+ yield ext.ExtensionData(
|
|
+ extension,
|
|
+ entry_point,
|
|
+ extension.get_config_schema(),
|
|
+ extension.get_default_config(),
|
|
+ extension.get_command(),
|
|
)
|
|
|
|
+ def test_real(self):
|
|
+ for dist in ext.load_extensions():
|
|
+ assert ext.validate_extension_data(dist)
|
|
+
|
|
def test_name_mismatch(self, ext_data):
|
|
ext_data.entry_point.name = "barfoo"
|
|
assert not ext.validate_extension_data(ext_data)
|
|
|
|
def test_distribution_not_found(self, ext_data):
|
|
- error = pkg_resources.DistributionNotFound
|
|
- ext_data.entry_point.require.side_effect = error
|
|
+ error = metadata.PackageNotFoundError
|
|
+ ext_data.entry_point.load.side_effect = error
|
|
assert not ext.validate_extension_data(ext_data)
|
|
|
|
+ @pytest.mark.skip("Version control missing in metadata")
|
|
def test_version_conflict(self, ext_data):
|
|
- error = pkg_resources.VersionConflict
|
|
+ # error = metadata.VersionConflict
|
|
+ error = metadata.PackageNotFoundError
|
|
ext_data.entry_point.require.side_effect = error
|
|
+ # error = metadata.VersionConflict(
|
|
+ # ext_data.extension, "test_expected"
|
|
+ # )
|
|
assert not ext.validate_extension_data(ext_data)
|
|
|
|
def test_entry_point_require_exception(self, ext_data):
|
|
- ext_data.entry_point.require.side_effect = Exception(
|
|
- "Some extension error"
|
|
- )
|
|
+ ext_data.entry_point.load.side_effect = Exception("Some extension error")
|
|
|
|
# Hope that entry points are well behaved, so exception will bubble.
|
|
with pytest.raises(Exception, match="Some extension error"):
|