Building a Python Package: Pre-Release (5)

by Jarrett Retz February 13th, 2021
python programming building a python package

Introduction

In this post, I want to go through using Python's test package index and then deploying a pre-release version to the actual Python index.

First, I changed the version. The development release was discouraged, so I am going with an alpha pre-release.

0.0.1.a1 (Python later changed this to 0.0.1a1)

It could easily have been 1.0.1.a1. However, it doesn't seem unusual to use the 0 epoch.

This wasn't the last change that I had to make to the project since the last article, so let's work through a few more.

Add pyproject.toml

We're going back to the documentation on packaging Python projects.

I added a pyproject.toml per the documentation. This code is pulled straight from the docs and doesn't use anything custom. The file is in the root folder at the top level.

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

setup.cfg

Previously, the project has a setup.py file. However, it's not recommended to use that type of file because the values can be dynamic. A static setup file is preferred.

This other static setup file, setup.cfg, has many of the same inputs as setup.py. It's short, and the syntax is a little different (no quotation marks), but the information is fundamental.

[metadata]

name = beasy
version = 0.0.1.a1
url = https://github.com/jdretz/simple-bea-client
author = Jarrett Retz
author_email = jretz@jrts.info
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: MIT License
    Operating System :: OS Independent
description = Simple Python API client for accessing data on the Bureau of Economic Analysis application programming interface.

long_description = file: README.md
long_description_content_type = text/markdown

[options]
python_requires = >=3.6
here = pathlib.Path(__file__).parent.resolve()

# Get the long description from the README file
long_description = (here / 'README.md').read_text(encoding='utf-8')

Distribution Archives

Next, we can generate the distribution archive files. We'll need the latest version of Python's build module. We can run the command below:

python3 -m pip install --upgrade build

Then, in our project root directory, we can run the build command to generate the files.

python -m build

After executing the build command, I got the following output.

Collecting wheel
  Using cached wheel-0.36.2-py2.py3-none-any.whl (35 kB)
Collecting setuptools>=42
  Using cached setuptools-53.0.0-py3-none-any.whl (784 kB)
Installing collected packages: wheel, setuptools
Successfully installed setuptools-53.0.0 wheel-0.36.2
WARNING: You are using pip version 20.3.3; however, version 21.0.1 is available.
You should consider upgrading via the '/Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pip install --upgrade pip' command.
/private/var/folders/qw/yqqx13kn74v_mjyknjykpm2h0000gn/T/build-env-39ze5cdf/lib/python3.9/site-packages/setuptools/dist.py:461: UserWarning: Normalizing '0.0.1.a1' to '0.0.1a1'
  warnings.warn(tmpl.format(**locals()))
running sdist
running egg_info
writing beasy.egg-info/PKG-INFO
writing dependency_links to beasy.egg-info/dependency_links.txt
writing requirements to beasy.egg-info/requires.txt
writing top-level names to beasy.egg-info/top_level.txt
reading manifest file 'beasy.egg-info/SOURCES.txt'
writing manifest file 'beasy.egg-info/SOURCES.txt'
running check
creating beasy-0.0.1a1
creating beasy-0.0.1a1/beasy.egg-info
copying files to beasy-0.0.1a1...
copying README.md -> beasy-0.0.1a1
copying pyproject.toml -> beasy-0.0.1a1
copying setup.cfg -> beasy-0.0.1a1
copying setup.py -> beasy-0.0.1a1
copying beasy.egg-info/PKG-INFO -> beasy-0.0.1a1/beasy.egg-info
copying beasy.egg-info/SOURCES.txt -> beasy-0.0.1a1/beasy.egg-info
copying beasy.egg-info/dependency_links.txt -> beasy-0.0.1a1/beasy.egg-info
copying beasy.egg-info/requires.txt -> beasy-0.0.1a1/beasy.egg-info
copying beasy.egg-info/top_level.txt -> beasy-0.0.1a1/beasy.egg-info
Writing beasy-0.0.1a1/setup.cfg
Creating tar archive
removing 'beasy-0.0.1a1' (and everything under it)
/private/var/folders/qw/yqqx13kn74v_mjyknjykpm2h0000gn/T/build-env-39ze5cdf/lib/python3.9/site-packages/setuptools/dist.py:461: UserWarning: Normalizing '0.0.1.a1' to '0.0.1a1'
  warnings.warn(tmpl.format(**locals()))
running bdist_wheel
running build
installing to build/bdist.macosx-10.9-x86_64/wheel
running install
running install_egg_info
running egg_info
writing beasy.egg-info/PKG-INFO
writing dependency_links to beasy.egg-info/dependency_links.txt
writing requirements to beasy.egg-info/requires.txt
writing top-level names to beasy.egg-info/top_level.txt
reading manifest file 'beasy.egg-info/SOURCES.txt'
writing manifest file 'beasy.egg-info/SOURCES.txt'
Copying beasy.egg-info to build/bdist.macosx-10.9-x86_64/wheel/beasy-0.0.1a1-py3.9.egg-info
running install_scripts
adding license file "LICENSE" (matched pattern "LICEN[CS]E*")
creating build/bdist.macosx-10.9-x86_64/wheel/beasy-0.0.1a1.dist-info/WHEEL
creating '/Users/jarrettretz/Programming/bea/dist/tmp6jhwb_pj/beasy-0.0.1a1-py3-none-any.whl' and adding 'build/bdist.macosx-10.9-x86_64/wheel' to it
adding 'beasy-0.0.1a1.dist-info/LICENSE'
adding 'beasy-0.0.1a1.dist-info/METADATA'
adding 'beasy-0.0.1a1.dist-info/WHEEL'
adding 'beasy-0.0.1a1.dist-info/top_level.txt'
adding 'beasy-0.0.1a1.dist-info/RECORD'
removing build/bdist.macosx-10.9-x86_64/wheel

It appears that things worked!

New files were created in the project. The project structure can be represented as:

.
├── LICENSE
├── Pipfile
├── Pipfile.lock
├── README.md
├── beasy
│ ├── __init__.py
│ ├── base.py
│ ├── beasy.py
│ └── tables
│     └── tables.py
├── beasy.egg-info
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ ├── dependency_links.txt
│ ├── requires.txt
│ └── top_level.txt
├── build
│ └── bdist.macosx-10.9-x86_64
├── dist
│ ├── beasy-0.0.1a1-py3-none-any.whl
│ └── beasy-0.0.1a1.tar.gz
├── pyproject.toml
├── setup.cfg
├── setup.py
└── tests

The two new folders are:

  1. /build
  2. /dist

Publishing to Test Python Index

Python has a useful test package index. It's called Test PyPI. The first thing you’ll need to do is register an account on Test PyPI.

Then, create an API token for uploading to the test index.

We'll have to do both of these tasks (registering and creating an API token) when we upload to the actual index.

Next, we can make sure that we have twine installed and in its latest version.

python3 -m pip install --upgrade twine

Once installed, run Twine to upload all of the archives under dist:

python3 -m twine upload --repository testpypi dist/*

You will be prompted for a username and password. For the username, use __token__. For the password, use the API token value, including the pypi- prefix.

jarrettretz@MacBook-Pro bea % python3 -m twine upload --repository testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
Enter your username: __token__
Enter your password: 
Uploading beasy-0.0.1a1-py3-none-any.whl
100%|██████████████████████████████████████| 7.49k/7.49k [00:01<00:00, 6.25kB/s]
Uploading beasy-0.0.1a1.tar.gz
100%|██████████████████████████████████████| 9.55k/9.55k [00:01<00:00, 8.70kB/s]

View at:
https://test.pypi.org/project/beasy/0.0.1a1/

Success, again! I was able to view the package online!

You can also install the packages on the test index. However, it's not guarantee how long they will be available. To install the test package, I ran:

pip install -i --no-deps --index-url https://test.pypi.org/simple/ beasy==0.0.1a1

jarrettretz@MacBook-Pro ~ % conda activate base
(base) jarrettretz@MacBook-Pro ~ % pip install -i --no-deps --index-url https://test.pypi.org/simple/ beasy==0.0.1a1
Looking in indexes: https://test.pypi.org/simple/
Requirement already satisfied: beasy==0.0.1a1 in ./Programming/bea (0.0.1a1)
Requirement already satisfied: requests in /opt/anaconda3/lib/python3.8/site-packages (from beasy==0.0.1a1) (2.25.0)
Requirement already satisfied: chardet<4,>=3.0.2 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (3.0.4)
Requirement already satisfied: idna<3,>=2.5 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (2.10)
Requirement already satisfied: certifi>=2017.4.17 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (2020.11.8)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /opt/anaconda3/lib/python3.8/site-packages (from requests->beasy==0.0.1a1) (1.25.11)
WARNING: You are using pip version 21.0; however, version 21.0.1 is available.
You should consider upgrading via the '/opt/anaconda3/bin/python -m pip install --upgrade pip' command.
(base) jarrettretz@MacBook-Pro ~ % python
Python 3.8.3 (default, Jul  2 2020, 11:26:31) 
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from beasy.beasy import Bea
>>> 

Now that we know the package works, we can try to publish it to the real index.

Publish to PyPI

As I mentioned, we now need to create an account on PyPI. The credentials aren't shared. Additionally, we need a token for the actual index.

We already created the distribution files. Therefore, we need to publish these to PyPI using twine.

In the root project folder, run:

twine upload dist/*

Enter __token__ for username and the new API token for the password.

jarrettretz@MacBook-Pro bea % twine upload dist/*
Uploading distributions to https://upload.pypi.org/legacy/
Enter your username: __token__
Enter your password: 
Uploading beasy-0.0.1a1-py3-none-any.whl
100%|████████████████████████████████████████████████████████████████████████████████████████| 7.49k/7.49k [00:00<00:00, 20.7kB/s]
100%|████████████████████████████████████████████████████████████████████████████████████████| 7.49k/7.49k [00:01<00:00, 4.51kB/s]
Uploading beasy-0.0.1a1.tar.gz
100%|████████████████████████████████████████████████████████████████████████████████████████| 9.55k/9.55k [00:00<00:00, 10.7kB/s]

View at:
https://pypi.org/project/beasy/0.0.1a1/

Now, the package is live at https://pypi.org/project/beasy/0.0.1a1/.

Because the package is using an alpha version, it can be installed with pip install beasy==0.0.1a1. This statement is explicit about the version.

Python removed the period that I had before the a in the version name, so I needed to update the README.md.

Upload to Github

Finally, since the package is live, I need to upload the project to Github.

I found a basic .gitignore file online that will prevent unnecessary files from being pushed.

To push the repository to Github, in the repository root, I first ran the command git init to push the repository to Github in the repository root.

After that, I added the remote origin:

git remote add origin https://github.com/jdretz/simple-bea-client.git

Finally, I committed the files (with the message "init commit") and pushed up to Github.

git push -u origin master

The project is now ready for use on PyPI and available on Github at https://github.com/jdretz/simple-bea-client.

Conclusion

This has been a reasonable and enjoyable experience. If you have made it through reading the full series, I hope it was beneficial somehow.

I still need to publish the first version and create examples for the README. However, the concludes this series on building a Python package. Thanks for reading!


Have a thought about the article?

Send JRTS a message!

We'll use this email to respond to your message.

Contact