How to create a CLI in Python that can be installed with PIP?

Question:

As the title suggests, I’m trying to make a python script accessible from the command line. I’ve found libraries like click and argv that make it easy to access arguments passed from the command line, but the user still has to run the script through Python.

Instead of

python /location/to/myscript.py

I want to be able to just do

myscript

from any directory

From what I understand, I can achieve this on my computer by editing my PATH variables. However, I would like to be able to simply do:

pip install myscript

and then access the script by typing myscript from anywhere. Is there some special code I would put in the setup.py?

Asked By: Param

||

Answers:

You can do this with setuptools

an example of a nice setup.py (say your package requires pandas and numpy):

import setuptools
setuptools.setup(
    name='myscript',
    version='1.0',
    scripts=['./scripts/myscript'],
    author='Me',
    description='This runs my script which is great.',
    packages=['lib.myscript'],
    install_requires=[
        'setuptools',
        'pandas >= 0.22.0',
        'numpy >= 1.16.0'
    ],
    python_requires='>=3.5'
)

Your directory should be setup as follows:

[dkennetz package]$ ls
lib scripts setup.py

inside lib would be:

[dkennetz package]$ ls lib
myscript

inside of myscript would be:

[dkennetz package]$ ls lib/myscript
__main__.py
__init__.py
helper_module1.py
helper_module2.py

main would be used to call your function and do whatever you want to do.

inside scripts would be:

[dkennetz package]$ ls scripts
myscript

and the contents of myscript would be:

#!/usr/bin/env bash

if [[ ! $@ ]]; then
    python3 -m myscript -h
else
    python3 -m myscript $@
fi

then to run you do: python setup.py install

which will install your program and all of the dependencies you included in install_requires=[] in your setup.py and install myscript as a command-line module:

[dkennetz ~]$ myscript
Answered By: d_kennetz

Assuming you are in the bash shell and python 3 is installed and you want to be able to do what you are requesting, you will need to append the path of the script file to your PATH variable in your .bash_profile file in your home directory. Also, in your python script file, you need to have something similar to the following as the first line of the script:

#!/usr/bin/env python3

Additionally, you can remove the extension (.py) from the script file, such that, as in my example above, the filename is a script in contrast to script.py.

You will also need to set the permission of your filename to

chmod 755 filename

If you want the script to be accessible system-wide, you will need to modify /etc/profile and add to the bottom of the file:

export PATH=$PATH:/path/to/script

Alternatively, if you move the python script file to /usr/local/bin, it may not be necessary to make any profile changes as this directory is often already in the PATH.

To see the value of PATH issue the following command at the shell

echo $PATH
Answered By: Daniel Reimann

Use console_scripts to hook to a specific Python method (not a whole executable), setup.py file:

from setuptools import setup
setup(
    ...
    entry_points = {
        'console_scripts': ['mybinary=mymodule.command_line:cli'],
    },
    name='mymodule',
    ...
)

the command_line.py script would be:

import mymodule

def cli():
    print("Hello world!")

and the project directory would look like this:

myproject/
    mymodule/
        __init__.py
        command_line.py
        ...
    setup.py

Setuptools will generate a standalone script ‘shim’ which imports your module and calls the registered function.

That shim allows you to call mybinary directly and ensures it’s correctly invoked by python. It provides platform-specific shims (i.e., on Windows it generates a .exe).

See packaging documentation for more details.

Answered By: Tombart

I know this question is older and for a project using setuptools definitely use Tombart’s answer

That said, I have been using poetry and that uses a .toml file and if that’s what you use, and since this is likely what others will search for here’s how you package a script with a toml file (at least with poetry)

[project.scripts]
myscript = "mybinary=mymodule.command_line:cli"

Not sure if this works for flit or any other package managers but it works for poetry

Answered By: Jab