The docs for the (awesome) Click package suggest a few reasons to use entry points instead of scripts, including

  1. cross-platform compatibility and
  2. avoiding having the interpreter assign __name__ to __main__, which could cause code to be imported twice (if another module imports your script)

Click is a nice way to implement functions for use as entry_points, btw.

Answer from jfeala on Stack Overflow
🌐
PyPA
setuptools.pypa.io › en › latest › userguide › entry_point.html
Entry Points - setuptools 84.0.0 documentation
Instead of this approach using __main__.py, you can also create a user-friendly CLI executable that can be called directly without python -m. In the above example, to create a command hello-world that invokes timmins.hello_world, add a console script entry point to your configuration: ... from setuptools import setup setup( # ..., entry_points={ 'console_scripts': [ 'hello-world = timmins:hello_world', ] } )
Discussions

Console_scripts entrypoints hidden behind extras are always installed
pip version 21.0.1 Python version 3.9.2 OS Linux Additional information Python is being installed from conda-forge Description According to [1] it should be possible to define console_scripts which are only installed if an extra is speci... More on github.com
🌐 github.com
5
March 22, 2021
deployment - How can I use setuptools to generate a console_scripts entry point which calls `python -m mypackage`? - Stack Overflow
I am also trying to generate my executable scripts upon python setuptools install using setuptools entry_points{'console_scripts': ... More on stackoverflow.com
🌐 stackoverflow.com
python - How to set the bin scripts entry point in `setup.py`? - Stack Overflow
I am trying to create a package (on a Mac) which can be installed with pip. This package contains one main executable in the repository named mycode.py which I can run locally as follows: python my... More on stackoverflow.com
🌐 stackoverflow.com
entry_points console_scripts not working in setuptools >=69 if `dynamic` is not configured
setuptools version setuptools>=69 Python version 3.8, 3.9, 3.10 OS ubuntu, Windows Additional environment information pyproject.toml: [build-system] requires = ["setuptools>=61.0", "pip >= 23.1", "... More on github.com
🌐 github.com
3
November 27, 2023
🌐
DEV Community
dev.to › demianbrecht › entry-points-in-python-34i3
Entry points in Python - DEV Community
April 9, 2020 - The combination of console_scripts and entry_points in a project's setup.py can help us achieve the same results as the previous sections, but without the need for the if __name__ == '__main__' pattern.
🌐
Python Packaging
packaging.python.org › specifications › entry-points
Entry points specification - Python Packaging User Guide
The group that an entry point belongs to indicates what sort of object it provides. For instance, the group console_scripts is for entry points referring to functions which can be used as a command, while pygments.styles is the group for classes defining pygments styles.
🌐
Readthedocs
python-packaging.readthedocs.io › en › latest › command-line-scripts.html
Command Line Scripts — Python Packaging Tutorial
The command_line.py submodule exists only to service the command line tool (which is a convenient organization method): ... setup( ... entry_points = { 'console_scripts': ['funniest-joke=funniest.command_line:main'], } ...
🌐
Rachum
amir.rachum.com › amir rachum's blog › python entry points explained
Python Entry Points Explained - Amir Rachum's
July 28, 2017 - console_scripts, they say, is a special type of entry point. setuptools reads its content as "<console script name> = <python object path>" and creates an appropriate script when your package is installed.
🌐
Chriswarrick
chriswarrick.com › blog › 2014 › 09 › 15 › python-apps-the-right-way-entry_points-and-scripts
Python Apps the Right Way: entry points and scripts | Chris Warrick
September 15, 2014 - You must use setuptools, otherwise this won’t work. The most important piece of code is the entry_points declaration (unsurprisingly). ... If you are developing a GUI application (in Tkinter, PyQt/PySide, wxPython, PyGTK, PyGame…), you should change the declaration to gui_scripts. On *nix, this makes no difference, but on Windows, it means that running your script by opening the created .exe files does not show a console window.
🌐
Tpleyer
blog.tpleyer.de › posts › 2017-12-13-Python-setup-py-entry-points.html
Python setup.py entry points - Tobi's blog
December 13, 2017 - The content of this file can grow ... many options, the one I want to talk about is the entry_points keyword. The most typical example of an entry point is the so called console_scripts entry point....
Find elsewhere
🌐
Benjamintoll
benjamintoll.com › 2021 › 04 › 04 › on-python-entry_points
On Python entry_points - benjamintoll.com
April 4, 2021 - Instead of writing a custom module for your super-duper app that locates and loads third-party extensions and scripts at runtime, leverage the native tooling that Python offers to do this (primarily via the pkg_resources module), with minimum fuss and overhead on your end. As long as the group name is exposed in the third-party packages, the tooling will find them and automatically load them at runtime. Here is an example of the entry_points section of a setup.cfg file for a third-party extension: entry_points = [console_scripts] saddle = saddle:main [foo.extension] saddle = saddle
🌐
Amirrachum
amirrachum.com › amir rachum's blog › python entry points explained
Python Entry Points Explained - Amir Rachum's Blog
July 28, 2017 - console_scripts, they say, is a special type of entry point. setuptools reads its content as "<console script name> = <python object path>" and creates an appropriate script when your package is installed.
🌐
GitHub
github.com › pypa › pip › issues › 9726
Console_scripts entrypoints hidden behind extras are always installed · Issue #9726 · pypa/pip
March 22, 2021 - According to [1] it should be possible to define console_scripts which are only installed if an extra is specified, i.e. example = example:my_func [bar]. Despite this is seems that pip ignores the section in square brackets and always installs all console_scripts. [1] https://packaging.python.org/specifications/entry-points/ The example command is only installed if the example package is installed with pip install .[bar]. ... $ cat pyproject.toml [build-system] requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" $ cat setup.cfg [metadata] name = example version = 1.0.1 [options] package_dir= =src packages=example [options.packages.find] where=src [options.entry_points] console_scripts = example = example:my_func [bar] $ cat src/example/__init__.py print("Hello world") def my_func(): print("Hello again!")
Author: pypa
🌐
Vanrees
reinout.vanrees.org › weblog › 2010 › 01 › 06 › zest-releaser-entry-points.html
Using setuptools entry points - Reinout van Rees
June 1, 2010 - An entry point for zest.releaser is configured like this in your setup.py: entry_points={ #'console_scripts': [ # 'myscript = my.package.scripts:main'], 'zest.releaser.prereleaser.middle': [ 'dosomething = my.package.some:some_entrypoint, ]},
🌐
Stack Overflow
stackoverflow.com › questions › 64878592 › how-to-set-the-bin-scripts-entry-point-in-setup-py
python - How to set the bin scripts entry point in `setup.py`? - Stack Overflow
.py is not needed if you want to follow this approach, just write python setup( # ... entry_points={'console_scripts': ['mycode=mycode.main:main']} # ... ) mycode folder will have a main.py with main function being called at entry this way! Better practice would be to create a setup.cfg file for config 2020-11-17T16:16:00.367Z+00:00 ·
🌐
GitHub
github.com › pypa › setuptools › issues › 4141
entry_points console_scripts not working in setuptools >=69 if `dynamic` is not configured · Issue #4141 · pypa/setuptools
November 27, 2023 - from pathlib import Path from setuptools import setup class Setup: def __init__(self): self.conf = {} self.conf["entry_points"] = {"console_scripts": []} for exe in Path.cwd().glob("src/**/exe/*.py"): exe = exe.relative_to(Path.cwd()) if exe.name == "__init__.py": continue final_exe = ("-".join(x.replace("_", "-") for x in exe.parent.parent.parts[1:]) + "-" + exe.stem.replace("_", "-")) module = ".".join(exe.parent.parts[1:]) + "." + exe.stem self.conf["entry_points"]["console_scripts"].append( f"{final_exe} = {module}:main" ) def __str__(self): return str(self.conf) def __call__(self): setup(**self.conf) SETUP = Setup() if __name__ == "__main__": SETUP()
Author: pypa
Top answer
1 of 3
6

If there is not a good reason to not do so, I would definitely advocate a spin on option 3. As @jonsharp mentions, breaking up your utility into clean units of functionality is a good way to ensure testability. Even the smallest scripts can eventually morph into a much larger program and making sure that you have an extensible API sooner rather than later will alleviate much headache down the road.

The way I'd approach this is:

  1. Break up your code into logical methods with clean and clear I/O
  2. Add unit tests. Having them is never a bad thing.
  3. Rather than using if __name__ == '__main__', create a main() (or similar) method containing your entry point
  4. Use setuptool's setup() function to define the script entry point in your setup.py file.

For example:

from setuptools import setup
setup(
    name='mypackage',
    version='0.1',
    entry_points={
        'console_scripts': [ 'myscript = mypackage.mymodule:main' ],
    }
)

Now, not only is all of your code (including main()) is easily unit testable, but you can still have your console entry point once you've done a python setup.py install|develop.

Any validation I do using, say, ArgumentParser.add_mutually_exclusive_group will not apply when my tool is run as a library instead of a command line script.

Depending on how your API is designed, you may need to add some extra validation to input parameters, but that should likely be there to prevent unexpected input anyways.

Edit: The only time I would use generally use subprocess is when I'm calling into a non-Python application or another Python script that I don't own or have the time to refactor, but the latter only being as a last resort. Most well-written Python utilities will expose both command line utilities and internal API.

2 of 3
3

My guess is that you can pursue #3 by way of #2 and that it won't require anything close to a total refactor. Often in these situations, you just need to make a few adjustments at the entry points and (sometimes) exit points.

  • If needed, wrap your current script in a main() function.

    def main(args, stdin = None):
        if stdin is None:
            stdin = sys.stdin
    
        # full script here
    
    if __name__ == '__main__':
        main(sys.argv[1:])
    
  • That function should take a list of strings. When you parse command-line options, operate on args, not sys.argv.

  • Similarly, adjust other parts of your script to avoid direct operations on sys.stdin and related streams. Instead, use the arguments passed to your main() function.

Programatic users (as opposed to command-line users) will pass in a list of strings and any open file handles (or iterables) they want the code to use. Later, if needed, you can make the programatic use more natural by writing a function that knows how to convert typical positional and keyword-style arguments into the list of strings that your option parser needs.

🌐
ACM@UIUC TIL
til.acm.illinois.edu › python › setuptools_entry_points
Setuptools entry points | ACM@UIUC TIL
April 29, 2025 - When building a Python package, the most common way of exposing a command line interface (CLI) is to use the “scripts” keyword in setup.py. # file: setup.py from setuptools import setup setup( name='my_package', scripts=['scripts/my_cli'], ... ) However, entry points represents an improved and cross-platform approach to accomplish the same goal. # file: setup.py from setuptools import setup setup( name='my_package', entry_points={ 'console_scripts': [ 'my_cli': 'my_package.cli:main' ] }, ...
Top answer
1 of 3
297

An "entry point" is typically a function (or other callable function-like object) that a developer or user of your Python package might want to use, though a non-callable object can be supplied as an entry point as well (as correctly pointed out in the comments!).

The most popular kind of entry point is the console_scripts entry point, which points to a function that you want made available as a command-line tool to whoever installs your package. This goes into your setup.py script like:

entry_points={
    'console_scripts': [
        'cursive = cursive.tools.cmd:cursive_command',
    ],
},

I have a package I've just deployed called cursive.tools, and I wanted it to make available a "cursive" command that someone could run from the command line, like:

$ cursive --help
usage: cursive ...

The way to do this is define a function, like maybe a cursive_command function in the file cursive/tools/cmd.py that looks like:

def cursive_command():
    args = sys.argv[1:]
    if len(args) < 1:
        print "usage: ..."

and so forth; it should assume that it's been called from the command line, parse the arguments that the user has provided, and ... well, do whatever the command is designed to do.

Install the docutils package for a great example of entry-point use: it will install something like a half-dozen useful commands for converting Python documentation to other formats.

2 of 3
234

EntryPoints provide a persistent, filesystem-based object name registration and name-based direct object import mechanism (implemented by the setuptools package).

They associate names of Python objects with free-form identifiers. So any other code using the same Python installation and knowing the identifier can access an object with the associated name, no matter where the object is defined. The associated names can be any names existing in a Python module; for example name of a class, function or variable. The entry point mechanism does not care what the name refers to, as long as it is importable.

As an example, let's use (the name of) a function, and an imaginary python module with a fully-qualified name 'myns.mypkg.mymodule':

def the_function():
   "function whose name is 'the_function', in 'mymodule' module"
   print "hello from the_function"

Entry points are registered via an entry points declaration in setup.py. To register the_function under entrypoint called 'my_ep_func':

    entry_points = {
        'my_ep_group_id': [
            'my_ep_func = myns.mypkg.mymodule:the_function'
        ]
    },

As the example shows, entry points are grouped; there's corresponding API to look up all entry points belonging to a group (example below).

Upon a package installation (ie. running 'python setup.py install'), the above declaration is parsed by setuptools. It then writes the parsed information in special file. After that, the pkg_resources API (part of setuptools) can be used to look up the entry point and access the object(s) with the associated name(s):

import pkg_resources

named_objects = {}
for ep in pkg_resources.iter_entry_points(group='my_ep_group_id'):
   named_objects.update({ep.name: ep.load()})

Here, setuptools read the entry point information that was written in special files. It found the entry point, imported the module (myns.mypkg.mymodule), and retrieved the_function defined there, upon call to pkg_resources.load().

Calling the_function would then be simple:

>>> named_objects'my_ep_func'
hello from the_function

Thus, while perhaps a bit difficult to grasp at first, the entry point mechanism is actually quite simple to use. It provides an useful tool for pluggable Python software development.

🌐
C0DE MAN1AC
virresh.wordpress.com › 2018 › 07 › 18 › console-endpoints-in-python-setup-tools-magic
Console endpoints in Python: setup tools magic ! – C0DE MAN1AC
July 21, 2018 - The way this works is the main package supports discovery of endpoints using the function iter_entry_points from setuptools. In reality, console_scripts are really just a special case of entrypoints in general. Instead of having to define a unique name for a console_script function, we have it’s name fixed to console_script in the setup.py.
🌐
GitHub
github.com › bazelbuild › rules_python › issues › 318
Using a Python Package with 'console_scripts' · Issue #318 · bazel-contrib/rules_python
May 26, 2020 - Hi, I have a Python package that generate console script as entry-point. The setup.py looks like so: entry_points={"console_scripts": ["my_package" = "my_package.cli:main"]} I would like to call this python command line script from withi...
Author: bazel-contrib