Wouldn’t you like to be able to wrap up all of your cool code in to a package, and then call your code from the command line using a user friendly command name? Sure you do!

Come on, let me show you how.

ubuntu@goodboy:~$ banana
Look ma, I'm an entry point!
ubuntu@goodboy:~$

The above demonstrates some python code from a package being called from the terminal command line using a user-friendly CLI command name.

I chose to name the command ‘banana‘ to make this process much clearer.

Please clone the ‘pkgexample‘ repository to play along with me.


https://github.com/RexBytes/pkgexample
0 forks.
0 stars.
0 open issues.

Recent commits:

ubuntu@goodboy:~/myrepos/rex$ tree -a pkgexample/

pkgexample/
├── .gitignore
├── LICENCE
├── pyproject.toml
├── README.md
└── src
    └── pkgexample
        ├── helloworld.py
        └── __init__.py

2 directories, 6 files
ubuntu@goodboy:~/myrepos/rex$

Create Your Python Console Commands

We are going to step through 3 different console command examples, building up to something you will find useful. We will start with a simple function call, then we will call a function that implements your class methods, and finally a full blown custom python module.

EXAMPLE 1: Simple Function Call

To enable console behavior of python packages we use something called ‘entry points’.

Step 1 – Add Your First Entry Point To __init__.py

Entry points are just python functions, with one important requirement. They are not able to accept arguments. If you want your python command line console to accept arguments you must use argparse within the body of your entry point function to parse user input given via sys.argv .

Entry point functions are added to __init__.py , open up the ‘./src/pkgexample/__init__.py‘ file and add the following.

def my_first_entry_point_funtion():
    print("Look ma, I'm an entry point!")

Step 2 – Add A Project Script Entry To pyproject.toml

The final step is to make a project script entry to your ‘pyproject.toml‘ file.
At the very end of your ‘pyproject.toml‘ make the following addition.

[project.scripts]
banana = "pkgexample:my_first_entry_point_funtion"

Detailed explanation of the entry point project script entry can be found here.
For our simple example, we are following this format.

{console script name} = {package name}:{entry point function name}

I have called the console script name ‘banana‘ to make this whole process clearer.

Step 3 – Build And Test Your Project

I know you are an expert at building and installing python packages, because you’ve read this guide. I am only reproducing the following commands for your convenience.

We will uninstall any version of ‘pkgexample’ we have, rebuild, and then re-install the package with the following commands run from your repository root directory.

ubuntu@goodboy:~/pkgexample$ python3 -m pip uninstall pkgexample ; 
ubuntu@goodboy:~/pkgexample$ rm -rf ./dist/ ; 
ubuntu@goodboy:~/pkgexample$ rm -rf ./src/pkgexample.egg-info/ ;
ubuntu@goodboy:~/pkgexample$ python3 -m build ;
ubuntu@goodboy:~/pkgexample$ python3 -m pip install ./dist/pkgexample-1.0.1-py3-none-any.whl ;

If you now run the command ‘banana‘ your entry point function will be triggered and it will run it’s code.

ubuntu@goodboy:~/pkgexample$ banana
Look ma, I'm an entry point!
ubuntu@goodboy:~/pkgexample$

EXAMPLE 2: Class Method Calls

You may have noticed that the entry point we created above doesn’t use any of the classes available in the ‘pkgexample’ package. Since the whole point of console scripts is to run your code in a more convenient way – I should show you how to do this.

Step 1 – Add Your Second Entry Point To __init__.py

We are going to add a second entry point function that uses the class ‘HelloWorld‘ from ‘pkgexample‘.

Open up your ‘./src/pkgexample/__init__.py‘ file and add apply the following changes.

At the top of the file we first import our class ‘HelloWorld‘ from the python module ‘helloworld‘, from the same local directory. Please note the period before the module name (I’ve seen people forget this, and spend hours debugging).

from .helloworld import HelloWorld 

def my_first_entry_point_funtion():
    print("Look ma, I'm an entry point!")

def my_second_entry_point_funtion():
    helloworld=HelloWorld()
    helloworld.print_message()

We then add our second entry point method which calls the ‘print_message()‘ method from an instance of the class ‘HelloWorld’.

Step 2 – Add Another Project Script Entry To pyproject.toml

You can make as many project script entries to your ‘pyproject.toml‘ file as you want.

Here I name my second console script as ‘apple‘.

[project.scripts]
banana = "pkgexample:my_first_entry_point_funtion"
apple  = "pkgexample:my_second_entry_point_funtion"

Step 3 – Build And Test Your Project

Once you rebuild, and reinstall the package you should be apply to run the command ‘apple’, and also the command ‘banana’.

ubuntu@goodboy:~/pkgexample$ apple
Hello World
ubuntu@goodboy:~/pkgexample$ banana
Look ma, I'm an entry point!

EXAMPLE 3: Custom Python Module Calls

One last example. You can also have entry points inside their own script files.

Step 1 – Create A New Python Module

Let’s start by creating a new python module called ‘myscript.py’ in the package directory under the src directory.

ubuntu@goodboy:~/myrepos/rex/$ tree -a pkgexample/
pkgexample/
├── .gitignore
├── LICENCE
├── pyproject.toml
├── README.md
└── src
    └── pkgexample
        ├── helloworld.py
        ├── __init__.py
        └── myscript.py       <--- New File

2 directories, 7 files
ubuntu@goodboy:~/myrepos/rex$

Step 2 – Write Your Entry Point Function

We’re going to stick to the script and write the same entry point function we did earlier. Please add the following code to the ‘myscript.py‘ file.

from .helloworld import HelloWorld 

def main():
    helloworld=HelloWorld()
    helloworld.print_message()

Step 3 – Add Another Project Script Entry To pyproject.toml

I have decided to call this console command ‘carrot‘. The [project.scripts] table at the end of your TOML file should look like this.


[project.scripts]
banana = "pkgexample:my_first_entry_point_funtion"
apple = "pkgexample:my_second_entry_point_funtion"
carrot = "pkgexample:myscript.main"

Please note the slightly different syntax for a script entry for ‘carrot’.
We have to name the script the entry function is contained in.

{console script name} = {package name}:{scriptfilename}.{entry point function name}

Step 4 – Repackage, Reinstall, And Run Your New Commands.

Once you repackage and reinstall you should be able to run the following three terminal commands, including the new command ‘carrot‘.

ubuntu@goodboy:~$ banana
Look ma, I'm an entry point!
ubuntu@goodboy:~$ apple
Hello World
ubuntu@goodboy:~$ carrot
Hello World
ubuntu@goodboy:~$

DOWNLOAD: Code Changes Available As Package

For convenience I have made all of the above changes publicly available as a new package called ‘pkgexampleconsole‘.

GitHub Repository

You can clone a copy from the following location.


https://github.com/RexBytes/pkgexampleconsole
0 forks.
0 stars.
0 open issues.

Recent commits:

ubuntu@goodboy:~/myrepos/rex$ tree -a pkgexampleconsole/
pkgexampleconsole/
├── .gitignore
├── LICENCE
├── pyproject.toml
├── README.md
└── src
    └── pkgexampleconsole
        ├── helloworld.py
        ├── __init__.py
        └── myscript.py

2 directories, 7 files
ubuntu@goodboy:~/myrepos/rex$

Live Package At pypi.org

If you prefer, you can immediately install and play with the ‘banana’, ‘apple’, and ‘carrot’ commands.

https://pypi.org/project/pkgexampleconsole/
ubuntu@goodboy:~$ python3 -m pip install pkgexampleconsole
Defaulting to user installation because normal site-packages is not writeable
Collecting pkgexampleconsole
  Downloading pkgexampleconsole-1.0.2-py3-none-any.whl (3.6 kB)
Installing collected packages: pkgexampleconsole
Successfully installed pkgexampleconsole-1.0.2

You should be able to immediately run the python console commands as follows…


ubuntu@goodboy:~$ banana
Look ma, I'm an entry point!
ubuntu@goodboy:~$ apple
Hello World
ubuntu@goodboy:~$ carrot
Hello World
ubuntu@goodboy:~$

WARNING! Common Gotchas. Pay attention.

Here are some things to watch out for incase you are experiencing difficulties.

Add Your Local User Bin Directory To Path

You must have your local user binary directory on your user path. From a terminal, run the following command.

export PATH=/home/ubuntu/.local/bin/:$PATH

If you do not, you will find that the following error will be given when trying to run your python console commands.

WARNING: The scripts apple, banana and carrot are installed in '/home/ubuntu/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.

Here is a full run through demonstrating what happens if your path isn’t set, and how after correcting your path the problem immediately resolved.

ubuntu@goodboy:~$ python3 -m pip install pkgexampleconsole
Defaulting to user installation because normal site-packages is not writeable
Collecting pkgexampleconsole
  Using cached pkgexampleconsole-1.0.2-py3-none-any.whl (3.6 kB)
Installing collected packages: pkgexampleconsole
  WARNING: The scripts apple, banana and carrot are installed in '/home/ubuntu/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed pkgexampleconsole-1.0.2
ubuntu@goodboy:~$ banana
bash: banana: command not found
ubuntu@goodboy:~$ export PATH=/home/ubuntu/.local/bin/:$PATH
ubuntu@goodboy:~$ banana
Look ma, I'm an entry point!

You can add the export path to the end of your ~/.profile file so that your path is set on boot,

# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
    fi
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

export PATH=/home/ubuntu/.local/bin/:$PATH

Entry Points With Interactive Install

I cover interactive installs elsewhere. If you do use interactive installs with console packaging the one item that doesn’t change unless your repackage and reinstall is the entry points.

[project.scripts]
banana = "pkgexample:my_first_entry_point_funtion"

You can make changes to all other code and see an immediate difference, but if you make even the slightest change to the above entry point you will need to rerun the interactive install commands. If you change ‘banana‘ to ‘corn‘ you will need to rerun,

python3 -m pip install --no-build-isolation -e .

Please read up more on interactive installs here.

2 thoughts on “Python Packages As Callable Console Commands”

Leave a Reply