Skip to content

feat(plugins): Switch to an importlib.metadata.EntryPoint-based plugin loading #632

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions commitizen/cz/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from __future__ import annotations

import importlib
import pkgutil
import warnings
from typing import Dict, Iterable, Type
from typing import Iterable, Optional

import importlib_metadata as metadata

from commitizen.cz.base import BaseCommitizen
from commitizen.cz.conventional_commits import ConventionalCommitsCz
from commitizen.cz.customize import CustomizeCommitsCz
from commitizen.cz.jira import JiraSmartCz


def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitizen]]:
def discover_plugins(
path: Optional[Iterable[str]] = None,
) -> dict[str, type[BaseCommitizen]]:
"""Discover commitizen plugins on the path

Args:
Expand All @@ -19,21 +22,19 @@ def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitize
Returns:
Dict[str, Type[BaseCommitizen]]: Registry with found plugins
"""
plugins = {}
for _finder, name, _ispkg in pkgutil.iter_modules(path):
try:
if name.startswith("cz_"):
plugins[name] = importlib.import_module(name).discover_this
except AttributeError as e:
warnings.warn(UserWarning(e.args[0]))
continue
return plugins


registry: Dict[str, Type[BaseCommitizen]] = {
"cz_conventional_commits": ConventionalCommitsCz,
"cz_jira": JiraSmartCz,
"cz_customize": CustomizeCommitsCz,
}

registry.update(discover_plugins())
for _, name, _ in pkgutil.iter_modules(path):
if name.startswith("cz_"):
mod = importlib.import_module(name)
if hasattr(mod, "discover_this"):
warnings.warn(
UserWarning(
f"Legacy plugin '{name}' has been ignored: please expose it the 'commitizen.plugin' entrypoint"
)
)

return {
ep.name: ep.load() for ep in metadata.entry_points(group="commitizen.plugin")
}


registry: dict[str, type[BaseCommitizen]] = discover_plugins()
70 changes: 62 additions & 8 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ The basic steps are:

1. Inheriting from `BaseCommitizen`
2. Give a name to your rules.
3. Expose the class at the end of your file assigning it to `discover_this`
4. Create a python package starting with `cz_` using `setup.py`, `poetry`, etc
3. Create a python package using `setup.py`, `poetry`, etc
4. Expose the class as a `commitizen.plugin` entrypoint

Check an [example](convcomms) on how to configure `BaseCommitizen`.

Expand All @@ -205,7 +205,7 @@ See [commitizen_cz_template](https://github.com/commitizen-tools/commitizen_cz_t

### Custom commit rules

Create a file starting with `cz_`, for example `cz_jira.py`. This prefix is used to detect the plug-in. Same method [flask uses]
Create a Python module, for example `cz_jira.py`.

Inherit from `BaseCommitizen`, and you must define `questions` and `message`. The others are optional.

Expand Down Expand Up @@ -257,8 +257,6 @@ class JiraCz(BaseCommitizen):
"""
return 'We use this because is useful'


discover_this = JiraCz # used by the plug-in system
```

The next file required is `setup.py` modified from flask version.
Expand All @@ -272,7 +270,12 @@ setup(
py_modules=['cz_jira'],
license='MIT',
long_description='this is a long description',
install_requires=['commitizen']
install_requires=['commitizen'],
entry_points = {
'commitizen.plugin': [
'cz_jira = cz_jira:JiraCz'
]
}
)
```

Expand All @@ -287,8 +290,6 @@ doing `pip install .`

If you feel like it should be part of this repo, create a PR.

[flask uses]: http://flask.pocoo.org/docs/0.12/extensiondev/

### Custom bump rules

You need to define 2 parameters inside your custom `BaseCommitizen`.
Expand Down Expand Up @@ -381,3 +382,56 @@ from commitizen.cz.exception import CzException
class NoSubjectProvidedException(CzException):
...
```

### Migrating from legacy plugin format

Commitizen migrated to a new plugin format relying on `importlib.metadata.EntryPoint`.
Migration should be straight-forward for legacy plugins:

- Remove the `discover_this` line from you plugin module
- Expose the plugin class under as a `commitizen.plugin` entrypoint.

The name of the plugin is now determined by the name of the entrypoint.

#### Example

If you were having a `CzPlugin` class in a `cz_plugin.py` module like this:

```python
from commitizen.cz.base import BaseCommitizen

class PluginCz(BaseCommitizen):
...

discover_this = PluginCz
```

Then remove the `discover_this` line:

```python
from commitizen.cz.base import BaseCommitizen

class PluginCz(BaseCommitizen):
...
```

and expose the class as entrypoint in you setuptools:

```python
from setuptools import setup

setup(
name='MyPlugin',
version='0.1.0',
py_modules=['cz_plugin'],
...
entry_points = {
'commitizen.plugin': [
'plugin = cz_plugin:PluginCz'
]
}
...
)
```

Then your plugin will be available under the name `plugin`.
Loading