182 lines
5.4 KiB
ReStructuredText
182 lines
5.4 KiB
ReStructuredText
.. _`entry_points`:
|
|
|
|
============
|
|
Entry Points
|
|
============
|
|
|
|
Packages may provide commands to be run at the console (console scripts),
|
|
such as the ``pip`` command. These commands are defined for a package
|
|
as a specific kind of entry point in the ``setup.cfg`` or
|
|
``setup.py``.
|
|
|
|
|
|
Console Scripts
|
|
===============
|
|
|
|
First consider an example without entry points. Imagine a package
|
|
defined thus:
|
|
|
|
.. code-block:: bash
|
|
|
|
timmins/
|
|
timmins/__init__.py
|
|
timmins/__main__.py
|
|
setup.cfg # or setup.py
|
|
#other necessary files
|
|
|
|
with ``__init__.py`` as:
|
|
|
|
.. code-block:: python
|
|
|
|
def hello_world():
|
|
print("Hello world")
|
|
|
|
and ``__main__.py`` providing a hook:
|
|
|
|
.. code-block:: python
|
|
|
|
from . import hello_world
|
|
|
|
if __name__ == '__main__':
|
|
hello_world()
|
|
|
|
After installing the package, the function may be invoked through the
|
|
`runpy <https://docs.python.org/3/library/runpy.html>`_ module:
|
|
|
|
.. code-block:: bash
|
|
|
|
python -m timmins
|
|
|
|
Adding a console script entry point allows the package to define a
|
|
user-friendly name for installers of the package to execute. Installers
|
|
like pip will create wrapper scripts to execute a function. In the
|
|
above example, to create a command ``hello-world`` that invokes
|
|
``timmins.hello_world``, add a console script entry point to
|
|
``setup.cfg``:
|
|
|
|
.. tab:: setup.cfg
|
|
|
|
.. code-block:: ini
|
|
|
|
[options.entry_points]
|
|
console_scripts =
|
|
hello-world = timmins:hello_world
|
|
|
|
.. tab:: setup.py
|
|
|
|
.. code-block:: python
|
|
|
|
from setuptools import setup
|
|
|
|
setup(
|
|
name='timmins',
|
|
version='0.0.1',
|
|
packages=['timmins'],
|
|
# ...
|
|
entry_points={
|
|
'console_scripts': [
|
|
'hello-world=timmins:hello_world',
|
|
]
|
|
}
|
|
)
|
|
|
|
|
|
After installing the package, a user may invoke that function by simply calling
|
|
``hello-world`` on the command line.
|
|
|
|
The syntax for entry points is specified as follows:
|
|
|
|
.. code-block:: ini
|
|
|
|
<name> = [<package>.[<subpackage>.]]<module>[:<object>.<object>]
|
|
|
|
where ``name`` is the name for the script you want to create, the left hand
|
|
side of ``:`` is the module that contains your function and the right hand
|
|
side is the object you want to invoke (e.g. a function).
|
|
|
|
In addition to ``console_scripts``, Setuptools supports ``gui_scripts``, which
|
|
will launch a GUI application without running in a terminal window.
|
|
|
|
|
|
.. _dynamic discovery of services and plugins:
|
|
|
|
Advertising Behavior
|
|
====================
|
|
|
|
Console scripts are one use of the more general concept of entry points. Entry
|
|
points more generally allow a packager to advertise behavior for discovery by
|
|
other libraries and applications. This feature enables "plug-in"-like
|
|
functionality, where one library solicits entry points and any number of other
|
|
libraries provide those entry points.
|
|
|
|
A good example of this plug-in behavior can be seen in
|
|
`pytest plugins <https://docs.pytest.org/en/latest/writing_plugins.html>`_,
|
|
where pytest is a test framework that allows other libraries to extend
|
|
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.
|
|
|
|
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`.
|
|
|
|
For example, to find the console script entry points from the example above:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> from importlib import metadata
|
|
>>> 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()``
|
|
method to import and load that entry point (module or object).
|
|
|
|
.. code-block:: ini
|
|
|
|
[options.entry_points]
|
|
my.plugins =
|
|
hello-world = timmins:hello_world
|
|
|
|
Then, a different project wishing to load 'my.plugins' plugins could run
|
|
the following routine to load (and invoke) such plugins:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> from importlib import metadata
|
|
>>> eps = metadata.entry_points()['my.plugins']
|
|
>>> for ep in eps:
|
|
... plugin = ep.load()
|
|
... plugin()
|
|
...
|
|
|
|
The project soliciting the entry points needs not to have any dependency
|
|
or prior knowledge about the libraries implementing the entry points, and
|
|
downstream users are able to compose functionality by pulling together
|
|
libraries implementing the entry points.
|
|
|
|
|
|
Dependency Management
|
|
=====================
|
|
|
|
Some entry points may require additional dependencies to properly function.
|
|
For such an entry point, declare in square brackets any number of dependency
|
|
``extras`` following the entry point definition. Such entry points will only
|
|
be viable if their extras were declared and installed. See the
|
|
:doc:`guide on dependencies management <dependency_management>` for
|
|
more information on defining extra requirements. Consider from the
|
|
above example:
|
|
|
|
.. code-block:: ini
|
|
|
|
[options.entry_points]
|
|
console_scripts =
|
|
hello-world = timmins:hello_world [pretty-printer]
|
|
|
|
In this case, the ``hello-world`` script is only viable if the ``pretty-printer``
|
|
extra is indicated, and so a plugin host might exclude that entry point
|
|
(i.e. not install a console script) if the relevant extra dependencies are not
|
|
installed.
|