django-safemigrate adds a safemigrate command to Django
to allow for safely running a migration command when deploying.
Install django-safemigrate, then add this to the
INSTALLED_APPS in the settings file:
INSTALLED_APPS = [
# ...
"django_safemigrate",
]Then mark any migration that may be run during a pre-deployment stage, such as a migration to add a column.
from django_safemigrate import Safe
class Migration(migrations.Migration):
safe = Safe.before_deployAt this point you can run the safemigrate Django command
to run the migrations, and only these migrations will run.
However, if migrations that are not safe to run before
the code is deployed are dependencies of this migration,
then these migrations will be blocked, and the safemigrate
command will fail with an error.
When the code is fully deployed, just run the normal migrate
Django command, which still functions normally.
For example, you could add the command to the release phase
for your Heroku app, and the safe migrations will be run
automatically when the new release is promoted.
There are three options for the value of the
safe property of the migration.
Safe.before_deployThis migration is only safe to run before the code change is deployed. For example, a migration that adds a new field to a model.
Safe.after_deployThis migration is only safe to run after the code change is deployed. For example, a migration that removes a field from a model.
By specifying a
delayparameter, you can specify when aSafe.after_deploymigration can be run with thesafemigratecommand. For example, if it's desired to wait a week before applying a migration, you can specifySafe.after_deploy(delay=timedelta(days=7)).The
delayis used with the datetime when the migration is first detected. The detection datetime is when thesafemigratecommand detects the migration in a plan that successfully runs. If the migration plan is blocked, such when aSafe.after_deployis in front of aSafe.before_deploy, no migrations are marked as detected.Note that a
Safe.after_deploymigration will not run the first time it's encountered.Safe.alwaysThis migration is safe to run before and after the code change is deployed. This is the default that is applied if no
safeproperty is given. For example, a migration that changes thehelp_textof a field.
To get the most from django-safemigrate,
it is important to make sure that all migrations
are marked with the appropriate safe value.
To help with this, we provide a hook for use with pre-commit.
Install and configure pre-commit,
then add this to the repos key of your .pre-commit-config.yaml:
repos:
- repo: https://github.com/ryanhiebert/django-safemigrate
rev: "6.0"
hooks:
- id: checkUnder normal operation, if there are migrations that must run before the deployment that depend on any migration that is marked to run after deployment, the command will raise an error to indicate that there are protected migrations that should have already been run, but have not been, and are blocking migrations that are expected to run.
In development, however, it is common that these would accumulate between developers, and since it is acceptable for there to be downtime during the transitional period in development, it is better to allow the command to continue without raising.
To enable nonstrict mode, add the SAFEMIGRATE setting:
SAFEMIGRATE = "nonstrict"In this mode safemigrate will run all the migrations
that are not blocked by any unsafe migrations.
Any remaining migrations can be run after the fact
using the normal migrate Django command.
To disable the protections of safemigrate entirely, add the
SAFEMIGRATE setting:
SAFEMIGRATE = "disabled"In this mode safemigrate will migrations as if they were
using the normal migrate Django command.
To get started contributing, you'll want to clone the repository, install dependencies with uv, and set up pre-commit.
git clone [email protected]:ryanhiebert/django-safemigrate.git
cd django-safemigrate
uv sync
pre-commit installTo run the tests use:
uvx --with tox-uv toxTo publish a new version:
- Find and replace all instances of the previous version with the new version.
- Commit and push that to origin.
- Tag the commit with the new version
git tag 1.0and push that to origin. - Create the new release on GitHub. It will be published to PyPI automatically.