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.
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.
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")
NullBooleanField in version 3.1, in favour of
We can run django-upgrade on the file like so:
$ django-upgrade example.py Rewriting example.py
After this, our field now uses
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.
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
Try out django-upgrade today. Bug reports are very welcome so we can ensure it only makes accurate replacements.
Learn more about pre-commit, particularly for Python projects, in my DX book.
One summary email a week, no spam, I pinky promise.
- Python Type Hints - How to Upgrade Syntax with pyupgrade
- Improve your Django experience with IPython
- Introducing django-linear-migrations
Tags: django, pre-commit