Developing Python CLIs with Poetry

2020 Feb 17

Poetry brings a simplified interface to Python’s packaging and distribution system. It makes all the right things easy to do.

Here’s how you use it to develop a new Python CLI project.

Getting started

We’ll make a CLI called hello-snake, in a package and project called snake. First run poetry new to create the project skeleton.

tom@tomtop 🎪:~/tmp
% poetry new snake
Created package snake in snake

The resulting directory tree:

tom@tomtop 🎪:~/tmp
% tree snake
snake
├── README.rst
├── pyproject.toml
├── snake
│   └── __init__.py
└── tests
    ├── __init__.py
    └── test_snake.py

Change to the snake project directory. Poetry will manage a virtualenv for the project, tied to the project path and Python version. Create the virtualenv and open a pre-activated shell with poetry shell.

tom@tomtop 🎪:~/tmp
% cd snake
tom@tomtop 🎪:~/tmp/snake
% poetry shell
Creating virtualenv snake--jYlyyG6-py3.8 in /Users/tom/Library/Caches/pypoetry/virtualenvs
Spawning shell within /Users/tom/Library/Caches/pypoetry/virtualenvs/snake--jYlyyG6-py3.8
tom@tomtop 🎪:~/tmp/snake
% . /Users/tom/Library/Caches/pypoetry/virtualenvs/snake--jYlyyG6-py3.8/bin/activate
(snake--jYlyyG6-py3.8) tom@tomtop 🎪:~/tmp/snake
%

Our CLI entrypoint will be in the snake.console module. Create it in snake/console.py, next to snake/__init__.py, and add a run() function to serve as entrypoint.

def run():
    print("🐍")

Our project will ship a single executable, named hello-snake. Add it to the tool.poetry.scripts section of pyproject.toml.

[tool.poetry.scripts]
hello-snake = 'snake.console:run'`

This specifies that the program hello-snake executes the function run() in the module snake.console.

Install the package and its dependencies to the virtualenv.

(snake--jYlyyG6-py3.8) tom@tomtop 🎪:~/tmp/snake
% poetry install
Updating dependencies
Resolving dependencies... (0.2s)

Writing lock file


Package operations: 9 installs, 0 updates, 0 removals

...
  - Installing snake (0.1.0)

You can now run the new CLI as hello-snake.

(snake--jYlyyG6-py3.8) tom@tomtop 🎪:~/tmp/snake
% hello-snake
🐍

The snake package is installed to the virtualenv as “editable”, so that changes made in the project source directory are reflected immediately whenever you run hello-snake. To try that out, let’s make the snake a little angrier. Update console.py.`

def run():
    print("🐍 tsssssss")

And hello-snake includes the change without having to re-install our package:

(snake--jYlyyG6-py3.8) tom@tomtop 🎪:~/tmp/snake
% hello-snake
🐍 tsssssss

All of this should continue to work the next time you open a poetry shell for this project.

Packaging and distribution

It’s easy to share and distribute your new CLI package. Use poetry build to create tarball and wheel packages under dist/.

tom@tomtop 🎪:~/tmp/snake
% poetry build
Building snake (0.1.0)
 - Building sdist
 - Built snake-0.1.0.tar.gz

 - Building wheel
 - Built snake-0.1.0-py3-none-any.whl

The wheel file can be distributed to users, to be installed to their global Python environment:

tom@tomtop 🎪:~/tmp/snake
% pip install --user dist/snake-0.1.0-py3-none-any.whl
Processing ./dist/snake-0.1.0-py3-none-any.whl
Installing collected packages: snake
Successfully installed snake-0.1.0
WARNING: You are using pip version 19.2.3, however version 20.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

Once the wheel is installed, the users can run hello-snake.

tom@tomtop 🎪:~/tmp/snake
% hello-snake
🐍 tsssssss