56

I develop a web-app using Flask under Python3. I have a problem with postgresql enum type on db migrate/upgrade.

I added a column "status" to model:

class Banner(db.Model):
    ...
    status = db.Column(db.Enum('active', 'inactive', 'archive', name='banner_status'))
    ...

Generated migration by python manage.py db migrate is:

from alembic import op
import sqlalchemy as sa

def upgrade():
    op.add_column('banner', sa.Column('status', sa.Enum('active', 'inactive', 'archive', name='banner_status'), nullable=True))

def downgrade():
    op.drop_column('banner', 'status')

And when I do python manage.py db upgrade I get an error:

...
sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) type "banner_status" does not exist
LINE 1: ALTER TABLE banner ADD COLUMN status banner_status

 [SQL: 'ALTER TABLE banner ADD COLUMN status banner_status']

Why migration does not create a type "banner_status"?

What am I doing wrong?

$ pip freeze
alembic==0.8.6
Flask==0.10.1
Flask-Fixtures==0.3.3
Flask-Login==0.3.2
Flask-Migrate==1.8.0
Flask-Script==2.0.5
Flask-SQLAlchemy==2.1
itsdangerous==0.24
Jinja2==2.8
Mako==1.0.4
MarkupSafe==0.23
psycopg2==2.6.1
python-editor==1.0
requests==2.10.0
SQLAlchemy==1.0.13
Werkzeug==0.11.9
Alexey Egorov
  • 2,221
  • 2
  • 18
  • 21
  • 2
    From reading http://stackoverflow.com/questions/14845203/altering-an-enum-field-using-alembic and https://bitbucket.org/zzzeek/alembic/issues/67/autogenerate-with-enums-on-postgres-dont it'd seem you might have to manually create and drop the enums. – Ilja Everilä Jun 16 '16 at 09:28

2 Answers2

129

I decided on this problem using that.

I changed the code of migration, and migration looks like this:

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

def upgrade():
    banner_status = postgresql.ENUM('active', 'inactive', 'archive', name='banner_status')
    banner_status.create(op.get_bind())

    op.add_column('banner', sa.Column('status', sa.Enum('active', 'inactive', 'archive', name='banner_status'), nullable=True))

def downgrade():
    op.drop_column('banner', 'status')

    banner_status = postgresql.ENUM('active', 'inactive', 'archive', name='banner_status')
    banner_status.drop(op.get_bind())

And now python manage.py db upgrade\downgrade is successfully executed.

I159
  • 29,741
  • 31
  • 97
  • 132
Alexey Egorov
  • 2,221
  • 2
  • 18
  • 21
  • 16
    For the downgrade you can also avoid duplication by executing SQL directly (at least for postgres): `op.execute("DROP TYPE banner_status;")` – Geekfish Oct 17 '17 at 16:01
  • 2
    @Geekfish Why not move `postgresql.ENUM('active', 'inactive', 'archive', name='banner_status')` outside of the upgrade and downgrade methods? Would there be any downsides? – radzak Jul 28 '19 at 13:48
  • 1
    @Jatimir it's been a while since I looked into it but your suggestion it's probably fine! – Geekfish Jul 29 '19 at 12:07
  • 4
    You can actually pass `banner_status` as the column type directly, rather than redefining it via `sa.Enum(...)` – kevlarr Sep 05 '19 at 15:15
  • 2
    you dont need to declare all enumeration options on downgrade, just `bind = op.get_bind()` then `sa.Enum(name='banner_status').drop(bind, checkfirst=False)` – UselesssCat Mar 01 '21 at 15:03
3

I think this way is more simple:

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

def upgrade():
    # others_column = ...
    banner_status = postgresql.ENUM('active', 'inactive', 'archive', name='banner_status', create_type=False), nullable=False)

Also added the postgresql.ENUM to your downgrade() function if that needed.

Tri
  • 2,722
  • 5
  • 36
  • 65