Publish a Python Project in 5 Steps
Published: 2021-11-27 03:10:01

This week, I've published [Silkie](https://github.com/oliver-pham/silkie), my Python static site generator (SSG), to [PyPI](https://pypi.org/project/silkie/). It may seem daunting at first, but you can publish your own Python package by following these 5 steps.

## Start

To avoid most painful misconfiguration, I organized my project to follow a standard directory structure:

```
silkie
├── LICENSE
├── README.md
├── pyproject.toml
├── requirements.txt
├── setup.cfg
├── silkie
│   ├── __init__.py
│   ├── __main__.py
│   ├── cli.py
│   ├── ...
├── tests
└── tox.ini
```

You might want to use [Cookiecutter](https://cookiecutter.readthedocs.io/en/latest/README.html#a-pantry-full-of-cookiecutters) to set up a template for your Python project if you are starting from scratch.

We'll be uploading our package to PyPI, so we should have an account created on PyPI. In addition, you can also create another account on [TestPyPI](https://test.pypi.org/) to test your package first before an official publication.

## Configure

[Setuptools](https://setuptools.pypa.io/en/latest/), a popular and extensible library for packaging Python projects, seems like a perfect choice for my project. Configuration steps for it includes writing two files: `pyproject.toml` and `setup.cfg`.

The `pyproject.toml` file simply declares the requirements for building our package:

```toml
[build-system]
requires = [
    "setuptools>=42",
    "wheel"
]
build-backend = "setuptools.build_meta"
```

Based on your project architecture, `setup.cfg` can be configured accordingly. I think [this tutorial](https://packaging.python.org/tutorials/packaging-projects/) does a better job explaining the details, so you should definitely check it out. As for Silkie, this is how I set it up:

```ini
[metadata]
name = silkie
version = 1.0.7
...
description = Static site generator with the smoothness of silk
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/oliver-pham/silkie
project_urls =
    Bug Tracker = https://github.com/oliver-pham/silkie/issues
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: MIT License
    Operating System :: OS Independent

[options]
packages = silkie
python_requires = >=3.9
install_requires =
    click >= 8.0.0
    markdown >= 3.3.0
    yattag >= 1.14.0
    python-frontmatter >= 1.0.0

[options.entry_points]
console_scripts = 
    silkie = silkie.cli:silkie
```

Some notes you may want to consider to avoid some misconfigurations (that I made):
- `packages`: I specify `silkie` directory directly because my project only has a single package. If you have multiple packages nested under a single directory, you should use `find:` and follow the instructions in [Setuptools documentation](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#using-find-or-find-packages)
- `install_requires`: specify all third-party dependencies that your project use. Simply put, anything that you `pip install` and import in your code should be declared here

## Build

Once you are done with configuration, let's build your Python package.

Before we can build it, make sure you have the latest version of `build` installed on your machine.

```
$ pip install --upgrade build
```

Then, just run build in the root directory of your project:

```
$ python -m build
```

You should have a `.whl` file and a `.tar.gz` file inside your `dist/` directory when the build process is completed.

## Test

Before publishing your package, make sure that the newly built distribution contains all the necessary packages and files. You can run this command to see what's inside your distribution:

```
$ tar tzf <your-package-name>.tar.gz
```

## Publish

To publish your Python package to PyPI, you must have `twine` installed on your machine. You can install its latest version with `pip`:

```
$ pip install --upgrade twine
```

Once it's installed, run this command to upload your distribution to TestPyPI for testing:

```
$ twine upload --repository TestPyPI dist/*
```

When prompted, you can either enter your account credentials or use an API token like this:

```
Enter your username: __token__
Enter your password: <your-API-token>
```

When your package can be uploaded to TestPyPI and installed on your local machine, you're ready to upload it to PyPI:

```
$ twine upload dist/*
```

Just like TestPyPI, your credentials or API token (**for PyPI**) can be used to authenticate your upload. Congratulations! You can now ask others to install and test your package.

As for Silkie, I've asked some of my friends to test the SSG. One of them found a [critical bug](https://github.com/oliver-pham/silkie/issues/33) due to a lack of testing. Another one suggested a piece of ASCII Art to me for Silkie, which was really nice. Overall, we have only tested it on Mac and Linux. If you happen to encounter any bug or issue while using Silkie, feel free to [open an issue](https://github.com/oliver-pham/silkie/issues/new/choose) on GitHub.

## Final thoughts
In general, I think publishing a Python package isn't really complex, but I was so nervous that I made a lot mistakes. It's fine if you mess up your release because you can always apply a new patch to it (unless your package is used by a majority of developers). Happy coding!