summaryrefslogtreecommitdiff
path: root/docs/userguide/entry_point.rst
diff options
context:
space:
mode:
authorSaniya Maheshwari <saniya.mah@gmail.com>2022-06-10 14:37:37 +0530
committerSaniya Maheshwari <saniya.mah@gmail.com>2022-06-10 14:37:37 +0530
commit59982b3be544362818d190f8a896bd53d9b87f09 (patch)
tree16e159df6e53756715e615cc18a2271951eaa2ad /docs/userguide/entry_point.rst
parent91e697b17bda17d89537c0d080da0e8a66e9da15 (diff)
downloadpython-setuptools-git-59982b3be544362818d190f8a896bd53d9b87f09.tar.gz
Added a lengthy example on entry points and plugins
Diffstat (limited to 'docs/userguide/entry_point.rst')
-rw-r--r--docs/userguide/entry_point.rst168
1 files changed, 165 insertions, 3 deletions
diff --git a/docs/userguide/entry_point.rst b/docs/userguide/entry_point.rst
index 32589985..1b58a7c8 100644
--- a/docs/userguide/entry_point.rst
+++ b/docs/userguide/entry_point.rst
@@ -218,8 +218,170 @@ or modify its functionality through the ``pytest11`` entry point.
The console scripts work similarly, where libraries advertise their commands
and tools like ``pip`` create wrapper scripts that invoke those commands.
+Entry Points for Plugins
+========================
+
+Let us consider a simple example to understand how we can implement entry points
+corresponding to plugins. Say we have a package ``timmins`` with the following
+directory structure::
+
+ timmins
+ ├── setup.py # and/or setup.cfg, pyproject.toml
+ └── src
+ └── timmins
+ └── __init__.py
+
+and in ``src/timmins/__init__.py`` we have the following code:
+
+.. code-block:: python
+
+ def hello_world():
+ print('Hello world')
+
+Basically, we have defined a ``hello_world()`` function which will print the text
+'Hello world'. Now, let us say we want to print the text 'Hello world' in different
+ways. The current function just prints the text as it is - let us say we want another
+style in which the text is enclosed within exclamation marks::
+
+ !!! Hello world !!!
+
+Let us see how this can be done using plugins. First, let us separate the style of
+printing the text from the text itself. In other words, we can change the code in
+``src/timmins/__init__.py`` to something like this:
+
+.. code-block:: python
+
+ def display(text):
+ print(text)
+
+ def hello_world():
+ display('Hello world')
+
+Here, the ``display()`` function controls the style of printing the text, and the
+``hello_world()`` function calls the ``display()`` function to print the text 'Hello
+world`.
+
+Right now the ``display()`` function just prints the text as it is. In order to be able
+to customize it, we can do the following. Let us introduce a new *group* of entry points
+named ``timmins.display``, and expect plugin packages implementing this entry point
+to supply a ``display()``-like function. Next, to be able to automatically discover plugin
+packages that implement this entry point, we can use the
+`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_ module,
+as follows:
+
+.. code-block:: python
+
+ from importlib.metadata import entry_points
+ display_eps = entry_points(group='timmins.display')
+
+.. note::
+ Each ``importlib.metadata.EntryPoint`` object is an object containing a ``name``, a
+ ``group``, and a ``value``.
+
+``display_eps`` will now be a list of ``EntryPoint`` objects, each referring to ``display()``-like
+functions defined by one or more installed plugin packages. Then, to import a specific
+``display()``-like function - let us choose the one corresponding to the first discovered
+entry point - we can use the ``load()`` method as follows:
+
+.. code-block:: python
+
+ display = display_eps[0].load()
+
+Finally, a sensible behaviour would be that if we cannot find any plugin packages customizing
+the ``display()`` function, we should fall back to our default implementation which prints
+the text as it is. With this behaviour included, the code in ``src/timmins/__init__.py``
+finally becomes:
+
+.. code-block:: python
+
+ from importlib.metadata import entry_points
+ display_eps = entry_points(group='timmins.display')
+ try:
+ display = display_eps[0].load()
+ except IndexError:
+ def display(text):
+ print(text)
+
+ def hello_world():
+ display('Hello world')
+
+That finishes the setup on ``timmins``'s side. Next, we need to implement a plugin
+which implements the entry point ``timmins.display``. Let us name this plugin
+``timmins-plugin-fancy``, and set it up with the following directory structure::
+
+ timmins-plugin-fancy
+ ├── setup.py # and/or setup.cfg, pyproject.toml
+ └── src
+ └── timmins_plugin_fancy
+ └── __init__.py
+
+And then, inside ``src/timmins_plugin_fancy/__init__.py``, we can put a function
+named ``excl_display()`` that prints the given text surrounded by exclamation marks:
+
+.. code-block:: python
+
+ def excl_display(text):
+ print('!!!', text, '!!!')
+
+This is the ``display()``-like function that we are looking to supply to the
+``timmins`` package. We can do that by adding the following in the configuration
+of ``timmins-plugin-fancy``:
+
+.. tab:: setup.cfg
+
+ .. code-block:: ini
+
+ [options.entry_points]
+ timmins.display =
+ excl = timmins_plugin_fancy:excl_display
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ from setuptools import setup
+
+ setup(
+ # ...,
+ entry_points = {
+ 'timmins.display' = [
+ 'excl=timmins_plugin_fancy:excl_display'
+ ]
+ }
+ )
+
+.. tab:: pyproject.toml (**EXPERIMENTAL**) [#experimental]_
+
+ .. code-block:: toml
+
+ [project.entry-points."timmins.display"]
+ excl = "timmins_plugin_fancy:excl_display"
+
+Basically, this configuration states that we are a supplying an entry point
+under the group ``timmins.display``. The entry point is named ``excl`` and it
+refers to the function ``excl_display`` defined by the package ``timmins_plugin_fancy``.
+
+Now, if we install both ``timmins`` and ``timmins_plugin_fancy``, we should get
+the following:
+
+.. code-block:: pycon
+
+ >>> from timmins import hello_world
+ >>> hello_world()
+ !!! Hello world !!!
+
+whereas if we only install ``timmins`` and not ``timmins_plugin_fancy``, we should
+get the following:
+
+.. code-block:: pycon
+
+ >>> from timmins import hello_world
+ >>> hello_world()
+ Hello world
+
+Therefore, our plugin works.
+
For a project wishing to solicit entry points, Setuptools recommends the
-`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_
module (part of stdlib since Python 3.8) or its backport,
:pypi:`importlib_metadata`.
@@ -231,8 +393,8 @@ For example, to find the console script entry points from the example above:
>>> eps = metadata.entry_points()['console_scripts']
``eps`` is now a list of ``EntryPoint`` objects, one of which corresponds
-to the ``hello-world = timmins:hello_world`` defined above. Each ``EntryPoint``
-contains the ``name``, ``group``, and ``value``. It also supplies a ``.load()``
+to the ``hello-world = timmins:hello_world`` defined above.
+It also supplies a ``.load()``
method to import and load that entry point (module or object).
.. code-block:: ini