Read time: 4.9 minutes (492 words)

Step 7: Publishing on PyPi

Python packages, including the mmdesigner application we are building, usually end up being published on the Python Package Index server. Once there, interested folks can install this tool on their systems using the standard pip command.

Our next job involves adding the structure needed to publish and maintain the project on PyPi.

Setup Files

To publish a project on PyPi, a setup.py file is required. This file contains information about the project that will end up being displayed on PyPi for interested folks to examine. Here is the file for this project:

setup.py
 1from setuptools import setup
 2import io
 3import re
 4from os.path import dirname
 5from os.path import join
 6
 7def read(*names, **kwargs):
 8    with io.open(
 9        join(dirname(__file__), *names),
10        encoding=kwargs.get('encoding', 'utf8')
11    ) as fh:
12        return fh.read()
13
14setup(
15    name='mmdesigner',
16    description='Indoor model airplane design using OpenSCAD',
17    long_description=re.compile(
18        '^.. start-badges.*^.. end-badges',
19        re.M | re.S
20    ).sub(
21        '',
22        read('README.rst')
23    ),
24    long_description_content_type='text/x-rst',
25    author='Roie R. Black',
26    author_email='roie.black@gmail.com',
27    url='https://github.com/rblack42/math-magik',
28    license='BSD',
29    version='0.1.10',
30    packages=['mmdesigner'],
31entry_points= {
32        "console_scripts": [
33            "mmd = mmdesigner.cli:cli"
34        ]
35    },
36    python_requires='>=3.7, <3.10',
37    classifiers=[
38        'Development Status :: 4 - Beta',
39        'Intended Audience :: Developers',
40        'Framework :: tox',
41        'License :: OSI Approved :: BSD License',
42        'Programming Language :: Python',
43        'Programming Language :: Python :: 3',
44        'Programming Language :: Python :: 3.7',
45        'Programming Language :: Python :: 3.8',
46        'Programming Language :: Python :: 3.9',
47        'Programming Language :: Python :: Implementation :: CPython',
48        'Programming Language :: Python :: Implementation :: PyPy',
49        'Topic :: Software Development :: Testing',
50    ],
51)

Details on all of this can be found in the pyPi documentation.

I also added one more file which aids in creating the package uploaded to PyPi

setup.cfg
1[bdist_wheel]
2universal = 1

Updating Version Numbers

There is a version number in the setup.py file. Every time we create a version of the project that we want to let users access on PyPi we will need to update this number. PyPi does not let you update the version currently shown (the latest) after publishing it.

So far, the version number appears in three places in this project:

  • README.rst - seen on GitHub

  • mmdesigner/__version__.py - the master project version number

  • setup.py - PyPi control file

Managing all three of these can be a problem, so there is a nice tool that helps keep them on track: bump2version.

Using this tool, which we can add to the requirements.txt file, involves creating a configuration file that looks like this:

.bumpversion.cfg
 1[bumpversion]
 2current_version = 0.1.10
 3commit = True
 4tag = True
 5
 6[bumpversion:file:mmdesigner/__init__.py]
 7search = __version__ = "v{current_version}"
 8replace = __version__ = "v{new_version}"
 9
10[bumpversion:file:setup.py]
11search = {current_version}
12replace = {new_version}
13
14[bumpversion:file:README.rst]
15search = {current_version}
16replace = {new_version}

Warning

The project is named bump2version, but the configuration file is just bumpversion. The original project developer abandoned the project, and a new maintainer renamed it.

With this file n place, we can increment any part of a semantic versioning number using one of the new Makefile commands provided in the Modular Make collection:

mk/version.mk
 1.PHONY: inc-major
 2inc-major:	## increment major version number
 3	@bump2version major
 4
 5.PHONY: inc-minor
 6inc-minor:	## increment minor version number
 7	@bump2version minor
 8
 9.PHONY: inc-patch
10inc-patch:	## increment patch version number
11	@bump2version patch
12
13.PHONY:	version
14version: ## display current version number
15	@cat mmdesigner/__init__.py

Publishing the Project

Now, when the project reaches a milestone point and you want to release a new version on PyPi, we need to do a few things:

  • Bump the version number as needed

  • Build the package for PyPi

  • Test the new version to make sure it will upload properly

  • Publish the version officially

We have already covered the tools needed to bump the version number properly.

Building a Package for PyPi

To build the package for PyPi, all we need to do is run the setup.py script using Python:

$ python setup.py sdist bdist_wheel

This command will build two files n a new dist directory in the project root directory:

  • mmdesigner-x.x.x-py2.py3-none-any-whl

  • mmdesigner-x.x.x.tar.gz

The first file is the one that will be delivered to a user running the pip command. The second one contains the source code for the project. This is just the contents of the mmdesigner directory, not the full documentation and test code available on GitHub.

Test The Package for PyPi

PyPi has a test server that can be used to check the package before it goes public. We will use the twine tool to send this project to the test server:

$ twine upload --repository testpypi dist/* --skip-existing

This will upload the new version. If you try to upload an existing version, nothing will actually be uploaded.

Publish the Package

Finally, we use twine to publish the new version officially:

$ twine upload  dist/* --skip-existing

All of these commands are included in a Modular Make module:

mk/pypi.mk
 1.PHONY: build
 2build:	## package project for PyPi
 3	rm -rf build dist
 4	python setup.py sdist bdist_wheel
 5	twine check dist/*
 6
 7.PHONY: pypitest
 8pypitest:	 build  ## upload project to pypi test server
 9	twine upload --repository testpypi dist/* --skip-existing
10
11.PHONY: upload
12upload:	build ## upload new version to PyPi
13	twine upload dist/* --skip-existing