Skip to content

Commit 791a08b

Browse files
authored
Feature: plugins detection using entrypoints (#1590)
* feat: add entrypoint plugin lookup * feat: add entrypoint plugins * feat: add entrypoint plugin for backends * fix: ensure cli uses entrypoints to list backend plugins * refactor: use importlib instead of pkg_resources * fix: ensure plugin lookup works for py37 * test: basic entrypoint check * docs: refactor installing plugins section * docs: add info on using entrypoints * docs: add info to CHANGES
1 parent 3f5abef commit 791a08b

File tree

9 files changed

+102
-17
lines changed

9 files changed

+102
-17
lines changed

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
v9.9.9 (unreleased)
22
-------------------
33

4+
features:
5+
6+
- core/plugins: detect plugins using entrypoints (#1590)
7+
48
fixes:
59

610
- docs: add unreleased section (#1576)

docs/user_guide/administration.rst

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,20 @@ If you just wish to know more about a specific command you can issue::
4040
Installing plugins
4141
------------------
4242

43-
Errbot plugins are typically published to and installed from `GitHub <http://github.com/>`_.
44-
We periodically crawl GitHub for errbot plugin repositories and `publish the results <https://github.com/errbotio/errbot/wiki>`_ for people to browse.
43+
Errbot plugins can be installed via these methods
44+
45+
* `!repos install` bot commnand
46+
* Cloning a `GitHub <http://github.com/>`_ repository
47+
* Extracting a tar/zip file
48+
* Using pip
49+
50+
51+
Using a bot command
52+
^^^^^^^^^^^^^^^^^^^
53+
54+
Plugins installed via the :code:`!repos` command are managed by errbot itself and stored inside the `BOT_DATA_DIR` you set in `config.py`.
55+
56+
We periodically crawl GitHub for errbot plugin repositories and `publish the results <https://errbot.io/repos.json>`_ for people to browse.
4557

4658
You can have your bot display the same list of repos by issuing::
4759

@@ -72,6 +84,32 @@ This can be done with::
7284
!repos update all
7385

7486

87+
Cloning a repository or tar/zip install
88+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
89+
90+
Using a git repository or tar/zip file to install plugins is setting up your plugins to be managed manually.
91+
92+
Plugins installed from cloning a repository need to be placed inside the `BOT_EXTRA_PLUGIN_DIR` path specified in the `config.py` file.
93+
94+
Assuming `BOT_EXTRA_PLUGIN_DIR` is set to `/opt/plugins`::
95+
96+
$ git clone https://github.com/errbotio/err-helloworld /opt/plugins/err-helloworld
97+
$ tar -zxvf err-helloworld.tar.gz -C /opt/plugins/
98+
99+
.. note::
100+
If a repo is cloned and the git remote information is present, updating the plugin may be possible via `!repos update`
101+
102+
103+
Using pip for plugins
104+
^^^^^^^^^^^^^^^^^^^^^
105+
106+
Plugins published to to pypi.org can be installed using pip.::
107+
108+
$ pip install errbot-plugin-helloworld
109+
110+
As part of the packaging configuration for the plugin, it should install all necessary dependencies for the plugin to work.
111+
112+
75113
Dependencies
76114
^^^^^^^^^^^^
77115

@@ -83,15 +121,6 @@ If you have installed Errbot in a virtualenv, this will run the equivalent of :c
83121
If no virtualenv is detected, the equivalent of :code:`pip install --user -r requirements.txt` is used to ensure the package(s) is/are only installed for the user running Err.
84122

85123

86-
Extra plugin directory
87-
^^^^^^^^^^^^^^^^^^^^^^
88-
89-
Plugins installed via the :code:`!repos` command are managed by errbot itself and stored inside the `BOT_DATA_DIR` you set in `config.py`.
90-
If you want to manage your plugins manually for any reason then errbot allows you to load additional plugins from a directory you specify.
91-
You can do so by specifying the setting `BOT_EXTRA_PLUGIN_DIR` in your `config.py` file.
92-
See the :download:`config-template.py` file for more details.
93-
94-
95124
.. _disabling_plugins:
96125

97126
Disabling plugins

docs/user_guide/plugin_development/basics.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,33 @@ the function `invert_string()`, the `helloworld` plugin can import it and use it
197197
"""Say hello to the world"""
198198
return invert_string("Hello, world!")
199199
200+
Packaging
201+
---------
202+
203+
A plugin can be packaged and distributed through pypi.org. The errbot plugin system uses entrypoints in setuptools to find available plugins.
204+
205+
The two entrypoint avialable are
206+
207+
* `errbot.plugins` - normal plugin and flows
208+
* `errbot.backend_plugins` - backend plugins for collaboration providers
209+
210+
To get this setup, add this block of code to `setup.py`.
211+
212+
.. code-block:: python
213+
214+
entry_points = {
215+
"errbot.plugins": [
216+
"helloworld = helloWorld:HelloWorld",
217+
]
218+
}
219+
220+
Optionally, you may need to include a `MANIFEST.in` to include files of the repo
221+
222+
.. code-block:: python
223+
224+
include *.py *.plug
225+
226+
200227
Wrapping up
201228
-----------
202229

errbot/backend_plugin_manager.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from errbot.plugin_info import PluginInfo
77

8-
from .utils import collect_roots
8+
from .utils import collect_roots, entry_point_plugins
99

1010
log = logging.getLogger(__name__)
1111

@@ -44,7 +44,8 @@ def __init__(
4444
self._base_class = base_class
4545

4646
self.plugin_info = None
47-
all_plugins_paths = collect_roots((base_search_dir, extra_search_dirs))
47+
ep = entry_point_plugins(group="errbot.backend_plugins")
48+
all_plugins_paths = collect_roots((base_search_dir, extra_search_dirs, ep))
4849

4950
for potential_plugin in enumerate_backend_plugins(all_plugins_paths):
5051
if potential_plugin.name == plugin_name:

errbot/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from errbot.bootstrap import CORE_BACKENDS
2828
from errbot.logs import root_logger
2929
from errbot.plugin_wizard import new_plugin_wizard
30-
from errbot.utils import collect_roots
30+
from errbot.utils import collect_roots, entry_point_plugins
3131
from errbot.version import VERSION
3232

3333
log = logging.getLogger(__name__)
@@ -281,6 +281,8 @@ def main() -> None:
281281
extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", [])
282282
if isinstance(extra_backend, str):
283283
extra_backend = [extra_backend]
284+
ep = entry_point_plugins(group="errbot.backend_plugins")
285+
extra_backend.extend(ep)
284286

285287
if args["list"]:
286288
from errbot.backend_plugin_manager import enumerate_backend_plugins

errbot/plugin_manager.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .plugin_info import PluginInfo
2020
from .storage import StoreMixin
2121
from .templating import add_plugin_templates_path, remove_plugin_templates_path
22-
from .utils import collect_roots, version2tuple
22+
from .utils import collect_roots, entry_point_plugins, version2tuple
2323
from .version import VERSION
2424

2525
PluginInstanceCallback = Callable[[str, Type[BotPlugin]], BotPlugin]
@@ -334,7 +334,8 @@ def update_plugin_places(self, path_list: str) -> Dict[Path, str]:
334334
:param path_list: the path list where to search for plugins.
335335
:return: the feedback for any specific path in case of error.
336336
"""
337-
repo_roots = (CORE_PLUGINS, self._extra_plugin_dir, path_list)
337+
ep = entry_point_plugins(group="errbot.plugins")
338+
repo_roots = (CORE_PLUGINS, self._extra_plugin_dir, path_list, ep)
338339

339340
all_roots = collect_roots(repo_roots)
340341

errbot/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
import sys
88
import time
99
from functools import wraps
10+
11+
try:
12+
from importlib.metadata import entry_points
13+
except ImportError:
14+
from importlib_metadata import entry_points
15+
1016
from platform import system
1117
from typing import List, Tuple, Union
1218

@@ -196,6 +202,13 @@ def collect_roots(base_paths: List, file_sig: str = "*.plug") -> List:
196202
return list(collections.OrderedDict.fromkeys(result))
197203

198204

205+
def entry_point_plugins(group):
206+
paths = []
207+
for entry_point in entry_points().get(group, []):
208+
paths.append(entry_point.dist._path.parent)
209+
return paths
210+
211+
199212
def global_restart() -> None:
200213
"""Restart the current process."""
201214
python = sys.executable

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
"deepmerge==1.0.1",
4343
]
4444

45+
if py_version < (3, 8):
46+
deps.append("importlib-metadata==4.12.0")
47+
4548
if py_version < (3, 9):
4649
deps.append("graphlib-backport==1.0.3")
4750

tests/plugin_management_test.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from errbot import plugin_manager
1010
from errbot.plugin_info import PluginInfo
1111
from errbot.plugin_manager import IncompatiblePluginException
12-
from errbot.utils import collect_roots, find_roots
12+
from errbot.utils import collect_roots, entry_point_plugins, find_roots
1313

1414
CORE_PLUGINS = plugin_manager.CORE_PLUGINS
1515

@@ -143,3 +143,8 @@ def test_errbot_version_check():
143143
plugin_manager.check_errbot_version(pi)
144144
finally:
145145
plugin_manager.VERSION = real_version
146+
147+
148+
def test_entry_point_plugin():
149+
no_plugins_found = entry_point_plugins("errbot.no_plugins")
150+
assert [] == no_plugins_found

0 commit comments

Comments
 (0)