How to Build a Python Package

How to Build a Python Package

Python, Golang, Javascript, etc libraries require packaging. It's done to easily distribute code among users thereby avoiding problems in future development.

In Python, a library is distributed through the Python Package Index (PyPI), a public hosting instance for packages.

Building the Python Application

The Python application we will push to PyPI will be a simple hello world (or hello user) application. It will be executable from the command line with a simple hello-world command or hello-world --name YOUR_NAME command.

Folder Structure

First, let's create the initial files needed. We do this with the following:

mkdir -p hello-user/hello_user
touch hello-user/hello_user/main.py hello-user/hello_user/__init__.py

This will result in the following structure:

.
hello-user
    └── hello_user
        ├── __init__.py
        └── main.py

The project has a top-level directory called hello-world and a subdirectory, hello_world which contains two files.

Code

Open the main.py and fill it up with the code below:

#!/usr/bin/env python
"""A CLI tool built with Click """

import click

@click.command()
@click.option("--greeting", default='Hello',help="How do you want to greet?")
@click.option("--name", default="World", help="Who to greet?")
def greet(greeting, name):
    print(f"{greeting} {name}")

if __name__ == '__main__':
    greet()

First, we're making use of an easy-to-use library called click. Click helps us to easily create command-line programs.

click.command shows that a function should be exposed to command-line access. click.option adds an argument to the command-line, automatically linking it to the function parameter of the same name (--greeting to greet and --name to name). click does some work behind the scenes so that we can call our greet method in our main block without parameters that are covered by the options decorators.

These decorators handle parsing command-line arguments and automatically produce help messages.

Packaging the Python Application

Installing the Required Tools

pip install setuptools twine

Setup Project

Now we've developed a simple application to be deployed to PyPI, the next thing to do is to configure the package. To keep things simple (like our python application), we will focus on the minimum amount of files needed to produce a package.

First, let's create setup.py file in the top-level directory. This is how this file looks:

from setuptools import setup, find_packages

setup(
    name="edeediong-hello-user",
    version="0.0.1",
    author="Example Author",
    author_email="author@example.com",
    url="https://edeediong.me",
    description="A hello user package",
    packages=find_packages(),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    install_requires=["click"],
    entry_points={
        "console_scripts": ["hello-world = hello_user.main:greet"]
    }
)

The setup.py imports two helpers from the setuptools module: setup and find_packages. The find_package discovers all subpackages (Python files) in the code automatically. The setup is what defines the package.

The documentation explains some of the parameters defined in the setup function and most are self-explanatory. I choose to explain the parameters that make our application special:

  • install_requires: Here we include the package our application depends on.
  • entry_points: Here, we ensure that our package can be executed by the user using hello-world on the terminal.

To produce a source distribution from the packages built, run the following command, and compare the results:

python setup.py sdist

A dist folder is created at the top-level directory with its source distribution. Details below:

.
├── dist
│   └── edeediong-hello-user-0.0.1.tar.gz
├── edeediong_hello_user.egg-info
│   ├── dependency_links.txt
│   ├── entry_points.txt
│   ├── PKG-INFO
│   ├── requires.txt
│   ├── SOURCES.txt
│   └── top_level.txt
├── hello_user
│   ├── __init__.py
│   └── main.py
├── README
└── setup.py

Publishing the Package to PyPI

Python Package Index (PyPI) is a repository of Python software that allows users to host Python packages and also install it. A Python package called twine is used to deploy packages from the local environment to the PyPI repository.

First, we upload the package to TestPyPI to ensure everything works as expected:

twine upload --repository-url https://test.pypi.org/legacy/ dist/edeediong-hello-user-0.0.1.tar.gz

Registration for the test instance of PyPI is important as Twine will ask for username and password to upload packages.

Then we test the package, we install it with pip locally:

pip install -i https://test.pypi.org/simple/ edeediong-hello-user

Finally, we test our application locally to see if it works fine:

$ hello-world
Hello World
$ hello-world --name Eddie
Hello Eddie

We don't want to mess with the Python Package Index which is why we won't upload our package to the actual PyPI. But if we have a live package that needs deployment, we deploy to PyPI with the command below:

twine upload dist/edeediong-hello-user-0.0.1.tar.gz

The difference here is we don't specify a repository url, we deploy straight to PyPI.

Conclusion

At the end of this tutorial, we have built a python application, packaged it, and deployed it to PyPI. I hope this guide helps anyone in the future when building their Python packages.

Glossary

edeediong: this is my name and I used it to avoid naming issues as PyPI needs unique names.