# Buggy code
def upgrade():
    try:
        op.drop_column("users", "auth_state")
    except sa.exc.OperationalError as e:
        # sqlite3 can't drop columns
        warnings.warn("Failed to drop column: %s" % e)
    op.add_column("users", sa.Column("encrypted_auth_state", sa.types.LargeBinary))


# Traceback (sqlite3.OperationalError)
$ jupyterhub
...
$ jupyterhub upgrade-db
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 19c0846f6344, base revision for 0.5
INFO  [alembic.runtime.migration] Running upgrade 19c0846f6344 -> eeb276e51423, auth_state
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
context)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/default.py", line 470, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: duplicate column name: auth_state

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/usr/local/bin/alembic", line 11, in <module>
sys.exit(main())
File "/usr/local/lib/python3.5/dist-packages/alembic/config.py", line 479, in main
CommandLine(prog=prog).main(argv=argv)
File "/usr/local/lib/python3.5/dist-packages/alembic/config.py", line 473, in main
self.run_cmd(cfg, options)
File "/usr/local/lib/python3.5/dist-packages/alembic/config.py", line 456, in run_cmd
**dict((k, getattr(options, k, None)) for k in kwarg)
File "/usr/local/lib/python3.5/dist-packages/alembic/command.py", line 254, in upgrade
script.run_env()
File "/usr/local/lib/python3.5/dist-packages/alembic/script/base.py", line 416, in run_env
util.load_python_file(self.dir, 'env.py')
File "/usr/local/lib/python3.5/dist-packages/alembic/util/pyfiles.py", line 93, in load_python_file
module = load_module_py(module_id, path)
File "/usr/local/lib/python3.5/dist-packages/alembic/util/compat.py", line 64, in load_module_py
module_id, path).load_module(module_id)
File "<frozen importlib._bootstrap_external>", line 388, in _check_name_wrapper
File "<frozen importlib._bootstrap_external>", line 809, in load_module
File "<frozen importlib._bootstrap_external>", line 668, in load_module
File "<frozen importlib._bootstrap>", line 268, in _load_module_shim
File "<frozen importlib._bootstrap>", line 693, in _load
File "<frozen importlib._bootstrap>", line 673, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 665, in exec_module
File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
File "/usr/local/lib/python3.5/dist-packages/jupyterhub/alembic/env.py", line 70, in <module>
run_migrations_online()
File "/usr/local/lib/python3.5/dist-packages/jupyterhub/alembic/env.py", line 65, in run_migrations_online
context.run_migrations()
File "<string>", line 8, in run_migrations
File "/usr/local/lib/python3.5/dist-packages/alembic/runtime/environment.py", line 817, in run_migrations
self.get_context().run_migrations(**kw)
File "/usr/local/lib/python3.5/dist-packages/alembic/runtime/migration.py", line 323, in run_migrations
step.migration_fn(**kw)
File "/usr/local/lib/python3.5/dist-packages/jupyterhub/alembic/versions/eeb276e51423_auth_state.py", line 21, in upgrade
op.add_column('users', sa.Column('auth_state', JSONDict))
File "<string>", line 8, in add_column
File "<string>", line 3, in add_column
File "/usr/local/lib/python3.5/dist-packages/alembic/operations/ops.py", line 1551, in add_column
return operations.invoke(op)
File "/usr/local/lib/python3.5/dist-packages/alembic/operations/base.py", line 318, in invoke
return fn(self, operation)
File "/usr/local/lib/python3.5/dist-packages/alembic/operations/toimpl.py", line 123, in add_column
schema=schema
File "/usr/local/lib/python3.5/dist-packages/alembic/ddl/impl.py", line 172, in add_column
self._exec(base.AddColumn(table_name, column, schema=schema))
File "/usr/local/lib/python3.5/dist-packages/alembic/ddl/impl.py", line 118, in _exec
return conn.execute(construct, *multiparams, **params)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/base.py", line 945, in execute
return meth(self, multiparams, params)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/sql/ddl.py", line 68, in _execute_on_connection
return connection._execute_ddl(self, multiparams, params)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/base.py", line 1002, in _execute_ddl
compiled
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/base.py", line 1189, in _execute_context
context)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/base.py", line 1402, in _handle_dbapi_exception
exc_info
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/util/compat.py", line 203, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb, cause=cause)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/util/compat.py", line 186, in reraise
raise value.with_traceback(tb)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
context)
File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/engine/default.py", line 470, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) duplicate column name: auth_state [SQL: 'ALTER TABLE users ADD COLUMN auth_state TEXT']
[E 2017-06-02 19:55:16.017 JupyterHub app:1527]
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/jupyterhub/app.py", line 1525, in launch_instance_async
yield self.start()
File "/usr/local/lib/python3.5/dist-packages/jupyterhub/app.py", line 1436, in start
self.subapp.start()
File "/usr/local/lib/python3.5/dist-packages/jupyterhub/app.py", line 185, in start
dbutil.upgrade(hub.db_url)
File "/usr/local/lib/python3.5/dist-packages/jupyterhub/dbutil.py", line 80, in upgrade
['alembic', '-c', alembic_ini, 'upgrade', revision]
File "/usr/lib/python3.5/subprocess.py", line 581, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['alembic', '-c', '/tmp/tmpkqvnuq0a/alembic.ini', 'upgrade', 'head']' returned non-zero exit status 1

# Fixed code
def upgrade():
    # proxy/table info is no longer in the database
    op.drop_table("proxies")
    op.drop_table("hubs")

    # drop some columns no longer in use
    try:
        op.drop_column("users", "auth_state")
        op.drop_column("users", "_server_id")
    except sa.exc.OperationalError:
        # this won't be a problem moving forward, but downgrade will fail
        if op.get_context().dialect.name == "sqlite":
            logger.warning(
                "sqlite cannot drop columns. Leaving unused old columns in place."
            )
        else:
            raise

    op.add_column("users", sa.Column("encrypted_auth_state", sa.types.LargeBinary))