Skip to main content

Web Frameworks

This page serves a reference guide for common tasks within web frameworks. Squawk doesn't have special support for Django or any other web ORM. This page is merely a guide.

Django ORM

Django can auto generate schema changes, but many times the generated SQL won't pass Squawk's rules. These custom database backends for zero downtime migrations might work for your use case:

making a field non-nullable

In the Django ORM, making a nullable field non-nullable requires care to make the change safe.

This example shows how to make a nullable boolean field a non-nullable field.

# before
class Post(Model):
published = models.BooleanField(null=True)
# after
class Post(Model):
published = models.BooleanField(null=False, default=False)

migration steps

We cannot use the auto generated SQL from Django's migration system. We must use django.db.migrations.operations.SeparateDatabaseAndState to make changes to our Django models while using custom SQL.

  1. Update the model with a default so newly created objects in Django get assigned a default value. This change must be deployed before making the SQL migrations.

    class Post(Model):
    published = models.BooleanField(default=False)
  2. Create trigger to set default value false for inserts published is null.

    CREATE FUNCTION null_published_trigger()
    RETURNS trigger AS '
    BEGIN
    IF NEW.published IS NULL THEN
    NEW.published := false;
    END IF;
    RETURN NEW;
    END' LANGUAGE 'plpgsql';

    CREATE TRIGGER published_default_trigger
    BEFORE INSERT ON post
    FOR EACH ROW
    EXECUTE PROCEDURE null_published_trigger();
  3. Set a default value for existing objects.

    UPDATE "post" SET "published" = false WHERE "published" IS NULL;
  1. Create a not null constraint.

    ALTER TABLE "post" ADD CONSTRAINT published_not_null
    CHECK ("published" IS NOT NULL) NOT VALID;
  2. Validate constraint.

    ALTER TABLE "post" VALIDATE CONSTRAINT published_not_null;
  3. Remove trigger.

    DROP TRIGGER IF EXISTS published_default_trigger on post;
    DROP FUNCTION IF EXISTS null_published_trigger;

Alembic and SQLAlchemy

usage

alembic upgrade head --sql | squawk

or you can choose revisions

alembic upgrade first_revision:last_revision --sql | squawk

settings

Use transaction_per_migration = True in configure

# env.py
...
context.configure(
...
transaction_per_migration=True
...
)
...

Set lock_timeout and statement_timeout

# env.py
...
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
connect_args={'options': '-c lock_timeout=4000 -c statement_timeout=5000'}
)
...