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

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
Learn more about pre-commit, particularly for Python projects, in my DX book.
One summary email a week, no spam, I pinky promise.
Related posts:
- Python Type Hints - How to Upgrade Syntax with pyupgrade
- Improve your Django experience with IPython
- Introducing django-linear-migrations
Tags: django, pre-commit