aboutsummaryrefslogtreecommitdiff
path: root/docs/userguide/entry_point.rst
blob: b97419c474477ffcad74c63dc3345143d3f758df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
.. _`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.