I've published my first Python package and now it's your turn. This guide will give you clear step-by-step instructions to plan, set up, and execute the publishing of your first public Python package. After following, you and the rest of the world will be able to pip install your very own published package.

A quick Google search will make publishing a Python package seem like a daunting task. The reality is that a simple first package is not hard at all, but the plethora of configuration options drown that out.

Getting on the Same Page

There's a bit of book-keeping that we have to do before we switch to code and terminals. The terminology that gets thrown around can be confusing. What's a module? A package? A library?

The way I've conceptualized these three terms is that a module can be as basic as a single Python file. A package is that module but prepared for distribution. Finally, a library would be a collection of modules, presumably prepared for distribution.

The next thing to clarify is what you'll need to follow along. Make sure you have an editor (I use Visual Studio), a terminal application (I use iTerm or Windows PowerShell), and an account on TestPyPI — the Python Package Index's sandbox. We want to practice there so you don't pollute the production index, but I'll include the commands for production publishing as well.

Lastly, I want to outline the steps we're going to take. We're going to set up the directory structure, add some content, then create the setup.py file which is used as an installation script.

Next, we'll create the package and upload it to (Test) PyPI. Finally, we'll discuss the next steps to continue development and some of the lessons learned.

Setting Up the Directories and Content

Let's start by creating the following folder structure replacing jhsu98 with the name of your package.

jhsu98/
  jhsu98/
    __init__.py
  LICENSE
  README.md
  setup.py

The module we're going to create will go directly inside __init__.py which is in the nested jhsu98 directory. Think of the top-level directory as our project and the nested directory as the module itself.

The LICENSE file will contain the license that the package is distributed under. I won't go in depth here, but select the license you want for your package and copy/paste accordingly.

The README.md file will contain a description of the package. Written in Markdown, this will be the document visible on the project's PyPI page.

Finally, the setup.py file will be used to provide details during package creation. It is effectively the metadata for your project. OK, let's get to fleshing out __init__.py and setup.py

The __init__.py file will be executed every time an instance of the module is initialized. We're going to make sure there are two things: a version and a function.

# __init__.py
__version__ = "0.0.1"
def helloWorld():
   print("Hello World")

Feel free to use your own version number and your own function(s) in the file. The main takeaway with the version is that you will need to increment each time you publish to PyPI.

Now for setup.py which is where the internet offers up a thousand different variations. Let's start by explaining what the file is and what it does.

I'm sure you noticed the .py extension so for starters it is a regular Python file—don't over-complicate its existence. The purpose of the file is to provide details of the package that will be used once we execute the file to trigger the packaging process.

After researching many setup.py tutorials and examples, here is what I've come to as my starting boilerplate:

import pathlib
from setuptools import setup
HERE = pathlib.Path(__file__).parent
README = (HERE / "README.md").read_text()
setup(
  name="jhsu98",
  version="0.0.1",
  description="",
  long_description=README,
  long_description_content_type="text/markdown",
  author="",
  author_email="",
  license="MIT",
  packages=["jhsu98"],
  zip_safe=False
)

A few highlights to focus on: make sure the version number matches __init__.py, I took the liberty of assuming an MIT license for free use and distribution, the value of packages=["jhsu98"] must match your module's name, and name="jhsu98" is the name of the project and technically can be different than your module's name.

I'll leave the rest for you to fill out, but after that, we're done creating our module and ready to package it.

Creating the Distribution Bundle

It's time to use your setup.py file to create the distribution bundle. Open your terminal and navigate to your parent project folder. Then run setup.py with the following command:

python setup.py sdist bdist_wheel

You'll see three directories created build, dist, and jhsu98.egg-info except with your module's name. We care mostly about the dist directory, which has a tarball (tar.gz file) named by your package's name and version. This is the file that will be published to (Test) PyPI.

There are a lot of different strategies for creating distributions that hinge on the content of the project and distribution method, but that's a Level 2 topic for another day. What's important is the single-file tarball that is going to be published to (Test) PyPI in the next section.

Publishing Your Package

The moment has finally arrived. It's time to publish your package. As mentioned earlier, the Python Package Index hosts an index for testing, which is where we'll start. First, create an account on https://test.pypi.org/

After your account has been created and verified let's go back to the terminal and install twine which will be used to upload the tarball.

pip install twine

Next, we'll use the twine module to upload to the test environment.

python3 -m twine upload --repository-url https://test.pypi.org/legacy/dist/*

For the sake of simplicity, I used the asterisk (*) to upload all; however, moving forward you can be more explicit and specify the exact file to upload.

After entering your username and password you should receive a success message: Congratulations, you just published a Python package!

You can view your project's homepage and pip install on any Python environment. Let's try out installing our module from pip. I'd recommend using a virtual environment, but it's up to you.

Because we published on the test index, we need to specify the install source. Use the following command, substituting your own package's name:

pip install -i https://test.pypi.org/simple/jhsu98

Assuming the install goes well, it's time to create a new .py file where you'll import your installed module and use it:

import jhsu98
jhsu98.helloWorld()

Here are the instructions if you're ready for the real deal. Create an account and use the following commands to upload and install.

python3 -m twine upload dist/*

pip install jhsu98

Continued Development and Lessons Learned

Naturally, you're pumped and ready to publish your awesome Python modules so here are a few tips to save time on Google and avoid the stumbling blocks I hit.

  • Think about file structure. You probably don't want the entire module or library to sit inside __init__.py, so plan out your files and import accordingly.
  • Have a plan for versioning. I like using a major.minor.micro structure for my projects. Briefly, I consider backward-breaking changes a major release, new features or a set of fixes a minor release, and superficial changes or singular bug fixes a micro release. Do not forget to update the version in both setup.py and __init__.py
  • Keep track of dependencies. As your project grows you may need to import other installed modules. You'll want to keep track of these in setup.py by adding a parameter named install_requires which is an array of package names. This will ensure those dependencies are included during installation.

Do you have any tips or lessons learned? Share your experiences publishing to the Python Package Index below.