I am working on an application using sqlalchemy, postgres and alembic.
The project structure is as follows:
.
├── alembic.ini
├── main.py
├── migrations
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── versions
├── models
│ ├── base.py
│ ├── datamodel1.py
│ ├── datamodel2.py
│ └── __init__.py
└── requirements.txt
3 directories, 10 files
Where:
the content of models/base.py is :
from sqlalchemy.ext.declarative.api import declarative_base, DeclarativeMeta
Base: DeclarativeMeta = declarative_base()
The content of models/datamodel1.py is :
from models.base import Base
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import String, Date, Float
class Model1(Base):
__tablename__ = 'model1_table'
model1_id = Column(String, primary_key=True)
col1 = Column(String)
col2 = Column(String)
The content of models/datamodel2.py is :
from models.base import Base
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import String, Integer, Date
from sqlalchemy.sql.schema import Column, ForeignKey
# The many to may relationship table
class Model1Model2(Base):
__tablename__ = 'model1_model2_table'
id = Column(Integer, primary_key=True)
model_1_id = Column(String, ForeignKey('model1.model1_id'))
model_2_id = Column(Integer, ForeignKey('model2.model2_id'))
class Model2(Base):
__tablename__ = 'model2_table'
model2_id = Column(Integer, primary_key=True)
model2_col1 = Column(String)
model2_col2 = Column(Date)
# Many to many relationship
model1_model2 = relationship('Model1', secondary='model1_model2_table', backref='model1_table')
The content of migrations/env.py is :
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import sys
sys.path.append('./')
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# I added the following 2 lines to replace the sqlalchemy.url in alembic.ini file.
db_string = f'postgresql+psycopg2://{db_username}:{db_password}#{db_host}:{db_port}/{db_name}'
config.set_main_option('sqlalchemy.url', db_string)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from models.datamodel1 import Model1
from models.datamodel2 import Model2, Model1Model2
from models.base import Base
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
include_schemas=True,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
include_schemas=True
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
As for alembic.ini file I didn't make any changes, I just commented the line:
sqlalchemy.url = driver://user:pass#localhost/dbname
because I assign it in migrations/env.py
When I make changes and run alembic revision --autogenerate -m 'Add new updates' the migration files are generated correctly and everything works as expected.
But when I run alembic revision --autogenerate -m 'Add new updates' when there are no changes, it shows this in the terminal:
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.ddl.postgresql] Detected sequence named 'model2_table_model2_id_seq' as owned by integer column 'model2_table(model2_id)', assuming SERIAL and omitting
INFO [alembic.ddl.postgresql] Detected sequence named 'model1_model2_table_id_seq' as owned by integer column 'model1_model2_table(id)', assuming SERIAL and omitting
Generating /home/user/projects/dev/project/migrations/versions/45c6fbdbd23c_add_new_updates.py ... done
And it generates empty migration file that contains:
"""Add new updates
Revision ID: 45c6fbdbd23c
Revises: 5c17014a7c18
Create Date: 2021-12-27 17:11:13.964287
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '45c6fbdbd23c'
down_revision = '5c17014a7c18'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
Is this the expected behavior or it has something to do with my architecture?
How to prevent Alembic from generating those empty migration files when there are no changes?
Is this the expected behavior or it has something to do with my architecture?
This is the expected behavior. Command alembic revision --autogenerate always creates a new migration file. If no changes exist than it creates an empty one.
You can use alembic-autogen-check to check if your migrations is in sync with models.
~ $ alembic-autogen-check
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO: Migrations in sync.
How to prevent Alembic from generating those empty migration files when there are no changes?
Also alembic-autogen-check returns zero code only in migrations are in sync with models. So, you can use it as one command
alembic-autogen-check || alembic revision --autogenerate -m 'Add new updates'
But it seems less convenient than use it separately
Related
I followed the documentation from Alembic to auto-generate migrations. My project structure looks like this:
alembic/
versions/
env.py
README
script.py.mako
data/
__init__.py
db.py
models.py
alembic.ini
app.db
I made changes to env.py by exactly following the document:
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from data.models import Base
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
This is my __init__.py:
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from .models import Base
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
db_url = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
engine = create_engine(db_url, echo=False)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)
I created a User class in models.py like this:
from sqlalchemy import Column, Sequence, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, Sequence('id_seq'), primary_key=True)
first_name = Column(String, nullable=False)
last_name = Column(String, nullable=False)
def __repr__(self):
return "<%s('%s', '%s')>" \
% (self.__class__.__qualname__, self.first_name,
self.last_name)
After that, I run migration by:
alembic revision --autogenerate -m "Added user table"
However, I got empty upgrade() and downgrade() in migration file:
"""Added user table
Revision ID: 279933caec54
Revises:
Create Date: 2021-05-12 16:21:05.772568
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '279933caec54'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
But when I checked the database, the user table was created there (I didn't do it myself). How did it happen? How did alembic create empty migration but generating the table?
I found the answer in this question. The problem described in this question is not the same as mine (the alembic didn't create an empty migration file in this case) though. That's why I asked the question here (may be seen as duplicated).
So as the answer suggested, I commented the Base.metadata.create_all(engine) in the __init__.py. It is this line of code that creates the user table (if it not exist). So when alembic checks my database, the table is already there. Since no differences are found, the upgrade() and downgrade() are empty.
in this situation don't import your Base model from base file, import your Base from some where you have extend this Base class
for example I have a structure like below:
also i imported Base class in alembic/.env from models.trade.
in this form your base class metadata will detect your models and your auto generation migrations will work fine.
So have 2 different models on 2 different schemas. One has a foreign key relation to the other. I run BaseOne.metadata.create_all(engine) then BaseTwo.metadata.create_all(engine) I get sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column...
BaseOne = declarative_base(metadata=MetaData(schema="a"))
BaseTwo = declarative_base(metadata=MetaData(schema="b"))
class Parent(BaseOne):
__tablename__ = "parent"
parent_id = Column(Integer, primary_key=True)
other_col = Column(String(20))
children = relationship("Child", backref="parent")
class Child(BaseTwo):
__tablename__ = "child"
child_id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("a.parent.parent_id"), nullable=False)
# Where I'm creating them
BaseOne.metadata.create_all(engine)
BaseTwo.metadata.create_all(engine)
Should note I've also tried explicitly stating the schema via __table_args__. Also I have connected to my postgres instance and have verified that the parent table exists with the target column.
It appears the issue was due to the fact I used multiple MetaData objects. It appears that they were unable to see each other. Simplified to a single declarative base and using __table_args__ to declare the schemas appeared to work. If someone knows how to declare multiple metadata objects and still be able to use .create_all feel free to post.
This may be solved by using Alembic to manage table creation. Ensure that all bases are included in the target_metadata list e.g.:
# pylint: skip-file
import os
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config
from sqlalchemy import pool
import unimatrix.ext.octet.orm
import gpo.infra.orm
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = [
unimatrix.ext.octet.orm.metadata,
gpo.infra.orm.Relation.metadata
]
# Configure SQLAlchemy to use the DB_URI environment variable.
config.set_main_option("sqlalchemy.url", os.environ["DB_URI"])
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
``
I'm using Flask with Flask-SQLAlchemy and Flask-Migrate to create an application, however when I try to create a migration nothing happens.
I've created two tables in app/models.py:
from flask import current_app
from . import db
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
def __init__(self, **kwargs):
super(Student, self).__init__(**kwargs)
def __repr__(self):
return '<Tutor {}>' % self.id
class Tutor(db.Model):
__tablename__ = 'tutors'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
def __init__(self, **kwargs):
super(Tutor, self).__init__(**kwargs)
def __repr__(self):
return '<Student %r>' % self.id
Then I also have app/__init__.py with the following code:
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
#from .models import User, Task, Project, UserProject
from config import config
bootstrap = Bootstrap()
db = SQLAlchemy()
migrate = Migrate()
def create_app(config_name='default'):
#print config_name.name
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
bootstrap.init_app(app)
db.init_app(app)
migrate.init_app(app, db)
## Register the main blueprint for main app functionality
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
and app.py:
import os
from app import create_app, db
from app.models import Tutor, Student
app = create_app('default')
#app.shell_context_processor
def make_shell_context():
return dict(db=db, Tutor=Tutor, Student=Student)
I can run flask db init with no problem and it creates the migrations directory and all necessary files with the following output:
Creating directory /Users/Jasmine/projects/flask/flask-tutoring/migrations ... done
Creating directory /Users/Jasmine/projects/flask/flask-tutoring/migrations/versions ... done
Generating /Users/Jasmine/projects/flask/flask-tutoring/migrations/script.py.mako ... done
Generating /Users/Jasmine/projects/flask/flask-tutoring/migrations/env.py ... done
Generating /Users/Jasmine/projects/flask/flask-tutoring/migrations/README ... done
Generating /Users/Jasmine/projects/flask/flask-tutoring/migrations/alembic.ini ... done
Please edit configuration/connection/logging settings in '/Users/Jasmine/projects/flask/flask-tutoring/migrations/alembic.ini' before proceeding.
but when I try and run flask db migrate alembic can't detect that I've got tables in app/models.py. I get the following output:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.env] No changes in schema detected.
There is no migration script created, its as though models.py doesn't exist.
Apologies if this is a repeated question, but I can't find another example where its the first migration that fails and no migration script at all is created.
I've tried checking if there is already a table created somewhere by running db.drop_all() in the shell but that doesn't seem to be the problem.
UPDATE
I figured out a way to solve this on my own but would like a better understanding of why this worked.
I re-named app.py to flasktutor.py and re-ran export FLASK_APP='flasktutor.py'. Subsequently the migration worked perfectly.
Please could someone explain why when the file was called app.py and I used export FLASK_APP='app.py' the migration did not register changes to the schema.
I encountered this problem and solved it by importing my models at env.py in the migrations folder right after the following comments
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from app.models import Student, Tutor
Pls make sure that you already import your models (Tutor, Student, ...) before your migrate.init_app(app, db)
I just ran into the same issue, following Miguel's great tutorial, and the hints here were very helpful!
My solution - which doesn't require "hacking" env.py - is to simply add
from app import models
to __init__.py.
If you have a different project layout you might need to adapt the import but it seems like you need to make sure that models gets imported properly for flask db migrate to work.
This is how I solved the problem in my case:
Import the Migrate model: from flask_migrate import Migrate
Initiate Migrate class: migrate = Migrate(app, db)
Comment db.create_all()
Drop your database now => DROP DATABASE db_name;
Create it again => CREATE DATABSE db_name OWNER owner_name;
Export you flask entry file => export FLASK_APP=name_app.py
Run flask db migrate
Note: The 6th step should be used in case you get this error:
Error: Could not locate a Flask application
Hope this will help someone.
Well, I encountered the same problem following Miguel Grinberg tutorial.
Previously I created the tables using the shell calling
db.create_all()
So, I thougth to drop the tables
db.drop_all()
and trying the migrate command again, it worked as expected:
Roberto#MyPC MINGW64 /e/Projects/Flask/flasky ((5c))
$ flask db migrate -m "initial migration - Role Users"
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'roles'
INFO [alembic.autogenerate.compare] Detected added table 'users'
INFO [alembic.autogenerate.compare] Detected added index 'ix_users_username' on '['username']'
Generating E:\Projects\Flask\flasky\migrations\versions\4de323c9c089_initial_migration_role_users.py ... done
After that, I used flask-migrate to re-create the tables
$ flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> b641ee80a60d, initial migration - Role Users
I encountered the same problem following Miguel Grinberg tutorial.
I solved this my adding
from app.models import User, Post
to migrations/env.py
The flask-migrate works after you change your table schema.
Such as, before:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
class Test(db.Model):
content = db.Column(db.String(60))
and then you change your Test schema, like:
class Test(db.Model):
content = db.Column(db.String(60))
add = db.Column(db.String(60))
Now, you can use flask db migrate -m "migrate test" to work.
You will get migrate version information.
This worked for me:
In migrations/env.py, import your models
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from app.models import * # <= Add this line
from flask import current_app
You can point to empty database only to create migration file, then return back to upgrade your real database.
After some time trying to fix it and reading about it, I just deleted the migrations directory (folder) and the app.db.
Then ran again:
"flask db init"
"flask db migrate"
"flask db upgrade"
This re-generate the directories and all working fine now.
If you want to improve your migration script, this link to an upgrade from Miguel, can be useful: https://www.youtube.com/watch?v=wpRVZFwsD70&feature=emb_logo
I am trying to autogenerate an alembic revision for the first time against a pre-existing database but when I run the following command
alembic revision --autogenerate
It generates a migration which attempts to create every table and index in my database. Similar to this:
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('table1',
sa.Column('id', sa.SmallInteger(), nullable=False),
sa.Column('name', sa.String(length=100), nullable=True),
sa.Column('desc', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name'),
schema='schema1'
)
op.create_index(op.f('ix_index1'), 'table1', ['name'], unique=False, schema='schema1')
... all my other tables/indexes ..
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_index1'), table_name='table1', schema='schema1')
op.drop_table('table1', schema='schema1')
... all my other tables/indexes ..
Then if I try and run the migration it fails because the objects already exist:
sqlalchemy.exc.ProgrammingError: (ProgrammingError) relation "table1" already exists
So it looks to me like alembic thinks that my database doesn't contain any tables, but it does.
Any ideas why this might be happening?
Configure alembic to look at your database
Have you set the target_metadata to your Base meta data?
From the documentation.
To use autogenerate, we first need to modify our env.py so that it
gets access to a table metadata object that contains the target.
Suppose our application has a declarative base in myapp.mymodel. This
base contains a MetaData object which contains Table objects defining
our database. We make sure this is loaded in env.py and then passed to
EnvironmentContext.configure() via the target_metadata argument. The
env.py sample script used in the generic template already has a
variable declaration near the top for our convenience, where we
replace None with our MetaData. Starting with:
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
we change to:
from myapp.mymodel import Base
target_metadata = Base.metadata
I have the following models in file listpull/models.py:
from datetime import datetime
from listpull import db
class Job(db.Model):
id = db.Column(db.Integer, primary_key=True)
list_type_id = db.Column(db.Integer, db.ForeignKey('list_type.id'),
nullable=False)
list_type = db.relationship('ListType',
backref=db.backref('jobs', lazy='dynamic'))
record_count = db.Column(db.Integer, nullable=False)
status = db.Column(db.Integer, nullable=False)
sf_job_id = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime, nullable=False)
compressed_csv = db.Column(db.LargeBinary)
def __init__(self, list_type, created_at=None):
self.list_type = list_type
if created_at is None:
created_at = datetime.utcnow()
self.created_at = created_at
def __repr__(self):
return '<Job {}>'.format(self.id)
class ListType(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False)
def __init__(self, name):
self.name = name
def __repr__(self):
return '<ListType {}>'.format(self.name)
I call ./run.py init then ./run.py migrate then ./run.py upgrade, and I see the migration file generated, but its empty:
"""empty message
Revision ID: 5048d48b21de
Revises: None
Create Date: 2013-10-11 13:25:43.131937
"""
# revision identifiers, used by Alembic.
revision = '5048d48b21de'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
pass
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
pass
### end Alembic commands ###
run.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from listpull import manager
manager.run()
listpull/__init__.py
# -*- coding: utf-8 -*-
# pylint: disable-msg=C0103
""" listpull module """
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand
from mom.client import SQLClient
from smartfocus.restclient import RESTClient
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
mom = SQLClient(app.config['MOM_HOST'],
app.config['MOM_USER'],
app.config['MOM_PASSWORD'],
app.config['MOM_DB'])
sf = RESTClient(app.config['SMARTFOCUS_URL'],
app.config['SMARTFOCUS_LOGIN'],
app.config['SMARTFOCUS_PASSWORD'],
app.config['SMARTFOCUS_KEY'])
import listpull.models
import listpull.views
UPDATE
If I run the shell via ./run.py shell and then do from listpull import * and call db.create_all(), I get the schema:
mark.richman#MBP:~/code/nhs-listpull$ sqlite3 app.db
-- Loading resources from /Users/mark.richman/.sqliterc
SQLite version 3.7.12 2012-04-03 19:43:07
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .schema
CREATE TABLE job (
id INTEGER NOT NULL,
list_type_id INTEGER NOT NULL,
record_count INTEGER NOT NULL,
status INTEGER NOT NULL,
sf_job_id INTEGER NOT NULL,
created_at DATETIME NOT NULL,
compressed_csv BLOB,
PRIMARY KEY (id),
FOREIGN KEY(list_type_id) REFERENCES list_type (id)
);
CREATE TABLE list_type (
id INTEGER NOT NULL,
name VARCHAR(80) NOT NULL,
PRIMARY KEY (id),
UNIQUE (name)
);
sqlite>
Unfortunately, the migrations still do not work.
When you call the migrate command Flask-Migrate (or actually Alembic underneath it) will look at your models.py and compare that to what's actually in your database.
The fact that you've got an empty migration script suggests you have updated your database to match your model through another method that is outside of Flask-Migrate's control, maybe by calling Flask-SQLAlchemy's db.create_all().
If you don't have any valuable data in your database, then open a Python shell and call db.drop_all() to empty it, then try the auto migration again.
UPDATE: I installed your project here and confirmed that migrations are working fine for me:
(venv)[miguel#miguel-linux nhs-listpull]$ ./run.py db init
Creating directory /home/miguel/tmp/mark/nhs-listpull/migrations...done
Creating directory /home/miguel/tmp/mark/nhs-listpull/migrations/versions...done
Generating /home/miguel/tmp/mark/nhs-listpull/migrations/script.py.mako...done
Generating /home/miguel/tmp/mark/nhs-listpull/migrations/env.pyc...done
Generating /home/miguel/tmp/mark/nhs-listpull/migrations/env.py...done
Generating /home/miguel/tmp/mark/nhs-listpull/migrations/README...done
Generating /home/miguel/tmp/mark/nhs-listpull/migrations/alembic.ini...done
Please edit configuration/connection/logging settings in
'/home/miguel/tmp/mark/nhs-listpull/migrations/alembic.ini' before
proceeding.
(venv)[miguel#miguel-linux nhs-listpull]$ ./run.py db migrate
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate] Detected added table 'list_type'
INFO [alembic.autogenerate] Detected added table 'job'
Generating /home/miguel/tmp/mark/nhs-
listpull/migrations/versions/48ff3456cfd3_.py...done
Try a fresh checkout, I think your setup is correct.
Ensure to import the Models in the manage.py file (or the file with the migrate instance). You have to import the models in the file, even if you are not explicitly using them. Alembic needs these imports to migrate, and to create the tables in the database. For example:
# ... some imports ...
from api.models import User, Bucketlist, BucketlistItem # Import the models
app = create_app('dev')
manager = Manager(app)
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
# ... some more code here ...
if __name__ == "__main__":
manager.run()
db.create_all()
I had the same issue but a different problem caused it.
Flask-migrate workflow consists of two consequent commands:
flask db migrate
which generates the migration and
flask db upgrade
which applies the migration. I forgot to run the last one and tried to start next migration without applying the previous one.
For anyone coming who comes across this, my problem was having
db.create_all()
in my main flask application file
which created the new table without the knowledge of alembic
Simply comment it out or delete it altogether so it doesn't mess with future migrations.
but unlike #Miguel's suggestion, instead of dropping the whole database (i had important information in it), i was able to fix it by deleting the new table created by Flask SQLAlchemy and then running the migration.
and this time alembic detected the new table and created a proper migration script
I just encountered a similar problem. I'd like to share my solution for anyone else encountering this thread. For me, I had my models in a package. For example models/user.py and I tried from app.models import * which did not detect anything on the migrate. However, if I changed the import to from app.models import user this is okay why my project is young, but as I have more models a bulk import would be preferable.
Strange solve for me is: delete database and folder migrations. Then
>>> from app import db
>>> db.create_all()
After flask db init or python app.py db init and then flask db migrate or python app.py db migrate. Wow, It's strange, but it works for me.