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
Make your development more pleasant with Boost Your Django DX.
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