Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spurious loop detected with 1.5.0+ when revision is substring of down_revision #784

Closed
alext opened this issue Jan 20, 2021 · 4 comments
Closed
Labels

Comments

@alext
Copy link

alext commented Jan 20, 2021

Describe the bug

Since upgrading alembic from 1.4.3 to 1.5.1 we've been getting spurious "Self-loop is detected errors" from our migrations. This appears to happen for any migration where revision is a substring of down_revision.

Expected behavior

This migrations should continue to be parsed and processed as before.

To Reproduce

I've created a minimal flask-sqlalchemy app with flask-migrate as follows:

app.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql:///minimal_app'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)


@app.route('/')
def hello():
    return "Hello World!"

if __name__ == '__main__':
    app.run()

migrations/versions/skeleton_user_.py

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'skeleton_user'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('user',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('user')
    # ### end Alembic commands ###

migrations/versions/user_.py

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'user'
down_revision = 'skeleton_user'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('user', sa.Column('name', sa.String(), nullable=True))
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('user', 'name')
    # ### end Alembic commands ###

Error

$ flask db heads
Traceback (most recent call last):
  File "/home/alex/.pyenv/versions/minimal-app/bin/flask", line 8, in <module>
    sys.exit(main())
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/flask/cli.py", line 967, in main
    cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/flask/cli.py", line 586, in main
    return super(FlaskGroup, self).main(*args, **kwargs)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/flask/cli.py", line 426, in decorator
    return __ctx.invoke(f, *args, **kwargs)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/flask_migrate/cli.py", line 191, in heads
    _heads(directory, verbose, resolve_dependencies)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/flask_migrate/__init__.py", line 96, in wrapped
    f(*args, **kwargs)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/flask_migrate/__init__.py", line 346, in heads
    command.heads(config, verbose=verbose,
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/command.py", line 442, in heads
    heads = script.get_revisions(script.get_heads())
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/script/base.py", line 314, in get_heads
    return list(self.revision_map.heads)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/util/langhelpers.py", line 234, in __get__
    obj.__dict__[self.__name__] = result = self.fget(obj)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/script/revision.py", line 104, in heads
    self._revision_map
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/util/langhelpers.py", line 234, in __get__
    obj.__dict__[self.__name__] = result = self.fget(obj)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/script/revision.py", line 155, in _revision_map
    for revision in self._generator():
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/script/base.py", line 112, in _load_revisions
    script = Script._from_filename(self, vers, file_)
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/script/base.py", line 913, in _from_filename
    return Script(module, revision, os.path.join(dir_, filename))
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/script/base.py", line 696, in __init__
    super(Script, self).__init__(
  File "/home/alex/.pyenv/versions/3.8.6/envs/minimal-app/lib/python3.8/site-packages/alembic/script/revision.py", line 1041, in __init__
    raise LoopDetected(revision)
alembic.script.revision.LoopDetected: Self-loop is detected in revisions (user)

With alembic 1.4.3:

$ flask db heads
user (head)

Versions.

  • OS: Ubuntu 20.04
  • Python: 3.8.6
  • Alembic: 1.5.1
  • SQLAlchemy: 1.3.22
  • Database: Postgres 12
  • DBAPI: psycopg2 2.8.6

Additional context

I suspect that this problem has arisen with the change in #758. From reading the code, I think there's an error on line 1037(in the PR) of revision.py. Where it's doing if down_revision and revision in down_revision: I think it should probably be doing if down_revision and revision == down_revision:.

Have a nice day!

@alext alext added the requires triage New issue that requires categorization label Jan 20, 2021
@zzzeek zzzeek added bug Something isn't working execution model regression and removed requires triage New issue that requires categorization labels Jan 20, 2021
@zzzeek
Copy link
Member

zzzeek commented Jan 20, 2021

how strange! let's look

@sqla-tester
Copy link
Collaborator

Mike Bayer has proposed a fix for this issue in the master branch:

ensure downrev/dependencies in tuple form before using "in" https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2492

@zzzeek
Copy link
Member

zzzeek commented Jan 20, 2021

I was seconds away from releasing 1.5.2, just in time!

@alext
Copy link
Author

alext commented Jan 20, 2021

Yay! thanks for the quick response on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants