Dropping Python 2 Support From Open Source Projects

I’ve recently dropped Python 2 support from most of the open source projects I maintain. Python 2 support ends 2020-01-01 (see pythonclock.org), and many major projects have signed the Python 3 Statement that declares that they will remove Python 2 support before, so this year is crunch time for migration.
Reasoning
I was originally thinking of maintaining Python 2 support until the end of the year. However, I changed my mind for a few reasons.
Firstly, Django dropped Python 2 support in version 2.0, at the start of 2018, setting a precedent.
Secondly, I have not done any professional work with Python 2 for some time. While I’m using some great tools to make cross-compatibility easier, I still don’t trust myself to remember all the differences, and I especially don’t want to make a mistake and release something broken.
Thirdly, I created a Twitter poll which came out with 69% in favour of dropping today, and only 15% waiting until 2020-01-01.
Fourthly, fellow Django contributor Josh Smeaton pointed out to me that upstream packages dropping Python 2 support gives teams more ammunition to use to convince management that upgrading needs prioritization. Dropping Python 2 support can actually help such users.
Fifthly, most of the packages I’m maintaining are pretty stable. The versions that support Python 2 will remain on PyPI and in the case of a major bug or security issue, I can always make a bug fix release branched from them. It’s only new features that won’t be added with Python 2 support.
Migration Checklist
The following is the checklist I used on each project to move to Python 3 only, ordered by affected files. My packages supported Python 2 and 3 in a single codebase, using six for compatibility, and checking with modernize, isort, and tox to ensure cross-compatibility. There are other ways of achieving cross-compatibility, such as 2to3 and future, so if you’re trying to follow this list on a project, you may need to make adjustments.
Every Python File
- Remove the UTF-8 coding header (e.g.
# coding=utf-8), as Python 3 reads source code in UTF-8 by default. - Remove the
__future__import header, which was used to backport Python 3 features into Python 2 (I used all the possible features,from __future__ import absolute_import, division, print_function, unicode_literals) - Remove usage of six, e.g. replacing
six.iteritems(a_dict)witha_dict.items(). - Remove any Python 2 only code paths - these are most clearly found when branched with
if six.PY2:, but can also be done with comparison ofsys.version_infoor just comments mentioning “python 2” or “py2”.
setup.py
- Remove usage of
codecs.openwhen reading theREADME.rstforlong_description. This was used to support all UTF-8 emojis on Python 2, but Python 3 opens text files in UTF-8 mode by default so the normalopen()can be used. - Remove
sixand any other Python 2 compatibility dependencies frominstall_requires. - Remove any Python 2 only requirements based on markers from
extras_require. - Update
python_requiresto declare Python 3.4+. - Remove Python 2 trove classifiers from
classifiers.
setup.cfg
- Remove
[bdist_wheel]section which set up universal wheel building withuniversal = 1. This is important, even if you have a package withpython_requiresset to 3.4+, if the wheel is uploaded as universal,pip install <package>on Python 2 will fail by downloading it and then executing it. I only learnt this half-way, after an issue report from a kind user. - Remove configuration for
isortto check for all__future__imports.
Testing
- Remove any Python 2 specific or compatibility packages from
requirements.in, as used bypip-compile. This includes those mentioned ininstall_requireswhich were pinned to ensure tests are repeatable, compatibility tools likemodernize, and backports likemockwhich was merged into the standard library in Python 3. - Re-run
pip-compileon Python 3 to generate a Python 3 onlyrequirements.txt. Previously I was using Python 2pip-compileand then just trusting that was compatible with Python 3, which was a bit lazy. - Remove Python 2 environments and configuration from
tox.ini
Example Pull Request
For an example migration, see my pull request on django-perf-rec. It shows most of the above steps in action.
Fin
I hope this can help you with removing Python 2 support from a codebase, whether in an application or a package!
😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸
One summary email a week, no spam, I pinky promise.
Related posts:
Tags: python