Releasing my Python Project
Tags: python, pypi, opensource, programming
Published: 2023-11-27 02:25:23

I have been working on [til-page-builder](https://github.com/Amnish04/til-page-builder) for this whole semester, and as the term approaches its end, I thought it was a good time to **release** the project.

I have published the package to [Python Package Index](https://pypi.org/), commonly called **PyPi**, and in this post, I'll be sharing the steps I had to follow in the process.

![PyPi](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dol39ygq8nxrxkfzvv85.png)

**Note:** I will be using [TestPyPi](https://test.pypi.org/) in this tutotal, which is a separate server for testing python packages, since the original Pypi has temporarily suspended new users registration.

![Registration Suspended](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7vm6qwyiskpltka4mu3.jpg)

## Table of Contents
&nbsp;1. [Registering as a New User 🧑🏻](#registering-as-a-new-user)
&nbsp;2. [Creating an API Key 🔑](#creating-an-api-key)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.1. [Enabling MFA 🔒](#enabling-mfa)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.2. [Getting the Token 💳](#getting-the-token)
&nbsp;3. [Adjusting Project Structure](#adjusting-project-structure)
&nbsp;4. [Configuring a Build Backend](#configuring-a-build-backend)
&nbsp;5. [Generating Distribution Packages](#generating-distribution-packages)
&nbsp;6. [Publishing the Package](#publishing-the-package)
&nbsp;7. [Installing the Package 💻](#installing-the-package)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 7.1. [User Testing](#user-testing)
&nbsp;8. [Conclusion 🎇](#conclusion)
&nbsp;9. [Attributions](#attributions)

## Registering as a New User 🧑🏻

The very first step is to [create a new PyPi account](https://pypi.org/account/register/) that we will use to publish our packages. 

![Test PyPi](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnu71co2albphgyowpvm.png)

Once we have an account, we can login using those credentials 

![Login to PyPi](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rntrdds9syhel05ykldi.png)

## Creating an API Key 🔑

After creating and logging into your account, the next step is to create an **API key** that will be used to **upload** packages to PyPi under our account.

### Enabling MFA 🔒

**However**, to be eligible to create an API key, you first need to go to Account Settings, and enable 2 factor authentication - which is again a **2-step process**. You first generate and store **recovery codes** for your account, after which you can enable [MFA](https://www.onelogin.com/learn/what-is-mfa).
Be **advised** that only **one** authentication application can be linked and in my case, I used [Microsoft Authenticator](https://www.microsoft.com/en-ca/security/mobile-authenticator-app) - a pretty popular and standard option.

![MFA](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15lmhgjl0d2rdl9vw0i3.png)

### Getting the Token 💳

Getting an [API token](https://www.wallarm.com/what/what-is-an-api-token-quick-guide) is pretty simple once you are eligible. All you have to do is go to the **API Token** section under Account Settings and click on **Add Token**.

![Add Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4v18ad6cwjok6dzsaha.png)

which will take you to the following screen

![Create Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqr5jd34kdlgqvhasuz5.png)

Here, you need to **name** your token and set a scope for its validity, which can be a **specific project** or **all projects** like I have selected.

Now click on **Create Token** and you'll see your token in the following format.

![Generated Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/azras2gxrjvw6yz76d5e.png)

## Adjusting Project Structure

The most **crucial** step in the entire process is to reorganize the project into a structure expected by PyPi.

![Package Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3q3u0wgv2zfghehld2o.png)

Let's discuss the role of each file in the figure.

**1. LICENSE:** This file contains information about the rights and permissions granted to users regarding the use, modification, distribution, and sharing of the software. I already had an [MIT License](https://opensource.org/license/mit/) in [my project](https://github.com/Amnish04/til-page-builder/blob/master/LICENSE).
**2. pyproject.toml:** It is a configuration file typically used for specifying build requirements and backend build systems for Python projects. I was already using this file for [Black](https://black.readthedocs.io/en/stable/) code formatter configuration.
**3. README.md:** Used as a documentation file for your project, typically includes project overview, installation instructions and optionally, contribution instructions.
**4. example_package_YOUR_USERNAME_HERE:** One big change I had to face was **restructuring** my project, essentially **packaging** all files in this directory. The name of this directory should be what you want to name your package and **shoud not** conflict with any of the existing packages.
Of course, since its a [Python Package](https://www.programiz.com/python-programming/package), it needs to have an [\_\_init\_\_.py](https://stackoverflow.com/questions/448271/what-is-init-py-for).
**5. tests/:** This is where you put all your unit and integration tests, I think its optional as **not all** projects will have tests.
The rest of the project remains as is.

⚠️**Important Note:** Make sure you are using [absolute imports](https://realpython.com/absolute-vs-relative-python-imports/) throughout the project, as **relative imports** might not behave as you expect, when your package is installed on other machines.

I spent quite a while to figure it out.

![Module Errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6j5r47q6dr2m2ojx1l4h.png)

and had to go through **3 patch fixes** to finally get it to work.

![Installing Latest Package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r4uze0zrdh6u3o3iqe6e.png)

## Configuring a Build Backend

From official [documentation](https://packaging.python.org/en/latest/tutorials/packaging-projects/#choosing-a-build-backend),
> Tools like pip and build do not actually convert your sources into a distribution package (like a wheel); that job is performed by a build backend. The build backend determines how your project will specify its configuration, including metadata (information about the project, for example, the name and tags that are displayed on PyPI) and input files.

I used [Hatchling](https://hatch.pypa.io/latest/config/build/) for my purpose, but there are other options available as well.

To configure **Hatchling** as your build backend, add the following lines to `pyproject.toml` file.

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
```

And the following **metadata** needs to be set
```toml
[project]
name = "til_page_builder"
version = "1.0.3"
authors = [
  { name="Amnish Singh Arora", email="amnishsingh04@gmail.com" },
]
description = "A command-line tool for authoring \"Today I Learned\" posts in Markdown, which can be converted to HTML for publishing on the web."
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]
```

I started out with version `1.0.0`, but got to `1.0.3` while getting everything to work correctly.

## Generating Distribution Packages

The next step is to generate [distribution packages](https://packaging.python.org/en/latest/glossary/#term-Distribution-Package) by building the project. These are the files that are uploaded to PyPi and installed as packages on systems.

First you need to make sure you have the latest [package builder](https://packaging.python.org/en/latest/key_projects/#build).

```
py -m pip install --upgrade build
```

Now make sure you're at the root of your project (at the same level as `pyproject.toml`), and run

```
py -m build
```

![Build Package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4lm1u9s711f7nw27cxaj.jpg)

Once it is successfull, you'll see a [tarball](https://en.wikipedia.org/wiki/Tarball) and a [whl](https://realpython.com/python-wheels/) file generated in the `dist` directory.

![Dist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ypnmkd8gzrcaexdqj4wa.png)

These are the files that need to be uploaded/published to PyPi.

## Publishing the Package

Congratulation for making to the **last step** of the process.
We'll use [twine](https://packaging.python.org/en/latest/key_projects/#twine) to upload the distribution packages.

```
py -m pip install --upgrade twine
```

Once installed, we can run

```
py -m twine upload --repository testpypi dist/*
```

![Upload Package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ykczwpgf4p9fic7zm0v3.png)

And that's how I was able to publish my first python package to get it in the hands of users.

📌 Notice that we are using a `--repository` flag to upload the package to **TestPyPi** instead of the real thing due to the issue I described in the beginning. But we can omit this flag to upload to the real server if we have the credentials.

## Installing the Package 💻

Now that we have the package published, its time to install and test if it works when installed.

```
py -m pip install --index-url https://test.pypi.org/simple/ --no-deps til-page-builder==<version_number_optional>
```

![Installing Latest Package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5nw7e4fe7fpzuvf7iz11.png)

Let's try to use it now 🤞

```
py -m til_page_builder.til_builder_main -v
```

```
py -m til_page_builder.til_builder_main -h
```

![Using Package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2z7qotttxrzkg5lxeyy.png)

That works. Let's try the main functionality now.

```
py -m til_page_builder.til_builder_main examples --output voila
```

And that works!

![That works](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ozztv8s1h1eoxy8i2lat.png)

### User Testing

I also updated the [README](https://github.com/Amnish04/til-page-builder/blob/master/README.md) file of my project and asked [Katie](https://dev.to/katiel) to help me with testing it locally.

And thanks to her, I was able to find a **critical defect**. In my distribution package, I wasn't specifying the dependencies that had to be installed which is why she couldn't run my tool on her system.
She asked me to refer to her [blog](https://dev.to/katiel/software-release-using-pypi-1ddi) and I was able to fix this problem by adding dependencies to `pyproject.toml` and creating a `setup.py` file.

[**pyproject.toml**](https://github.com/Amnish04/til-page-builder/blob/master/pyproject.toml)
```
...
...
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "til_page_builder"
version = "1.0.4"
...
...
dependencies = [
    "yattag==1.15.1",
    "tomli==2.0.1"
]
...
...
```

[**setup.py**](https://github.com/Amnish04/til-page-builder/blob/master/setup.py)
```python
from setuptools import setup, find_packages
from src.til_page_builder.version import __version__ as version

with open("requirements.txt") as f:
    requirements = f.read().splitlines()

setup(
    name="til_page_builder",
    version=version,
    packages=find_packages(),
    install_requires=requirements,
    entry_points={
        "console_scripts": [
            "til_page_builder=til_page_builder.til_builder_main:main",  # Adjust 'module_name' and 'main' accordingly
        ],
    },
    python_requires=">=3.8",
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    author="Amnish Singh Arora",
    author_email="amnishsingh04@gmail.com",
    description="A command-line tool for authoring 'Today I Learned' posts in Markdown, which can be converted to HTML for publishing on the web.",
    long_description=open("README.md").read(),
    long_description_content_type="text/markdown",
    url="https://github.com/pypa/sampleproject",
    project_urls={
        "Homepage": "https://github.com/pypa/sampleproject",
        "Issues": "https://github.com/pypa/sampleproject/issues",
    },
)
```

## Conclusion 🎇

In this post, I shared my experience uploading my first ever Python Package to PyPi, as I wrap up my open source class for this semester. I am really glad that I was able to carry my project to its [1.0 release](https://test.pypi.org/project/til-page-builder/), and will continue making improvements so it actually becomes a useful tool.

![Release 1.0](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5en2kfzdwy6wgqy8gmh.png)

Here's one to the 1.0 [release](https://github.com/Amnish04/til-page-builder/releases) 🍾

![Latest Release](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqye7kv4vo0ju68db2c3.png)


## Attributions
Cover Photo by <a href="https://unsplash.com/@hiteshchoudhary?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Hitesh Choudhary</a> on <a href="https://unsplash.com/photos/person-holding-sticky-note-D9Zow2REm8U?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>