Skip to content

Commit 8276b11

Browse files
committed
feat(plugins): Switch to an importlib.metadata.EntryPoint-based plugin loading
Plugins are now loaded using the `commitizen.plugin` entrypoint while legacy plugin are not loaded anymore but a warning is raised when one is seen. Fixes commitizen-tools#495 BREAKING CHANGE: Plugins are now exposed as `commitizen.plugin` entrypoints
1 parent 838878e commit 8276b11

File tree

4 files changed

+86
-34
lines changed

4 files changed

+86
-34
lines changed

commitizen/_compat.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import sys
2+
3+
if sys.version_info < (3, 10):
4+
import importlib_metadata as metadata
5+
else:
6+
from importlib import metadata

commitizen/cz/__init__.py

+17-21
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
import warnings
44
from typing import Dict, Iterable, Type
55

6+
from commitizen._compat import metadata
67
from commitizen.cz.base import BaseCommitizen
7-
from commitizen.cz.conventional_commits import ConventionalCommitsCz
8-
from commitizen.cz.customize import CustomizeCommitsCz
9-
from commitizen.cz.jira import JiraSmartCz
108

119

1210
def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitizen]]:
@@ -19,21 +17,19 @@ def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitize
1917
Returns:
2018
Dict[str, Type[BaseCommitizen]]: Registry with found plugins
2119
"""
22-
plugins = {}
23-
for _finder, name, _ispkg in pkgutil.iter_modules(path):
24-
try:
25-
if name.startswith("cz_"):
26-
plugins[name] = importlib.import_module(name).discover_this
27-
except AttributeError as e:
28-
warnings.warn(UserWarning(e.args[0]))
29-
continue
30-
return plugins
31-
32-
33-
registry: Dict[str, Type[BaseCommitizen]] = {
34-
"cz_conventional_commits": ConventionalCommitsCz,
35-
"cz_jira": JiraSmartCz,
36-
"cz_customize": CustomizeCommitsCz,
37-
}
38-
39-
registry.update(discover_plugins())
20+
for _, name, _ in pkgutil.iter_modules(path):
21+
if name.startswith("cz_"):
22+
mod = importlib.import_module(name)
23+
if hasattr(mod, "discover_this"):
24+
warnings.warn(
25+
UserWarning(
26+
f"Legacy plugin '{name}' has been ignored: please expose it the 'commitizen.plugin' entrypoint"
27+
)
28+
)
29+
30+
return {
31+
ep.name: ep.load() for ep in metadata.entry_points(group="commitizen.plugin")
32+
}
33+
34+
35+
registry: Dict[str, Type[BaseCommitizen]] = discover_plugins()

pyproject.toml

+6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ pyyaml = ">=3.08"
7171
argcomplete = ">=1.12.1,<2.1"
7272
typing-extensions = "^4.0.1"
7373
charset-normalizer = "^2.1.0"
74+
importlib_metadata = { version = ">=3.6", python = "<3.10" }
7475

7576
[tool.poetry.dev-dependencies]
7677
ipython = "^7.2"
@@ -101,6 +102,11 @@ pytest-xdist = "^2.5.0"
101102
cz = "commitizen.cli:main"
102103
git-cz = "commitizen.cli:main"
103104

105+
[tool.poetry.plugins."commitizen.plugin"]
106+
cz_conventional_commits = "commitizen.cz.conventional_commits:ConventionalCommitsCz"
107+
cz_jira = "commitizen.cz.jira:JiraSmartCz"
108+
cz_customize = "commitizen.cz.customize:CustomizeCommitsCz"
109+
104110
[tool.isort]
105111
profile = "black"
106112
known_first_party = ["commitizen", "tests"]

tests/test_factory.py

+57-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import sys
2+
from textwrap import dedent
23

34
import pytest
45

56
from commitizen import BaseCommitizen, defaults, factory
7+
from commitizen._compat import metadata
68
from commitizen.config import BaseConfig
79
from commitizen.cz import discover_plugins
10+
from commitizen.cz.conventional_commits import ConventionalCommitsCz
11+
from commitizen.cz.customize import CustomizeCommitsCz
12+
from commitizen.cz.jira import JiraSmartCz
813
from commitizen.exceptions import NoCommitizenFoundException
914

1015

@@ -24,17 +29,19 @@ def test_factory_fails():
2429
assert "The committer has not been found in the system." in str(excinfo)
2530

2631

27-
@pytest.mark.parametrize(
28-
"module_content, plugin_name, expected_plugins",
29-
[
30-
("", "cz_no_plugin", {}),
31-
],
32-
)
33-
def test_discover_plugins(module_content, plugin_name, expected_plugins, tmp_path):
34-
no_plugin_folder = tmp_path / plugin_name
35-
no_plugin_folder.mkdir()
36-
init_file = no_plugin_folder / "__init__.py"
37-
init_file.write_text(module_content)
32+
def test_discover_plugins(tmp_path):
33+
legacy_plugin_folder = tmp_path / "cz_legacy"
34+
legacy_plugin_folder.mkdir()
35+
init_file = legacy_plugin_folder / "__init__.py"
36+
init_file.write_text(
37+
dedent(
38+
"""\
39+
class Plugin: pass
40+
41+
discover_this = Plugin
42+
"""
43+
)
44+
)
3845

3946
sys.path.append(tmp_path.as_posix())
4047
with pytest.warns(UserWarning) as record:
@@ -43,6 +50,43 @@ def test_discover_plugins(module_content, plugin_name, expected_plugins, tmp_pat
4350

4451
assert (
4552
record[0].message.args[0]
46-
== f"module '{plugin_name}' has no attribute 'discover_this'"
53+
== "Legacy plugin 'cz_legacy' has been ignored: please expose it the 'commitizen.plugin' entrypoint"
54+
)
55+
assert "cz_legacy" not in discovered_plugins
56+
57+
58+
def test_discover_external_plugin(mocker):
59+
class Plugin:
60+
pass
61+
62+
class OtherPlugin:
63+
pass
64+
65+
ep_plugin = metadata.EntryPoint("test", "some:Plugin", "commitizen.plugin")
66+
ep_other_plugin = metadata.EntryPoint(
67+
"not-selected", "some:OtherPlugin", "commitizen.not_a_plugin"
4768
)
48-
assert expected_plugins == discovered_plugins
69+
eps = [ep_plugin, ep_other_plugin]
70+
71+
mocker.patch.object(ep_plugin, "load", return_value=Plugin)
72+
mocker.patch.object(ep_other_plugin, "load", return_value=OtherPlugin)
73+
74+
def mock_entrypoints(**kwargs):
75+
group = kwargs.get("group")
76+
return metadata.EntryPoints(ep for ep in eps if ep.group == group)
77+
78+
mocker.patch.object(metadata, "entry_points", side_effect=mock_entrypoints)
79+
80+
assert discover_plugins() == {"test": Plugin}
81+
82+
83+
def test_discover_internal_plugins():
84+
expected = {
85+
"cz_conventional_commits": ConventionalCommitsCz,
86+
"cz_jira": JiraSmartCz,
87+
"cz_customize": CustomizeCommitsCz,
88+
}
89+
90+
discovered = discover_plugins()
91+
92+
assert set(expected.items()).issubset(set(discovered.items()))

0 commit comments

Comments
 (0)