Removing Python 3.6 Support from My Packages

Bye Python 3.6.

Python 3.6 reached its end of life on the 23rd December. As its release manager put on the Python forum, it has gracefully “ridden off into the sunset”.

Yesterday I followed up my book release with some relaxing maintenance to remove Python 3.6 support from the packages I maintain. I used the myrepos tool to make PR’s in parallel with a script, as I’ve previously covered. This meant I could update and release 34 packages within a couple of hours.

In this post I’ll cover some of what that entailed, in case it helps you update your own projects.

Below is the script I used, followed by some explanation. You can also find this script within my scripts repo, alongside other “modify repo” scripts.

set -eu

git diff --exit-code
git checkout main
git pull

sd ' +- 3\.6\n' '' .github/workflows/main.yml
sd ' +py36.*\n' '' tox.ini
sd -s 'py{36,37' 'py{37' tox.ini
sd -s "target-version = ['py36']" "target-version = ['py37']" pyproject.toml
sd '\[--py36-plus' '[--py37-plus' .pre-commit-config.yaml
sd -s "Python 3.6 to " "Python 3.7 to " README.rst
sd ' +Programming Language :: Python :: 3\.6\n' '' setup.cfg
sd -s 'python_requires = >=3.6' 'python_requires = >=3.7' setup.cfg
sd -f m ' +subprocess\.run\((\n|.)*?python3\.6(\n|.)*?\)\n' '' requirements/
rm requirements/py36*txt
# shellcheck disable=SC2016
sd -f m '(=======
=======)' '$1

* Drop Python 3.6 support.' HISTORY.rst
git add .github/workflows/main.yml tox.ini pyproject.toml .pre-commit-config.yaml README.rst setup.cfg requirements/ HISTORY.rst

if [ -f docs/installation.rst ]; then
    sd -s "Python 3.6 to " "Python 3.7 to " README.rst
    git add docs/installation.rst

git switch -c drop_python_3.6
git commit -m "Drop Python 3.6 support

Its EOL was 2021-12-23: ."

git push -u origin "$(git rev-parse --abbrev-ref HEAD)"
gh pr create --fill
gh pr merge --squash --delete-branch --auto

A brief explanation:

pypugrade Rewrites

Shout outs to pyupgrade, which removes old blocks guarded by outdated sys.version_info checks. This feature saved me a bunch of fiddly changes. ran pyupgrade for me, and pushed the changes back to the PR.

For example, in django-linear-migrations, pyupgrade rewrote this block:

from types import ModuleType

if sys.version_info >= (3, 7):

    def is_namespace_module(module: ModuleType) -> bool:
        return module.__file__ is None


    def is_namespace_module(module: ModuleType) -> bool:
        return getattr(module, "__file__", None) is None

…to just the Python 3.7+ side:

from types import ModuleType

def is_namespace_module(module: ModuleType) -> bool:
    return module.__file__ is None


Remaining References

A handful of repositories had extra references to Python 3.6/3.7 that needed removing. I found these with this rg (ripgrep) command:

rg --color always -g '!*.txt' '\b3\b.*\b[67]\b' | less

--color always keeps the colour when piped to less, which helped me skim through the results.

New Versions

After merging the respective PR’s, I released new versions of all packages. For those still on Python 3.6, Pip will obey the python_requires field and find the older versions. If you use any packages please upgrade and check everything’s okay for you.


Bye bye Python 3.6. You were a good release.

f"F" to pay respects,


Learn how to make your tests run quickly in my book Speed Up Your Django Tests.

Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts: