Why Flask Migrations does not detect a field's length change?

Question:

I have the following model, I want to change the length of name, when I do the migration it does not detect the changes

class Client(db.Model):
    __tablename__ = "client"
    client_id = db.Column(
        db.Integer,
        primary_key=True,
        autoincrement=True
    )
    name = db.Column(db.String(65))
    email = db.Column(db.String(255)) 

For example change to

name = db.Column(db.String(100))

NFO [alembic.env] No changes in schema detected.

But when I change the name, if it detects the changes

INFO  [alembic.autogenerate.compare] Detected added column 'client.name_test'
INFO  [alembic.autogenerate.compare] Detected removed column 'client.name'
Asked By: Alex

||

Answers:

Update – June 2020

Type comparison changed in Alembic 1.4, so field length changes should be more reliably identified. From the changelog:

A major rework of the “type comparison” logic is in place which
changes the entire approach by which column datatypes are compared.
Types are now compared based on the DDL string generated by the
metadata type vs. the datatype reflected from the database. This means
we compare types based on what would actually render and additionally
if elements of the types change like string length, those changes are
detected as well. False positives like those generated between
SQLAlchemy Boolean and MySQL TINYINT should also be resolved. Thanks
very much to Paul Becotte for lots of hard work and patience on this
one.

The change log also cites this issue and this documentation.


Original Answer

TL;DR:

context.configure(
    # ...
    compare_type = True
)

I’ve testing this on a string length change in PG backend and it does work, however as you can see below, the docs currently state that it should not. Here’s the relevant section of the docs:

Autogenerate can optionally detect:

  • Change of column type. This will occur if you set the
    EnvironmentContext.configure.compare_type parameter to True, or to a
    custom callable function. The default implementation only detects
    major type changes, such as between Numeric and String, and does not
    detect changes in arguments such as lengths
    , precisions, or
    enumeration members. The type comparison logic is extensible to work
    around these limitations, see Comparing Types for details.

And the API Reference for compare_type states:

Indicates type comparison behavior during an autogenerate operation.
Defaults to False which disables type comparison. Set to True to turn
on default type comparison, which has varied accuracy depending on
backend. See Comparing Types for an example as well as information on
other type comparison options.

Lastly, in the section titled, Comparing Types, the following example is given for how to enable the type comparisons:

context.configure(
    # ...
    compare_type = True
)

You’ll find the context.configure() call in the env.py script that is automatically generated by alembic nested inside the connect context:

with connectable.connect() as connection:
    context.configure(
        connection=connection, target_metadata=target_metadata
    )

… and just add the compare_type param in there.

In the same section, they go on to say:

Note The default type comparison logic (which is end-user extensible)
currently works for major changes in type only, such as between
Numeric and String. The logic will not detect changes such as:

  • changes between types that have the same “type affinity”, such as
    between VARCHAR and TEXT, or FLOAT and NUMERIC

  • changes between the arguments within the type, such as the lengths of
    strings
    , precision values for numerics, the elements inside of an
    enumeration.

Detection of these kinds of parameters is a long term project on the SQLAlchemy side.

So it’s interesting to see it mentioned a couple of times in the docs that this should not work. As mentioned earlier, I have tested this on postgres and can confirm that setting compare_type=True does generate a revision for the length of the column, so perhaps the docs are lagging a little behind on this, or the maintainers aren’t ready to declare it as a feature yet.

I’ve also tested on MySQL and can confirm that string length changes are also picked up if compare_type=True.

Answered By: SuperShoot

I am assuming you are using the Flask-Migrate package for migrations.
By default, Alembic doesn’t recognize the change on the existing column attribute changes.

You should add
compare_type=True when you create the Migrate class

migrate = Migrate(app, db, compare_type=True)

Now again you try to flask db migrate to generate new alembic migration .py file for newly changes on existing column

Answered By: Abdul Basit