| Dan Willemsen | adad21e | 2022-03-25 17:22:05 -0700 | [diff] [blame] | 1 | .. _`entry_points`: |
| 2 | |
| 3 | ============ |
| 4 | Entry Points |
| 5 | ============ |
| 6 | |
| 7 | Packages may provide commands to be run at the console (console scripts), |
| 8 | such as the ``pip`` command. These commands are defined for a package |
| 9 | as a specific kind of entry point in the ``setup.cfg`` or |
| 10 | ``setup.py``. |
| 11 | |
| 12 | |
| 13 | Console Scripts |
| 14 | =============== |
| 15 | |
| 16 | First consider an example without entry points. Imagine a package |
| 17 | defined thus: |
| 18 | |
| 19 | .. code-block:: bash |
| 20 | |
| 21 | timmins/ |
| 22 | timmins/__init__.py |
| 23 | timmins/__main__.py |
| 24 | setup.cfg # or setup.py |
| 25 | #other necessary files |
| 26 | |
| 27 | with ``__init__.py`` as: |
| 28 | |
| 29 | .. code-block:: python |
| 30 | |
| 31 | def hello_world(): |
| 32 | print("Hello world") |
| 33 | |
| 34 | and ``__main__.py`` providing a hook: |
| 35 | |
| 36 | .. code-block:: python |
| 37 | |
| 38 | from . import hello_world |
| 39 | |
| 40 | if __name__ == '__main__': |
| 41 | hello_world() |
| 42 | |
| 43 | After installing the package, the function may be invoked through the |
| 44 | `runpy <https://docs.python.org/3/library/runpy.html>`_ module: |
| 45 | |
| 46 | .. code-block:: bash |
| 47 | |
| 48 | python -m timmins |
| 49 | |
| 50 | Adding a console script entry point allows the package to define a |
| 51 | user-friendly name for installers of the package to execute. Installers |
| 52 | like pip will create wrapper scripts to execute a function. In the |
| 53 | above example, to create a command ``hello-world`` that invokes |
| 54 | ``timmins.hello_world``, add a console script entry point to |
| 55 | ``setup.cfg``: |
| 56 | |
| 57 | .. tab:: setup.cfg |
| 58 | |
| 59 | .. code-block:: ini |
| 60 | |
| 61 | [options.entry_points] |
| 62 | console_scripts = |
| 63 | hello-world = timmins:hello_world |
| 64 | |
| 65 | .. tab:: setup.py |
| 66 | |
| 67 | .. code-block:: python |
| 68 | |
| 69 | from setuptools import setup |
| 70 | |
| 71 | setup( |
| 72 | name='timmins', |
| 73 | version='0.0.1', |
| 74 | packages=['timmins'], |
| 75 | # ... |
| 76 | entry_points={ |
| 77 | 'console_scripts': [ |
| 78 | 'hello-world=timmins:hello_world', |
| 79 | ] |
| 80 | } |
| 81 | ) |
| 82 | |
| 83 | |
| 84 | After installing the package, a user may invoke that function by simply calling |
| 85 | ``hello-world`` on the command line. |
| 86 | |
| 87 | The syntax for entry points is specified as follows: |
| 88 | |
| 89 | .. code-block:: ini |
| 90 | |
| 91 | <name> = [<package>.[<subpackage>.]]<module>[:<object>.<object>] |
| 92 | |
| 93 | where ``name`` is the name for the script you want to create, the left hand |
| 94 | side of ``:`` is the module that contains your function and the right hand |
| 95 | side is the object you want to invoke (e.g. a function). |
| 96 | |
| 97 | In addition to ``console_scripts``, Setuptools supports ``gui_scripts``, which |
| 98 | will launch a GUI application without running in a terminal window. |
| 99 | |
| 100 | |
| 101 | .. _dynamic discovery of services and plugins: |
| 102 | |
| 103 | Advertising Behavior |
| 104 | ==================== |
| 105 | |
| 106 | Console scripts are one use of the more general concept of entry points. Entry |
| 107 | points more generally allow a packager to advertise behavior for discovery by |
| 108 | other libraries and applications. This feature enables "plug-in"-like |
| 109 | functionality, where one library solicits entry points and any number of other |
| 110 | libraries provide those entry points. |
| 111 | |
| 112 | A good example of this plug-in behavior can be seen in |
| 113 | `pytest plugins <https://docs.pytest.org/en/latest/writing_plugins.html>`_, |
| 114 | where pytest is a test framework that allows other libraries to extend |
| 115 | or modify its functionality through the ``pytest11`` entry point. |
| 116 | |
| 117 | The console scripts work similarly, where libraries advertise their commands |
| 118 | and tools like ``pip`` create wrapper scripts that invoke those commands. |
| 119 | |
| 120 | For a project wishing to solicit entry points, Setuptools recommends the |
| 121 | `importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_ |
| 122 | module (part of stdlib since Python 3.8) or its backport, |
| 123 | :pypi:`importlib_metadata`. |
| 124 | |
| 125 | For example, to find the console script entry points from the example above: |
| 126 | |
| 127 | .. code-block:: pycon |
| 128 | |
| 129 | >>> from importlib import metadata |
| 130 | >>> eps = metadata.entry_points()['console_scripts'] |
| 131 | |
| 132 | ``eps`` is now a list of ``EntryPoint`` objects, one of which corresponds |
| 133 | to the ``hello-world = timmins:hello_world`` defined above. Each ``EntryPoint`` |
| 134 | contains the ``name``, ``group``, and ``value``. It also supplies a ``.load()`` |
| 135 | method to import and load that entry point (module or object). |
| 136 | |
| 137 | .. code-block:: ini |
| 138 | |
| 139 | [options.entry_points] |
| 140 | my.plugins = |
| 141 | hello-world = timmins:hello_world |
| 142 | |
| 143 | Then, a different project wishing to load 'my.plugins' plugins could run |
| 144 | the following routine to load (and invoke) such plugins: |
| 145 | |
| 146 | .. code-block:: pycon |
| 147 | |
| 148 | >>> from importlib import metadata |
| 149 | >>> eps = metadata.entry_points()['my.plugins'] |
| 150 | >>> for ep in eps: |
| 151 | ... plugin = ep.load() |
| 152 | ... plugin() |
| 153 | ... |
| 154 | |
| 155 | The project soliciting the entry points needs not to have any dependency |
| 156 | or prior knowledge about the libraries implementing the entry points, and |
| 157 | downstream users are able to compose functionality by pulling together |
| 158 | libraries implementing the entry points. |
| 159 | |
| 160 | |
| 161 | Dependency Management |
| 162 | ===================== |
| 163 | |
| 164 | Some entry points may require additional dependencies to properly function. |
| 165 | For such an entry point, declare in square brackets any number of dependency |
| 166 | ``extras`` following the entry point definition. Such entry points will only |
| 167 | be viable if their extras were declared and installed. See the |
| 168 | :doc:`guide on dependencies management <dependency_management>` for |
| 169 | more information on defining extra requirements. Consider from the |
| 170 | above example: |
| 171 | |
| 172 | .. code-block:: ini |
| 173 | |
| 174 | [options.entry_points] |
| 175 | console_scripts = |
| 176 | hello-world = timmins:hello_world [pretty-printer] |
| 177 | |
| 178 | In this case, the ``hello-world`` script is only viable if the ``pretty-printer`` |
| 179 | extra is indicated, and so a plugin host might exclude that entry point |
| 180 | (i.e. not install a console script) if the relevant extra dependencies are not |
| 181 | installed. |