Skip to content

Commit

Permalink
Refactor and add backward compatibility
Browse files Browse the repository at this point in the history
The refactor is significant. It needed to reorder operations to
allow for calculating safety configuration and delays, but along
the way cleaned up naming, the default configuration, and the
categories calculated and output to the user.

Rename the internal enums and enum items. Call the enum that
represents the run mode of "strict", "nonstrict", or "disabled"
Mode, with PEP8 compliant names of STRICT, NONSTRICT, and DISABLED.
Call the enum that represents the when each migration is allowed
to run When, with names of BEFORE_DEPLOY, AFTER_DEPLOY, and ALWAYS.

Factor out the loops that walk the dependency tree to categorize
the migrations into ready, delayed, and blocked. Remove the
category of "protected" migrations and combine it with delayed.

Refactor the model manager into a model queryset, and separate
filtering the queryset from resolving the final data type.

Only write and read SafeMigration records for migrations that are
after_deploy and have a delay set.

Only write SafeMigration records for migrations that are delayed,
not blocked or ready, to preserve appropriate semantics for
nonstrict mode.

Special-case Safe.before_deploy, Safe.after_deploy, and Safe.always
to be allowed as callables for backward compatiblity.

Categorize ``Safe.after_deploy`` and ``Safe.always`` migrations as
blocked if they depend on other blocked migrations. The earliest
blocked migrations must still be a ``Safe.before_deploy`` migration
that depends on a ``Safe.after_deploy`` migration.

Change the default safe marking from ``Safe.after_deploy`` to
``Safe.always``, to be more reasonable for working with third-party
apps that do not use django-safemigrate.
  • Loading branch information
ryanhiebert committed Jul 21, 2024
1 parent 7798942 commit 9241a06
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 189 deletions.
16 changes: 13 additions & 3 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
Pending
*******
5.0 (TBD)
*********

* Add support for Django 5.1.
* Drop support for Django 3.2, 4.0, 4.1.
* Convert ``Safe`` to be a custom class rather than an ``Enum``.
* The valid values for ``safe`` are:
* The standard values of ``Safe`` now support being called. For example:

* ``Safe.before_deploy()``
* ``Safe.after_deploy()``
* ``Safe.always()``
* Add support for allowing a ``Safe.after_deploy(delay=timedelta())``
migration to be migrated after the delay has passed.
* Change the default value of ``safe`` to ``Safe.always``.
This is a better default for third party apps that are not using
``django_safemigrate``.
* ``Safe.after_deploy`` and ``Safe.always`` migrations will be
reported as blocked if they are behind a blocked ``Safe.before_deploy``
migration.
* ``Safe.after_deploy`` migrations are now reported along with other
delayed migrations instead of being separately reported as protected.
* Use PEP8 compliant capitalization for enums internally. This doesn't
affect any documented API usage.

4.3 (2024-03-28)
++++++++++++++++
Expand Down
28 changes: 12 additions & 16 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ such as a migration to add a column.
from django_safemigrate import Safe
class Migration(migrations.Migration):
safe = Safe.before_deploy()
safe = Safe.before_deploy
At this point you can run the ``safemigrate`` Django command
to run the migrations, and only these migrations will run.
Expand All @@ -66,35 +66,32 @@ Safety Options
There are three options for the value of the
``safe`` property of the migration.

* ``Safe.before_deploy()``
* ``Safe.before_deploy``

This 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_deploy(delay=None)``
* ``Safe.after_deploy``

This migration is only safe to run after the code change is deployed.
This is the default that is applied if no ``safe`` property is given.
For example, a migration that removes a field from a model.

By specifying a ``delay`` parameter, you can specify when a
``Safe.after_deploy()`` migration can be run with the ``safemigrate``
``Safe.after_deploy`` migration can be run with the ``safemigrate``
command. For example, if it's desired to wait a week before applying
a migration, you can specify ``Safe.after_deploy(delay=timedelta(days=7))``.

The ``delay`` is used with the datetime when the migration is first detected.
The detection datetime is when the ``safemigrate`` command detects the
migration in a plan that successfully runs. If the migration plan is blocked,
such when a ``Safe.after_deploy(delay=None)`` is in front of a
``Safe.before_deploy()``, no migrations are marked as detected.

Note that a ``Safe.after_deploy()`` migration will not run the first
time it's encountered.
The ``delay`` begins when safemigrate first delays the migration
without it being blocked, on a non-faking run that begins executing.
If the migration is blocked, such as if it is behind a ``Safe.before_deploy``
migration that is behind another ``Safe.after_deploy`` migration,
no migrations are marked as detected.

* ``Safe.always()``
* ``Safe.always``

This migration is safe to run before *and* after
the code change is deployed.
This is the default that is applied if no ``safe`` property is given.
For example, a migration that changes the ``help_text`` of a field.

Pre-commit Hook
Expand Down Expand Up @@ -122,8 +119,7 @@ Nonstrict Mode

Under normal operation, if there are migrations
that must run before the deployment that depend
on any migration that is marked to run after deployment
(or is not marked),
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,
Expand Down
16 changes: 8 additions & 8 deletions src/django_safemigrate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@
from enum import Enum


class SafeEnum(Enum):
always = "always"
before_deploy = "before_deploy"
after_deploy = "after_deploy"
class When(Enum):
ALWAYS = "always"
BEFORE_DEPLOY = "before_deploy"
AFTER_DEPLOY = "after_deploy"


@dataclass
class Safe:
safe: SafeEnum
when: When
delay: timedelta | None = None

@classmethod
def always(cls):
return cls(safe=SafeEnum.always)
return cls(when=When.ALWAYS)

@classmethod
def before_deploy(cls):
return cls(safe=SafeEnum.before_deploy)
return cls(when=When.BEFORE_DEPLOY)

@classmethod
def after_deploy(cls, *, delay: timedelta = None):
return cls(safe=SafeEnum.after_deploy, delay=delay)
return cls(when=When.AFTER_DEPLOY, delay=delay)
Loading

0 comments on commit 9241a06

Please sign in to comment.