Recreating Foreign Keys with Alembic

| programming | python | databases |

Alembic is a great tool for keeping track of schema changes in python applications. I am using it to manage DB migrations for braindump along with Flask SQL Alchemy as my ORM. One challenge is managing proper foreign key constraints. By default if you define a foreign key relationship in your schema definition it will not generate the proper migration code. For example, in braindump we have a one to many relationship between users and notes.

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(254), unique=True, index=True)
    password_hash = db.Column(db.String(256))
    confirmed = db.Column(db.Boolean, default=False)
    avatar_hash = db.Column(db.String(32))
    created_date = db.Column(db.DateTime(), default=datetime.utcnow)
    updated_date = db.Column(db.DateTime(), default=datetime.utcnow)
<span class="nx">notes</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">relationship</span><span class="p">(</span>
    <span class="s1">'Note'</span><span class="p">,</span> <span class="nx">backref</span><span class="o">=</span><span class="s1">'author'</span><span class="p">,</span>
    <span class="nx">lazy</span><span class="o">=</span><span class="s1">'dynamic'</span><span class="p">,</span> <span class="nx">cascade</span><span class="o">=</span><span class="s2">"all, delete-orphan"</span><span class="p">)</span>

Even though we define the cascade behavior using SQLAlchemy. When we generate the migration with alembic we get something like this:
sa.ForeignKeyConstraint(['author_id'], ['users.id'], ),
Notice how we are missing the ondelete action. What we actually want is something like this:
sa.ForeignKeyConstraint(['author_id'], ['users.id'], ondelete='CASCADE')
Running the default migration will not create the proper relationship in your database and in our case we are not able to delete a user until we have deleted all of the related records as well. There are two ways to fix this. If you catch this before running your migration addingondelete='CASCADE' will create the proper relationship. If you are like me, and do not catch this, then you will need to run a second migration to remove and recreate these keys. The migration code to do this is shown below:
from alembic import op
import sqlalchemy as sa

def upgrade(): with op.batch_alter_table(“notes”) as batch_op: batch_op.drop_constraint( “notes_author_id_fkey”, type_=“foreignkey”) op.create_foreign_key( “notes_author_id_fkey”, “notes”, “users”, [“author_id”], [“id”], ondelete=“CASCADE”)

Now you have the proper foreign key constraints and the CASCADE action exists in the DB.

Thank you for reading! Share your thoughts with me on bluesky, mastodon, or via email.

Check out some more stuff to read down below.

Most popular posts this month

Recent Favorite Blog Posts

This is a collection of the last 8 posts that I bookmarked.

Articles from blogs I follow around the net

[RODEN] Enrique Allen

Roden Readers — The first memory I have of Enrique Allen is from the campus of Stanford. He had just graduated from the d.school and was teaching part-time. We were about to start working together. He was all bounding lightness. That’s the first image: Jum…

via Craig Mod — Writer + Photographer November 21, 2024

17/11/2024

# Back in May I wrote about being inspired to write a track based on a YouTube comment. I recorded a test not long after and built on that into June. Then the breakdown struck and I had a massive crisis of confidence alongside the depression and anxiety. I …

via Colin Walker - Daily Feed November 21, 2024

Cold reading an ADHD affliction

I'm sure there are truly pathological cases of ADHD out there, and maybe taking amphetamines really is a magic pill for some folks. But there clearly is also an entire cottage industry cropping up around convincing perfectly normal people that they …

via David Heinemeier Hansson November 20, 2024

Generated by openring