Introducing django-upgrade, a tool for upgrading your Django projects

2021-09-16 Giddy-up, we’re going to the future!

Django deprecates a small list of features with every feature release, requiring us to update our projects, which can be monotonous. Today I’m announcing a new tool I’ve created, django-upgrade, that automates some of this drudgery for us all.

The Story

For a while I’ve enjoyed using pyupgrade, which upgrades Python syntax to the latest and greatest. pyupgrade is both fast and accurate, what you’d hope for in such a tool. The idea is to run it continuously, via pre-commit or similar, so that any new usage of old syntax you add gets automatically upgraded.

Last year, after a django-developers discussion, Bruno Alla created a similar tool for upgrading Django projects called django-codemod. django-codemod works well, and I’ve enjoyed running it on a few projects, saving me hours of boring find-and-replace. The only downside is that it’s relatively slow, taking up to several minutes on a medium sized project. This makes it okay for one-off runs, but it’s not suitable for running continuously (except on CI).

django-codemod’s slowness is due to the underlying library, LibCST, which implements its parser in Python. In contrast, pyupgrade is fast because it uses Python’s own C-based parser. LibCST also comes with compatibility concerns - it still doesn’t support Python 3.9, even though 3.10 is around the corner.

I have tried my hand at combining the best of pyupgrade and django-codemod into a new tool: django-upgrade. It’s available today on PyPI, although it doesn’t cover every recent deprecation, and it has a couple known bugs.

I started working on django-upgrade a few weeks ago without announcing it. Since then some people spotted it on GitHub and have chimed in, including Bruno. We’re working on getting django-upgrade to cover all the same fixes as django-codemod.

Eggsample

django-upgrade is a CLI that upgrades Python files in place. For example, imagine we had this model definition:

from django.db.models import Model, NullBooleanField


class Book(Model):
    valuable = NullBooleanField("Valuable")

Django deprecated NullBooleanField in version 3.1, in favour of BooleanField with null=True.

We can run django-upgrade on the file like so:

$ django-upgrade example.py
Rewriting example.py

After this, our field now uses BooleanField:

from django.db.models import Model, BooleanField


class Book(Model):
    valuable = BooleanField("Valuable", null=True)

django-upgrade runs fixers for many such cases, all documented in its README.

With pre-commit

django-upgrade can run every time you run git commit via pre-commit. The art here is to have it run before your general formatting tools, such as Black and isort. This allows django-upgrade to make its changes without worrying about matching your code style.

For example, here’s my current config on one project using django-upgrade (Python tools only):

repos:
  - repo: https://github.com/asottile/pyupgrade
    rev: v2.26.0
    hooks:
      - id: pyupgrade
        args: [--py39-plus]
  - repo: https://github.com/adamchainz/django-upgrade
    rev: 1.2.0
    hooks:
      - id: django-upgrade
        args: [--target-version, "3.2"]
  - repo: https://github.com/psf/black
    rev: 21.8b0
    hooks:
      - id: black
  - repo: https://github.com/pycqa/isort
    rev: 5.9.3
    hooks:
      - id: isort
  - repo: https://github.com/PyCQA/flake8
    rev: 3.9.2
    hooks:
      - id: flake8

Fin

Try out django-upgrade today. Bug reports are very welcome so we can ensure it only makes accurate replacements.

Enjoy,

—Adam


📙👉Speed Up Your Django Tests👈📙


Subscribe via RSS, Twitter, or email:

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

Related posts:

Tags: django