Commit c33e3c68 by Arun Babu Neelicattu

replace entrypoints use with importlib.metadata

parent c1475a0b
......@@ -190,14 +190,6 @@ paramiko = ["paramiko"]
pgp = ["gpg"]
[[package]]
name = "entrypoints"
version = "0.4"
description = "Discover and load entry points from installed packages."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "execnet"
version = "1.9.0"
description = "execnet: rapid multi-Python deployment"
......@@ -787,14 +779,6 @@ optional = false
python-versions = ">=3.6"
[[package]]
name = "types-entrypoints"
version = "0.3.7"
description = "Typing stubs for entrypoints"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "types-html5lib"
version = "1.1.7"
description = "Typing stubs for html5lib"
......@@ -903,7 +887,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "0eeafb89a4be53861db33f20d414e84bcf87c4e90a2c7b70ce66e40846d99e02"
content-hash = "4c18d27808d5a637e9591ff8444ef9435ffa1fa1df5cdffd5e4fd1c5780547a4"
[metadata.files]
atomicwrites = [
......@@ -1096,10 +1080,6 @@ dulwich = [
{file = "dulwich-0.20.42-cp39-cp39-win_amd64.whl", hash = "sha256:23922557bc487597ff1a0efc56e4436e275a82837b55e5f733e3e05c873e92b1"},
{file = "dulwich-0.20.42.tar.gz", hash = "sha256:72ba3b60ae6a554d1332b3b40a345febe16ec469cf6014bb443b719902e33ef0"},
]
entrypoints = [
{file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"},
{file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"},
]
execnet = [
{file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"},
{file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
......@@ -1454,10 +1434,6 @@ typed-ast = [
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
types-entrypoints = [
{file = "types-entrypoints-0.3.7.tar.gz", hash = "sha256:3d002ba32fc2c7fb44f65228fb912969a495cb4aa4ae63e05152f4304bc9bba2"},
{file = "types_entrypoints-0.3.7-py3-none-any.whl", hash = "sha256:06aea9245f25b7b335ef0c3e8e4cc422b1f7c2cc47ae11f9a000a0a15d8ea6aa"},
]
types-html5lib = [
{file = "types-html5lib-1.1.7.tar.gz", hash = "sha256:fdb307102ceea52adf52aea0e10255d142ee29d7f169014144a730707a92066a"},
{file = "types_html5lib-1.1.7-py3-none-any.whl", hash = "sha256:4bc5a1de03b9a697b7cc04b0d03eeed5d36c0073165dd9dc54f2f43d0f23b31f"},
......
......@@ -50,7 +50,6 @@ cachecontrol = { version = "^0.12.9", extras = ["filecache"] }
cachy = "^0.3.0"
cleo = "^1.0.0a5"
crashtest = "^0.3.0"
entrypoints = "^0.4"
html5lib = "^1.0"
importlib-metadata = { version = "^4.4", python = "<3.10" }
# keyring uses calver, so version is unclamped
......@@ -85,7 +84,6 @@ typing-extensions = { version = "^4.0.0", python = "<3.8" }
zipp = { version = "^3.4", python = "<3.8" }
flatdict = "^4.0.1"
mypy = ">=0.960"
types-entrypoints = ">=0.3.7"
types-html5lib = ">=1.1.7"
types-jsonschema = ">=4.4.4"
types-requests = ">=2.27.11"
......@@ -140,8 +138,10 @@ enable_error_code = [
# warning.
[[tool.mypy.overrides]]
module = [
'poetry.console.commands.self.show.plugins',
'poetry.installation.executor',
'poetry.mixology.version_solver',
'poetry.plugins.plugin_manager',
'poetry.repositories.installed_repository',
'poetry.utils.env',
]
......
......@@ -8,15 +8,32 @@ from poetry.console.commands.self.self_command import SelfCommand
if TYPE_CHECKING:
from entrypoints import EntryPoint
from poetry.core.packages.package import Package
from poetry.utils._compat import metadata
@dataclasses.dataclass
class PluginPackage:
package: Package
plugins: list[EntryPoint] = dataclasses.field(default_factory=list)
application_plugins: list[EntryPoint] = dataclasses.field(default_factory=list)
plugins: list[metadata.EntryPoint] = dataclasses.field(default_factory=list)
application_plugins: list[metadata.EntryPoint] = dataclasses.field(
default_factory=list
)
def append(self, entry_point: metadata.EntryPoint) -> None:
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin import Plugin
group = entry_point.group # type: ignore[attr-defined]
if group == ApplicationPlugin.group:
self.application_plugins.append(entry_point)
elif group == Plugin.group:
self.plugins.append(entry_point)
else:
name = entry_point.name # type: ignore[attr-defined]
raise ValueError(f"Unknown plugin group ({group}) for {name}")
class SelfShowPluginsCommand(SelfCommand):
......@@ -43,9 +60,6 @@ commands respectively.
plugins: dict[str, PluginPackage] = {}
system_env = EnvManager.get_system_env(naive=True)
entry_points = PluginManager(ApplicationPlugin.group).get_plugin_entry_points(
env=system_env
) + PluginManager(Plugin.group).get_plugin_entry_points(env=system_env)
installed_repository = InstalledRepository.load(
system_env, with_dependencies=True
)
......@@ -54,21 +68,20 @@ commands respectively.
pkg.name: pkg for pkg in installed_repository.packages
}
for entry_point in entry_points:
plugin = entry_point.load()
for group in [ApplicationPlugin.group, Plugin.group]:
for entry_point in PluginManager(group).get_plugin_entry_points(
env=system_env
):
assert entry_point.dist is not None
assert entry_point.distro is not None
package = packages_by_name[canonicalize_name(entry_point.distro.name)]
package = packages_by_name[canonicalize_name(entry_point.dist.name)]
name = package.pretty_name
info = plugins.get(name) or PluginPackage(package=package)
name = package.pretty_name
if issubclass(plugin, ApplicationPlugin):
info.application_plugins.append(entry_point)
else:
info.plugins.append(entry_point)
info = plugins.get(name) or PluginPackage(package=package)
info.append(entry_point)
plugins[name] = info
plugins[name] = info
for name, info in plugins.items():
package = info.package
......
from __future__ import annotations
import logging
import sys
from typing import TYPE_CHECKING
import entrypoints
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin import Plugin
from poetry.utils._compat import metadata
if TYPE_CHECKING:
......@@ -36,16 +34,29 @@ class PluginManager:
plugin_entrypoints = self.get_plugin_entry_points(env=env)
for entrypoint in plugin_entrypoints:
self._load_plugin_entrypoint(entrypoint)
for ep in plugin_entrypoints:
self._load_plugin_entry_point(ep)
@staticmethod
def _is_plugin_candidate(ep: metadata.EntryPoint, env: Env | None = None) -> bool:
"""
Helper method to check if given entry point is a valid as a plugin candidate.
When an environment is specified, the entry point's associated distribution
should be installed, and discoverable in the given environment.
"""
return env is None or (
ep.dist is not None
and env.site_packages.find_distribution(ep.dist.name) is not None
)
def get_plugin_entry_points(
self, env: Env | None = None
) -> list[entrypoints.EntryPoint]:
entry_points: list[entrypoints.EntryPoint] = entrypoints.get_group_all(
self._group, path=env.sys_path if env else sys.path
)
return entry_points
) -> list[metadata.EntryPoint]:
return [
ep
for ep in metadata.entry_points(group=self._group)
if self._is_plugin_candidate(ep, env)
]
def add_plugin(self, plugin: Plugin) -> None:
if not isinstance(plugin, (Plugin, ApplicationPlugin)):
......@@ -59,10 +70,10 @@ class PluginManager:
for plugin in self._plugins:
plugin.activate(*args, **kwargs)
def _load_plugin_entrypoint(self, entrypoint: entrypoints.EntryPoint) -> None:
logger.debug(f"Loading the {entrypoint.name} plugin")
def _load_plugin_entry_point(self, ep: metadata.EntryPoint) -> None:
logger.debug(f"Loading the {ep.name} plugin") # type: ignore[attr-defined]
plugin = entrypoint.load()
plugin = ep.load() # type: ignore[no-untyped-call]
if not issubclass(plugin, (Plugin, ApplicationPlugin)):
raise ValueError(
......
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
import pytest
from entrypoints import Distribution
from entrypoints import EntryPoint as _EntryPoint
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.factory import Factory
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin import Plugin
from poetry.utils._compat import metadata
if TYPE_CHECKING:
from os import PathLike
from cleo.io.io import IO
from cleo.testers.command_tester import CommandTester
from pytest_mock import MockerFixture
from poetry.plugins.base_plugin import BasePlugin
from poetry.poetry import Poetry
from poetry.repositories import Repository
from poetry.utils.env import Env
from tests.helpers import PoetryTestApplication
from tests.types import CommandTesterFactory
class EntryPoint(_EntryPoint):
class DoNothingPlugin(Plugin):
def activate(self, poetry: Poetry, io: IO) -> None:
pass
class EntryPoint(metadata.EntryPoint):
def load(self) -> type[BasePlugin]:
if "ApplicationPlugin" in self.object_name:
if self.group == ApplicationPlugin.group:
return ApplicationPlugin
return Plugin
return DoNothingPlugin
@pytest.fixture()
......@@ -37,49 +49,112 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester:
@pytest.fixture()
def plugin_package() -> Package:
return Package("poetry-plugin", "1.2.3")
def plugin_package_requires_dist() -> list[str]:
return []
@pytest.fixture()
def plugin_distro(plugin_package: Package) -> Distribution:
return Distribution(plugin_package.name, plugin_package.version.to_string())
def plugin_package(plugin_package_requires_dist: list[str]) -> Package:
package = Package("poetry-plugin", "1.2.3")
for requirement in plugin_package_requires_dist:
package.add_dependency(Dependency.create_from_pep_508(requirement))
@pytest.mark.parametrize("entrypoint_name", ["poetry-plugin", "not-package-name"])
def test_show_displays_installed_plugins(
app: PoetryTestApplication,
tester: CommandTester,
installed: Repository,
mocker: MockerFixture,
plugin_package: Package,
plugin_distro: Distribution,
entrypoint_name: str,
):
mocker.patch(
"entrypoints.get_group_all",
side_effect=[
[
EntryPoint(
entrypoint_name,
"poetry_plugin.plugins:ApplicationPlugin",
"FirstApplicationPlugin",
distro=plugin_distro,
)
],
[
EntryPoint(
entrypoint_name,
"poetry_plugin.plugins:Plugin",
"FirstPlugin",
distro=plugin_distro,
return package
@pytest.fixture()
def plugin_distro(plugin_package: Package, tmp_dir: str) -> metadata.Distribution:
class MockDistribution(metadata.Distribution):
def read_text(self, filename: str) -> str | None:
if filename == "METADATA":
return "\n".join(
[
f"Name: {plugin_package.name}",
f"Version: {plugin_package.version}",
*[
f"Requires-Dist: {dep.to_pep_508()}"
for dep in plugin_package.requires
],
]
)
],
],
)
return None
def locate_file(self, path: PathLike[str]) -> PathLike[str]:
return Path(tmp_dir, path)
return MockDistribution()
@pytest.fixture
def entry_point_name() -> str:
return "poetry-plugin"
@pytest.fixture
def entry_point_values_by_group() -> dict[str, list[str]]:
return {}
@pytest.fixture
def entry_points(
entry_point_name: str,
entry_point_values_by_group: dict[str, list[str]],
plugin_distro: metadata.Distribution,
) -> Callable[[...], list[metadata.EntryPoint]]:
by_group = {
key: [
EntryPoint(name=entry_point_name, group=key, value=value)._for(
plugin_distro
)
for value in values
]
for key, values in entry_point_values_by_group.items()
}
def _entry_points(**params: Any) -> list[metadata.EntryPoint]:
group = params.get("group")
if group not in by_group:
return []
return by_group.get(group)
return _entry_points
@pytest.fixture(autouse=True)
def mock_metadata_entry_points(
plugin_package: Package,
plugin_distro: metadata.Distribution,
installed: Repository,
mocker: MockerFixture,
tmp_venv: Env,
entry_points: Callable[[...], metadata.EntryPoint],
) -> None:
installed.add_package(plugin_package)
mocker.patch.object(
tmp_venv.site_packages, "find_distribution", return_value=plugin_distro
)
mocker.patch.object(metadata, "entry_points", entry_points)
@pytest.mark.parametrize("entry_point_name", ["poetry-plugin", "not-package-name"])
@pytest.mark.parametrize(
"entry_point_values_by_group",
[
{
ApplicationPlugin.group: ["FirstApplicationPlugin"],
Plugin.group: ["FirstPlugin"],
}
],
)
def test_show_displays_installed_plugins(
app: PoetryTestApplication,
tester: CommandTester,
):
tester.execute("")
expected = """
......@@ -90,50 +165,22 @@ def test_show_displays_installed_plugins(
assert tester.io.fetch_output() == expected
@pytest.mark.parametrize(
"entry_point_values_by_group",
[
{
ApplicationPlugin.group: [
"FirstApplicationPlugin",
"SecondApplicationPlugin",
],
Plugin.group: ["FirstPlugin", "SecondPlugin"],
}
],
)
def test_show_displays_installed_plugins_with_multiple_plugins(
app: PoetryTestApplication,
tester: CommandTester,
installed: Repository,
mocker: MockerFixture,
plugin_package: Package,
plugin_distro: Distribution,
):
mocker.patch(
"entrypoints.get_group_all",
side_effect=[
[
EntryPoint(
"poetry-plugin",
"poetry_plugin.plugins:ApplicationPlugin",
"FirstApplicationPlugin",
distro=plugin_distro,
),
EntryPoint(
"poetry-plugin",
"poetry_plugin.plugins:ApplicationPlugin",
"SecondApplicationPlugin",
distro=plugin_distro,
),
],
[
EntryPoint(
"poetry-plugin",
"poetry_plugin.plugins:Plugin",
"FirstPlugin",
distro=plugin_distro,
),
EntryPoint(
"poetry-plugin",
"poetry_plugin.plugins:Plugin",
"SecondPlugin",
distro=plugin_distro,
),
],
],
)
installed.add_package(plugin_package)
tester.execute("")
expected = """
......@@ -144,40 +191,22 @@ def test_show_displays_installed_plugins_with_multiple_plugins(
assert tester.io.fetch_output() == expected
@pytest.mark.parametrize(
"plugin_package_requires_dist", [["foo (>=1.2.3)", "bar (<4.5.6)"]]
)
@pytest.mark.parametrize(
"entry_point_values_by_group",
[
{
ApplicationPlugin.group: ["FirstApplicationPlugin"],
Plugin.group: ["FirstPlugin"],
}
],
)
def test_show_displays_installed_plugins_with_dependencies(
app: PoetryTestApplication,
tester: CommandTester,
installed: Repository,
mocker: MockerFixture,
plugin_package: Package,
plugin_distro: Distribution,
):
mocker.patch(
"entrypoints.get_group_all",
side_effect=[
[
EntryPoint(
"poetry-plugin",
"poetry_plugin.plugins:ApplicationPlugin",
"FirstApplicationPlugin",
distro=plugin_distro,
)
],
[
EntryPoint(
"poetry-plugin",
"poetry_plugin.plugins:Plugin",
"FirstPlugin",
distro=plugin_distro,
)
],
],
)
plugin_package.add_dependency(Factory.create_dependency("foo", ">=1.2.3"))
plugin_package.add_dependency(Factory.create_dependency("bar", "<4.5.6"))
installed.add_package(plugin_package)
tester.execute("")
expected = """
......
......@@ -7,11 +7,11 @@ from typing import TYPE_CHECKING
import pytest
from cleo.testers.application_tester import ApplicationTester
from entrypoints import EntryPoint
from poetry.console.application import Application
from poetry.console.commands.command import Command
from poetry.plugins.application_plugin import ApplicationPlugin
from tests.helpers import mock_metadata_entry_points
if TYPE_CHECKING:
......@@ -33,16 +33,12 @@ class AddCommandPlugin(ApplicationPlugin):
commands = [FooCommand]
def test_application_with_plugins(mocker: MockerFixture):
mocker.patch(
"entrypoints.get_group_all",
return_value=[
EntryPoint(
"my-plugin", "tests.console.test_application", "AddCommandPlugin"
)
],
)
@pytest.fixture
def with_add_command_plugin(mocker: MockerFixture) -> None:
mock_metadata_entry_points(mocker, AddCommandPlugin)
def test_application_with_plugins(with_add_command_plugin: None):
app = Application()
tester = ApplicationTester(app)
......@@ -52,16 +48,7 @@ def test_application_with_plugins(mocker: MockerFixture):
assert tester.status_code == 0
def test_application_with_plugins_disabled(mocker: MockerFixture):
mocker.patch(
"entrypoints.get_group_all",
return_value=[
EntryPoint(
"my-plugin", "tests.console.test_application", "AddCommandPlugin"
)
],
)
def test_application_with_plugins_disabled(with_add_command_plugin: None):
app = Application()
tester = ApplicationTester(app)
......@@ -71,16 +58,7 @@ def test_application_with_plugins_disabled(mocker: MockerFixture):
assert tester.status_code == 0
def test_application_execute_plugin_command(mocker: MockerFixture):
mocker.patch(
"entrypoints.get_group_all",
return_value=[
EntryPoint(
"my-plugin", "tests.console.test_application", "AddCommandPlugin"
)
],
)
def test_application_execute_plugin_command(with_add_command_plugin: None):
app = Application()
tester = ApplicationTester(app)
......@@ -91,17 +69,8 @@ def test_application_execute_plugin_command(mocker: MockerFixture):
def test_application_execute_plugin_command_with_plugins_disabled(
mocker: MockerFixture,
with_add_command_plugin: None,
):
mocker.patch(
"entrypoints.get_group_all",
return_value=[
EntryPoint(
"my-plugin", "tests.console.test_application", "AddCommandPlugin"
)
],
)
app = Application()
tester = ApplicationTester(app)
......
......@@ -25,6 +25,7 @@ from poetry.installation.executor import Executor
from poetry.packages import Locker
from poetry.repositories import Repository
from poetry.repositories.exceptions import PackageNotFound
from poetry.utils._compat import metadata
if TYPE_CHECKING:
......@@ -32,6 +33,7 @@ if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.semver.version import Version
from pytest_mock import MockerFixture
from tomlkit.toml_document import TOMLDocument
from poetry.installation.operations.operation import Operation
......@@ -251,3 +253,31 @@ def isolated_environment(
os.environ.clear()
os.environ.update(original_environ)
def make_entry_point_from_plugin(
name: str, cls: type[Any], dist: metadata.Distribution | None = None
) -> metadata.EntryPoint:
ep = metadata.EntryPoint(
name=name,
group=getattr(cls, "group", None),
value=f"{cls.__module__}:{cls.__name__}",
)
if dist:
return ep._for(dist)
return ep
def mock_metadata_entry_points(
mocker: MockerFixture,
cls: type[Any],
name: str = "my-plugin",
dist: metadata.Distribution | None = None,
) -> None:
mocker.patch.object(
metadata,
"entry_points",
return_value=[make_entry_point_from_plugin(name, cls, dist)],
)
......@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING
import pytest
from cleo.io.buffered_io import BufferedIO
from entrypoints import EntryPoint
from poetry.core.packages.project_package import ProjectPackage
from poetry.packages.locker import Locker
......@@ -15,6 +14,7 @@ from poetry.plugins import Plugin
from poetry.plugins.plugin_manager import PluginManager
from poetry.poetry import Poetry
from tests.compat import Protocol
from tests.helpers import mock_metadata_entry_points
if TYPE_CHECKING:
......@@ -81,17 +81,9 @@ def test_load_plugins_and_activate(
manager_factory: ManagerFactory,
poetry: Poetry,
io: BufferedIO,
mocker: MockerFixture,
with_my_plugin: None,
):
manager = manager_factory()
mocker.patch(
"entrypoints.get_group_all",
return_value=[
EntryPoint("my-plugin", "tests.plugins.test_plugin_manager", "MyPlugin")
],
)
manager.load_plugins()
manager.activate(poetry, io)
......@@ -99,23 +91,24 @@ def test_load_plugins_and_activate(
assert io.fetch_output() == "Setting readmes\n"
@pytest.fixture
def with_my_plugin(mocker: MockerFixture) -> None:
mock_metadata_entry_points(mocker, MyPlugin)
@pytest.fixture
def with_invalid_plugin(mocker: MockerFixture) -> None:
mock_metadata_entry_points(mocker, InvalidPlugin)
def test_load_plugins_with_invalid_plugin(
manager_factory: ManagerFactory,
poetry: Poetry,
io: BufferedIO,
mocker: MockerFixture,
with_invalid_plugin: None,
):
manager = manager_factory()
mocker.patch(
"entrypoints.get_group_all",
return_value=[
EntryPoint(
"my-plugin", "tests.plugins.test_plugin_manager", "InvalidPlugin"
)
],
)
with pytest.raises(ValueError):
manager.load_plugins()
......@@ -124,15 +117,8 @@ def test_load_plugins_with_plugins_disabled(
no_plugin_manager: PluginManager,
poetry: Poetry,
io: BufferedIO,
mocker: MockerFixture,
with_my_plugin: None,
):
mocker.patch(
"entrypoints.get_group_all",
return_value=[
EntryPoint("my-plugin", "tests.plugins.test_plugin_manager", "MyPlugin")
],
)
no_plugin_manager.load_plugins()
assert poetry.package.version.text == "1.2.3"
......
......@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING
import pytest
from deepdiff import DeepDiff
from entrypoints import EntryPoint
from poetry.core.semver.helpers import parse_constraint
from poetry.core.toml.file import TOMLFile
......@@ -14,6 +13,7 @@ from poetry.factory import Factory
from poetry.plugins.plugin import Plugin
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pypi_repository import PyPiRepository
from tests.helpers import mock_metadata_entry_points
if TYPE_CHECKING:
......@@ -336,10 +336,7 @@ def test_create_poetry_with_local_config(fixture_dir: FixtureDirGetter):
def test_create_poetry_with_plugins(mocker: MockerFixture):
mocker.patch(
"entrypoints.get_group_all",
return_value=[EntryPoint("my-plugin", "tests.test_factory", "MyPlugin")],
)
mock_metadata_entry_points(mocker, MyPlugin)
poetry = Factory().create_poetry(fixtures_dir / "sample_project")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment