TL;DR: How do I get alembic to understand and generate SQL for materialized views created in sqlalchemy? I'm using flask-sqlalchemy and also using alembic with postgres. To get a materialized view working with sqlalchemy, I followed a nice post on the topic. I used it heavily, with just a few minor divergences (the article uses flask-sqlalchemy as well, however the complete code example uses sqlalchemy's declarative base directly instead). class ActivityView(db.Model): __table__ = create_materialized_view( 'activity_view', db.select([ Activity.id.label('id'), Activity.name.label('name'), Activity.start_date.label('start_date'), ]).where( db.and_( Activity.start_date != None, Activity.start_date <= datetime_to_str(datetime.now(tz=pytz.UTC) + timedelta(hours=48)) ) ) ) @classmethod def refresh(cls, concurrently=True): refresh_materialized_view(cls.__table__.fullname, concurrently) db.Index('activity_view_index', ActivityView.__table__.c.id, ActivityView.__table__.c.start_date, unique=True) The create_materialized_view and refresh_materialized_view methods are taken straight from the blog post. Note that the example above has been greatly simplified and probably seems silly because of my simplifications, but the real idea I want to get at is how do I get alembic to translate this view to a series of alembic operations during a migration? When I run tests, the code runs fine, the view gets generated fine and everything works. When alembic runs it doesn't do anything with the view. So what I end up doing is copying the SQL that the tests emit for the materialized view into the alembic migrations/versions file and just end up executing that directly as: op.execute(activities_view_sql) Similarly, I do the same direct SQL execution when generating the unique index on the materialized view. Unfortunately my approach is error prone and creates seemingly unnecessary code duplication. Is there a way to get alembic to understand my ActivityView so that any time it changes, alembic will know how to update the view? Thanks much! Continue reading...