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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
.. _`entry_points`:
==========================================
Entry Points and Automatic Script Creation
==========================================
After installing some packages, you may realize you can invoke some commands
without explicitly calling the python interpreter. For example, instead of
calling ``python -m pip install`` you can just do ``pip install``. The magic
behind this is entry point, a keyword passed to your ``setup.cfg`` or
``setup.py`` to create script wrapped around function in your libraries.
Using entry point in your package
=================================
Let's start with an example. Suppose you have written your package like this:
.. code-block:: bash
timmins/
timmins/__init__.py
setup.cfg # or setup.py
#other necessary files
and in your ``__init__.py`` it defines a function:
.. code-block:: python
def helloworld():
print("Hello world")
After installing the package, you can invoke this function in the following
manner, without applying any magic:
.. code-block:: bash
python -m mypkg.helloworld
But entry point simplifies the call and would create a wrapper script around
your function, making it behave more natively (you type in ``helloworld`` and
the ``helloworld`` function residing inside ``__init__.py`` is executed!). To
accomplish that, add the following lines to your ``setup.cfg`` or ``setup.py``:
.. code-block:: ini
[options]
#...
entry_points =
[console_scripts]
helloworld = mypkg:helloworld
.. code-block:: python
setup(
#...
entry_points = """
[console_scripts]
helloworld = mypkg:helloworld
"""
)
The syntax for entry points is specified as follows:
.. code-block::
[<type>]
<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). ``type`` specifies the
type of entry point (pertinent to the program that exploits it). ``setuptools``
natively supports ``[console_script]`` and ``[gui_script]``.
.. note::
the syntax is not limited to ``INI`` string as demonstrated above. You can
also pass in the values in the form of a dictionary or list. Check out
:ref:`keyword reference <keywords_ref>` for more details
After installation, you will be able to invoke that function by simply calling
``helloworld`` on your command line. It will even do command line argument
parsing for you!
Dynamic discovery of services (aka plugin support)
==================================================
The ability of entry points isn't limited to "advertising" your functions. Its
implementation allows us to accomplish more powerful features, such as creating
plugins. In fact, the aforementioned script wrapping ability is a form of
plugin that was built into ``setuptools``. With that being said, you now have
more options than ``[console_script]`` or ``[gui_script]`` when creating your
package.
To understand how you can extend this functionality, let's go through how
``setuptool`` does its ``[console_script]`` magic. Again, we use the same
example as above:
.. code-block:: ini
[options]
# ...
entry_points =
[console_scripts]
helloworld = mypkg:helloworld
Package installation contains multiple steps, so at some point, this package
becomes available to your interpreter, and if you run the following code:
.. code-block:: ini
>>> import pkg_resources #a module part of setuptools
>>> [item for item in
pkg_srouces.working_set.iter_entry_points('console_scripts')]
It will return a list of special objects (called "EntryPoints"), and there
will be one of them that corresponds to the ``helloworld = mypkg:helloworld``
which we defined above. In fact, this object doesn't just contain the string,
but also an encompassing representation of the package that created it.
In the case of ``console_scripts``, setuptools will automatically invoke
an internal function that utilizes this object and create the wrapper scripts
and place them in your ``bin`` directory for your interpreter. How
``pkg_resource`` look up all the entry points is further detailed in our
:ref:`developer_guide` (WIP). With that being said, if you specify a different
entry point:
.. code-block:: ini
[options]
# ...
entry_points =
[iam.just.playing.around]
helloworld = mypkg:helloworld
Then, running the same python expression like above:
.. code-block:: python
>>> import pkg_resources
>>> [item for item in
pkg_srouces.working_set.iter_entry_points('iam.just.playing.around')
]
will create another ``EntryPoints`` object that contains the
``helloworld = mypkg:helloworld`` and you can create custom
functions to exploit its information however you want. For example, one of
the installed programs on your system may contain a startup script that
scans the system for all the packages that specify this
``iam.just.playing.around`` entry points, such that when you install this new
package, it becomes immediately available without having to reconfigure
the already installed program. This in fact is the very idea of a plugin!
Dependencies management for entry points
========================================
Some entry points may require additional dependencies for them to work and
others may trigger the installation of additional dependencies only when they
are run. While this is elaborated in more excrutiating details on
:ref:`guide on dependencies management <dependency_management>`, we will
provide a brief overview on the entry point aspect.
Dependencies of this manner are declared using the ``extra_requires`` keywords,
which takes a mapping of the arbitary name of the functionality and a list of
its depencencies, optionally suffixed with its :ref:`version specifier
<version_specifier>`. For example, our package provides "pdf" output capability
which requires at least 0.3 version of "ReportLab" and whatever version of "RXP"
.. code-block:: ini
[options.extras_require]
PDF = ReportLab>=1.2; RXP
.. code-block:: python
setup(
extras_require = {
"PDF": ["ReportLab>=1.2", "RXP"],
}
)
And we only want them to be installed if the console script entry point
``rst2pdf`` is run:
.. code-block:: ini
[options]
entry_points =
['console_script']
rst2pdf = project_a.tools.pdfgen [PDF]
rst2html = project_a.tools.htmlgen
.. code-block:: python
setup(
entry_points = """
['console_script']
rst2pdf = project_a.tools.pdfgen [PDF]
rst2html = project_a.tools.htmlgen
"""
)
|