DEV Community

Cover image for Building and Releasing a Python CLI
Namatuzio
Namatuzio

Posted on

Building and Releasing a Python CLI

So this week I packaged and released the final version of my CLI, Tiller. It is now officially downloadable through PyPI as well!

This was something totally new to me so there was lots to be learned!

Package and Release method

One of the first things to consider when releasing any kind of project is where users will be able to find it. Considering I used Python, I decided to go with a popular package hosting site called PyPi. The best part is that there's a very useful guide on how to do just what I wanted to do!

This guide helped tremendously with getting everything uploaded and working, but don't worry if you decide to go with other package distribution sites or platforms, there are bound to be many tutorials on how to use it!

So to get started, I refactored my file structure within my repo to separate test and functional code. I also added an empty __init__.py file, which I was unsure of its use until today. __init__.py is a handy file that tells Python what to do when a package is first installed. I also learned that without an init file, Python packages are treated as namespace packages, which for 99% of use cases, is completely unnecessary, especially for my CLI which should be run in a self-contained package.

Another concept I learned was about build backends, an import step which is used to initialize and install any dependencies of the app you're packaging. Since the tutorial went with using Hatch that is also what I went with, though it didn't provide a lot of useful details especially because it didn't show how to add any dependencies, so I took a look at the docs which were very nice and simple to follow.

Then I set all the metadata required for my app such as version numbers, names, authors, etc. and moved on to building my application. This was done using py -m build which generated 2 types of files; a tar.gz which is a source distribution file and a .whl which is a built distribution file.

A source distribution file is a file that contains raw information about a package and contains only the essential files required for installing from tools like pip or being turned into a built distribution.

A built distribution file is a pre-built package file that simply needs to find the right home for the package to run as expected. Both were concepts that were somewhat new to me. I've seen built distribution files previously but had no idea what the file extension really meant.

Anyways, I then installed twine which would allow me to finally upload my CLI to PyPi. Running py -m twine upload --repository testpypi dist/* took everything from the previously made dist file and uploaded it onto PyPi to be openly downloaded. After some bug fixing and a few patch release later, everything was running smoothly, and tiller was finally ready to be used.

Problems

During the packaging process, I ran into a few problems, it's the reason why my tags are all over the place in terms of versioning. Some of the changes were so minuscule I overlooked adding a new patch version.

A lot of my problems came from my pyproject.toml file which after uploading everything to PyPi, would not properly load everything which made me lost on how to run my app. It was at this point that I actually decided to take a deeper look at the hatchling docs to find out I was missing the dependencies part. I also decided to add an entry point into my app, allowing users to simply the call to running tiller by adding the following to my pyproject.toml

[project.scripts]
tiller = "src.namatuzio_tiller_package.main:app"
Enter fullscreen mode Exit fullscreen mode

This lets users call all my functionality by calling tiller as opposed to ./main.py when the app is installed.

Another issue was forgetting to update certain aspects of my code. For example, when I tested my -v functionality, I had the check set to a default string, when I could've simply imported my __version__ variable from the main file to have it be the same in multiple locations.

Testing other CLIs

The CLI I decided to test was Txt2StaticHTML a package of similar functionality, though done in C#. It was interesting testing it mainly because of how different NuGet packages are from Python packages. I had to open up VS2022 and install the NuGet package. Everything was inside of the namespace the repo owner had developed using the namespace allowed all of the functionality to become available. Calling

Converter.Run(<DIR>);
Enter fullscreen mode Exit fullscreen mode

Ran the default functionality, and even more could be called using a string array. I didn't have much feedback to give especially since the README drew everything out for me. The functionality was all there and it was pretty simple to use and a great refresher on C#.

Having my CLI tested

The owner of Txt2StaticHtml also tested my package out. He installed everything from the PyPi page using pip and followed the rest of my README to test the functionality. There wasn't a lot of feedback from the tester because I tried to make my README as descriptive as possible.

Try it for yourself!

Did you want to try out Tiller? Well if that's a yes then worry not! It's extremely simple to download and use.

Installation

Please note that Tiller requires Python 3.11 or higher to run due to the use of TOMLib.

pip install -i https://test.pypi.org/simple/ namatuzio-tiller
Enter fullscreen mode Exit fullscreen mode

Package Usage:

tiller [OPTIONS] DIR
Enter fullscreen mode Exit fullscreen mode

Options:

--version, -v  Print the current version of Tiller.
--help, -h     Print the help message.
--config, -c   Specify the path to a TOML config file to be used.
--output, -o   Change the output directory of the .html files.
--lang, -l    Specify the language of the generated HTML file.
Enter fullscreen mode Exit fullscreen mode

Display the current version of Tiller:

.\main.py --version (or -v)

Tiller Version: 1.1.0 
Enter fullscreen mode Exit fullscreen mode

Transform a file through a relative path:

tiller .\example.txt
Converted example.txt to example.html
Enter fullscreen mode Exit fullscreen mode
.\example.txt

Hello

World\\

How are you?
Enter fullscreen mode Exit fullscreen mode
.\til\example.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Hello</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <h1>Hello</h1>
    <body>
        <p>Hello</p>
        <p>World</p>
        <p>How are you?</p>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Generate HTML file with a different language:

tiller --lang fr .\example.txt

Converted example.txt to example.html
Enter fullscreen mode Exit fullscreen mode
.\example.txt

Hello

World

How are you?
Enter fullscreen mode Exit fullscreen mode
.\til\example.html
<!DOCTYPE html>
<html lang="fr">
    <style>
    body {
        background-color: rgb(0, 116, 145);
        text-align: center;
        color: white;
        font-family: Arial, Helvetica, sans-serif;
        font-size: xx-large;
    }
    </style>
    <head>
        <meta charset="UTF-8">
        <title>example</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <h1>example</h1>
    <body>
        <p>Hello</p>
        <p>World</p>
        <p>How are you?</p>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Transform file(s) using a TOML config file

tiller --config (or -c) .\examples\TOMLExample.toml .\examples

Converted example.txt to example.html
Converted example2.txt to example2.html
Converted example3.md to example3.html
example4.html is not a .txt file or a .md file. Skipping...

TOMLExample.toml is not a .txt file or a .md file. Skipping...
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
mmascioni profile image
Matt Mascioni

Congrats on the release of your project! Nice walkthrough around what you encountered in the packaging process 😀