Skip to content

Commit

Permalink
Feature: plugins detection using entrypoints (#1590)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sijis authored Jul 14, 2022
1 parent 3f5abef commit 791a08b
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
v9.9.9 (unreleased)
-------------------

features:

- core/plugins: detect plugins using entrypoints (#1590)

fixes:

- docs: add unreleased section (#1576)
Expand Down
51 changes: 40 additions & 11 deletions docs/user_guide/administration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,20 @@ If you just wish to know more about a specific command you can issue::
Installing plugins
------------------

Errbot plugins are typically published to and installed from `GitHub <http://github.com/>`_.
We periodically crawl GitHub for errbot plugin repositories and `publish the results <https://github.com/errbotio/errbot/wiki>`_ for people to browse.
Errbot plugins can be installed via these methods

* `!repos install` bot commnand
* Cloning a `GitHub <http://github.com/>`_ repository
* Extracting a tar/zip file
* Using pip


Using a bot command
^^^^^^^^^^^^^^^^^^^

Plugins installed via the :code:`!repos` command are managed by errbot itself and stored inside the `BOT_DATA_DIR` you set in `config.py`.

We periodically crawl GitHub for errbot plugin repositories and `publish the results <https://errbot.io/repos.json>`_ for people to browse.

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

Expand Down Expand Up @@ -72,6 +84,32 @@ This can be done with::
!repos update all


Cloning a repository or tar/zip install
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Using a git repository or tar/zip file to install plugins is setting up your plugins to be managed manually.

Plugins installed from cloning a repository need to be placed inside the `BOT_EXTRA_PLUGIN_DIR` path specified in the `config.py` file.

Assuming `BOT_EXTRA_PLUGIN_DIR` is set to `/opt/plugins`::

$ git clone https://github.com/errbotio/err-helloworld /opt/plugins/err-helloworld
$ tar -zxvf err-helloworld.tar.gz -C /opt/plugins/

.. note::
If a repo is cloned and the git remote information is present, updating the plugin may be possible via `!repos update`


Using pip for plugins
^^^^^^^^^^^^^^^^^^^^^

Plugins published to to pypi.org can be installed using pip.::

$ pip install errbot-plugin-helloworld

As part of the packaging configuration for the plugin, it should install all necessary dependencies for the plugin to work.


Dependencies
^^^^^^^^^^^^

Expand All @@ -83,15 +121,6 @@ If you have installed Errbot in a virtualenv, this will run the equivalent of :c
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.


Extra plugin directory
^^^^^^^^^^^^^^^^^^^^^^

Plugins installed via the :code:`!repos` command are managed by errbot itself and stored inside the `BOT_DATA_DIR` you set in `config.py`.
If you want to manage your plugins manually for any reason then errbot allows you to load additional plugins from a directory you specify.
You can do so by specifying the setting `BOT_EXTRA_PLUGIN_DIR` in your `config.py` file.
See the :download:`config-template.py` file for more details.


.. _disabling_plugins:

Disabling plugins
Expand Down
27 changes: 27 additions & 0 deletions docs/user_guide/plugin_development/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,33 @@ the function `invert_string()`, the `helloworld` plugin can import it and use it
"""Say hello to the world"""
return invert_string("Hello, world!")
Packaging
---------

A plugin can be packaged and distributed through pypi.org. The errbot plugin system uses entrypoints in setuptools to find available plugins.

The two entrypoint avialable are

* `errbot.plugins` - normal plugin and flows
* `errbot.backend_plugins` - backend plugins for collaboration providers

To get this setup, add this block of code to `setup.py`.

.. code-block:: python
entry_points = {
"errbot.plugins": [
"helloworld = helloWorld:HelloWorld",
]
}
Optionally, you may need to include a `MANIFEST.in` to include files of the repo

.. code-block:: python
include *.py *.plug
Wrapping up
-----------

Expand Down
5 changes: 3 additions & 2 deletions errbot/backend_plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from errbot.plugin_info import PluginInfo

from .utils import collect_roots
from .utils import collect_roots, entry_point_plugins

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -44,7 +44,8 @@ def __init__(
self._base_class = base_class

self.plugin_info = None
all_plugins_paths = collect_roots((base_search_dir, extra_search_dirs))
ep = entry_point_plugins(group="errbot.backend_plugins")
all_plugins_paths = collect_roots((base_search_dir, extra_search_dirs, ep))

for potential_plugin in enumerate_backend_plugins(all_plugins_paths):
if potential_plugin.name == plugin_name:
Expand Down
4 changes: 3 additions & 1 deletion errbot/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from errbot.bootstrap import CORE_BACKENDS
from errbot.logs import root_logger
from errbot.plugin_wizard import new_plugin_wizard
from errbot.utils import collect_roots
from errbot.utils import collect_roots, entry_point_plugins
from errbot.version import VERSION

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -281,6 +281,8 @@ def main() -> None:
extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", [])
if isinstance(extra_backend, str):
extra_backend = [extra_backend]
ep = entry_point_plugins(group="errbot.backend_plugins")
extra_backend.extend(ep)

if args["list"]:
from errbot.backend_plugin_manager import enumerate_backend_plugins
Expand Down
5 changes: 3 additions & 2 deletions errbot/plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .plugin_info import PluginInfo
from .storage import StoreMixin
from .templating import add_plugin_templates_path, remove_plugin_templates_path
from .utils import collect_roots, version2tuple
from .utils import collect_roots, entry_point_plugins, version2tuple
from .version import VERSION

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

all_roots = collect_roots(repo_roots)

Expand Down
13 changes: 13 additions & 0 deletions errbot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
import sys
import time
from functools import wraps

try:
from importlib.metadata import entry_points
except ImportError:
from importlib_metadata import entry_points

from platform import system
from typing import List, Tuple, Union

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


def entry_point_plugins(group):
paths = []
for entry_point in entry_points().get(group, []):
paths.append(entry_point.dist._path.parent)
return paths


def global_restart() -> None:
"""Restart the current process."""
python = sys.executable
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
"deepmerge==1.0.1",
]

if py_version < (3, 8):
deps.append("importlib-metadata==4.12.0")

if py_version < (3, 9):
deps.append("graphlib-backport==1.0.3")

Expand Down
7 changes: 6 additions & 1 deletion tests/plugin_management_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from errbot import plugin_manager
from errbot.plugin_info import PluginInfo
from errbot.plugin_manager import IncompatiblePluginException
from errbot.utils import collect_roots, find_roots
from errbot.utils import collect_roots, entry_point_plugins, find_roots

CORE_PLUGINS = plugin_manager.CORE_PLUGINS

Expand Down Expand Up @@ -143,3 +143,8 @@ def test_errbot_version_check():
plugin_manager.check_errbot_version(pi)
finally:
plugin_manager.VERSION = real_version


def test_entry_point_plugin():
no_plugins_found = entry_point_plugins("errbot.no_plugins")
assert [] == no_plugins_found

0 comments on commit 791a08b

Please sign in to comment.