Commit a5b144e3 authored by Jason R. Coombs's avatar Jason R. Coombs Committed by GitHub

Merge pull request #2154 from alvyjudy/entrypoint

Step 6 on 2093: userguide on entry points now available
parents 01a87625 dfe2ef5d
========================================== .. _`entry_points`:
Entry Points and Automatic Script Creation
========================================== ============
Entry Points
Packaging and installing scripts can be a bit awkward with the distutils. For ============
one thing, there's no easy way to have a script's filename match local
conventions on both Windows and POSIX platforms. For another, you often have Packages may provide commands to be run at the console (console scripts),
to create a separate file just for the "main" script, when your actual "main" such as the ``pip`` command. These commands are defined for a package
is a function in a module somewhere. And even in Python 2.4, using the ``-m`` as a specific kind of entry point in the ``setup.cfg`` or
option only works for actual ``.py`` files that aren't installed in a package. ``setup.py``.
``setuptools`` fixes all of these problems by automatically generating scripts
for you with the correct extension, and on Windows it will even create an Console Scripts
``.exe`` file so that users don't have to change their ``PATHEXT`` settings. ===============
The way to use this feature is to define "entry points" in your setup script
that indicate what function the generated script should import and run. For First consider an example without entry points. Imagine a package
example, to create two console scripts called ``foo`` and ``bar``, and a GUI defined thus::
script called ``baz``, you might do something like this::
.. code-block:: bash
setup(
# other arguments here... timmins/
entry_points={ timmins/__init__.py
"console_scripts": [ timmins/__main__.py
"foo = my_package.some_module:main_func", setup.cfg # or setup.py
"bar = other_module:some_func", #other necessary files
],
"gui_scripts": [ with ``__init__.py`` as:
"baz = my_package_gui:start_func",
] .. code-block:: python
}
) def helloworld():
print("Hello world")
When this project is installed on non-Windows platforms (using "setup.py
install", "setup.py develop", or with pip), a set of ``foo``, ``bar``, and ``__main__.py`` providing a hook:
and ``baz`` scripts will be installed that import ``main_func`` and
``some_func`` from the specified modules. The functions you specify are from . import hello_world
called with no arguments, and their return value is passed to if __name__ == '__main__':
``sys.exit()``, so you can return an errorlevel or message to print to hello_world()
stderr.
After installing the package, the function may be invoked through the
On Windows, a set of ``foo.exe``, ``bar.exe``, and ``baz.exe`` launchers are `runpy <https://docs.python.org/3/library/runpy.html>`_ module::
created, alongside a set of ``foo.py``, ``bar.py``, and ``baz.pyw`` files. The
``.exe`` wrappers find and execute the right version of Python to run the .. code-block:: bash
``.py`` or ``.pyw`` file.
python -m timmins
You may define as many "console script" and "gui script" entry points as you
like, and each one can optionally specify "extras" that it depends on, that Adding a console script entry point allows the package to define a
will be added to ``sys.path`` when the script is run. For more information on user-friendly name for installers of the package to execute. Installers
"extras", see the section below on `Declaring Extras`_. For more information like pip will create wrapper scripts to execute a function. In the
on "entry points" in general, see the section below on `Dynamic Discovery of above example, to create a command ``hello-world`` that invokes
Services and Plugins`_. ``timmins.hello_world``, add a console script entry point to
``setup.cfg``::
Dynamic Discovery of Services and Plugins .. code-block:: ini
-----------------------------------------
[options.entry_points]
``setuptools`` supports creating libraries that "plug in" to extensible console_scripts =
applications and frameworks, by letting you register "entry points" in your hello-world = timmins:hello_world
project that can be imported by the application or framework.
After installing the package, a user may invoke that function by simply calling
For example, suppose that a blogging tool wants to support plugins ``hello-world`` on the command line.
that provide translation for various file types to the blog's output format.
The framework might define an "entry point group" called ``blogtool.parsers``, The syntax for entry points is specified as follows:
and then allow plugins to register entry points for the file extensions they
support. .. code-block::
This would allow people to create distributions that contain one or more <name> = [<package>.[<subpackage>.]]<module>[:<object>.<object>]
parsers for different file types, and then the blogging tool would be able to
find the parsers at runtime by looking up an entry point for the file where ``name`` is the name for the script you want to create, the left hand
extension (or mime type, or however it wants to). 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).
Note that if the blogging tool includes parsers for certain file formats, it
can register these as entry points in its own setup script, which means it In addition to ``console_scripts``, Setuptools supports ``gui_scripts``, which
doesn't have to special-case its built-in formats. They can just be treated will launch a GUI application without running in a terminal window.
the same as any other plugin's entry points would be.
If you're creating a project that plugs in to an existing application or Advertising Behavior
framework, you'll need to know what entry points or entry point groups are ====================
defined by that application or framework. Then, you can register entry points
in your setup script. Here are a few examples of ways you might register an Console scripts are one use of the more general concept of entry points. Entry
``.rst`` file parser entry point in the ``blogtool.parsers`` entry point group, points more generally allow a packager to advertise behavior for discovery by
for our hypothetical blogging tool:: other libraries and applications. This feature enables "plug-in"-like
functionality, where one library solicits entry points and any number of other
setup( libraries provide those entry points.
# ...
entry_points={"blogtool.parsers": ".rst = some_module:SomeClass"} 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
setup( or modify its functionality through the ``pytest11`` entry point.
# ...
entry_points={"blogtool.parsers": [".rst = some_module:a_func"]} The console scripts work similarly, where libraries advertise their commands
) and tools like ``pip`` create wrapper scripts that invoke those commands.
setup( For a project wishing to solicit entry points, Setuptools recommends the
# ... `importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_
entry_points=""" module (part of stdlib since Python 3.8) or its backport,
[blogtool.parsers] `importlib_metadata <https://pypi.org/project/importlib_metadata>`_.
.rst = some.nested.module:SomeClass.some_classmethod [reST]
""", For example, to find the console script entry points from the example above::
extras_require=dict(reST="Docutils>=0.3.5")
) .. code-block:: python
The ``entry_points`` argument to ``setup()`` accepts either a string with >>> from importlib import metadata
``.ini``-style sections, or a dictionary mapping entry point group names to >>> eps = metadata.entry_points()['console_scripts']
either strings or lists of strings containing entry point specifiers. An
entry point specifier consists of a name and value, separated by an ``=`` ``eps`` is now a list of ``EntryPoint`` objects, one of which corresponds
sign. The value consists of a dotted module name, optionally followed by a to the ``hello-world = timmins:hello_world`` defined above. Each ``EntryPoint``
``:`` and a dotted identifier naming an object within the module. It can contains the ``name``, ``group``, and ``value``. It also supplies a ``.load()``
also include a bracketed list of "extras" that are required for the entry method to import and load that entry point (module or object).
point to be used. When the invoking application or framework requests loading
of an entry point, any requirements implied by the associated extras will be .. code-block:: ini
passed to ``pkg_resources.require()``, so that an appropriate error message
can be displayed if the needed package(s) are missing. (Of course, the [options.entry_points]
invoking app or framework can ignore such errors if it wants to make an entry my.plugins =
point optional if a requirement isn't installed.) hello-world = timmins:hello_world
\ No newline at end of file
Then, a different project wishing to load 'my.plugins' plugins could run
the following routine to load (and invoke) such plugins::
.. code-block:: python
>>> 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 brakets 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
:ref:`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.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment