I'm trying to get sqlalchemy-continuum to work alongside flask-sqlalchemy and flask-migrate. My __init__.py
file looks like this:
import os
from flask import Flask
def create_app():
"""Create and configure an instance of the Flask application."""
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SQLALCHEMY_DATABASE_URI='postgres+psycopg2://{}:{}@{}:{}/{}'.format(
os.environ['POSTGRES_USER'],
os.environ['POSTGRES_PASSWORD'],
os.environ['POSTGRES_HOST'],
os.environ['POSTGRES_PORT'],
os.environ['POSTGRES_DB']
),
SQLALCHEMY_TRACK_MODIFICATIONS=False
)
try:
os.makedirs(app.instance_path)
except OSError:
pass
from .models import db, migrate
db.init_app(app)
migrate.init_app(app, db)
return app
My models.py file looks like this:
import sqlalchemy
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from sqlalchemy_continuum import make_versioned
db = SQLAlchemy()
migrate = Migrate()
make_versioned(user_cls=None)
class User(db.Model):
__versioned__ = {}
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(20), unique=True, nullable=False)
def __repr__(self):
return '<User {} - {}>'.format(self.username, self.email)
sqlalchemy.orm.configure_mappers()
I then run the following flask-migrate commands to initialise and migrate the database:
flask db init
flask db migrate
flask db upgrade
The output of the flask db upgrade command seems to show the correct tables being created:
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'transaction'
INFO [alembic.autogenerate.compare] Detected added table 'user'
INFO [alembic.autogenerate.compare] Detected added table 'user_version'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_version_end_transaction_id' on '['end_transaction_id']'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_version_operation_type' on '['operation_type']'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_version_transaction_id' on '['transaction_id']'
In the python shell I can do the following:
>>> from test_flask.__init__ import create_app
>>> from test_flask.models import db, User
>>> app = create_app()
>>> with app.app_context():
... user = User(username='devuser', email='[email protected]',
password='devpassword')
... db.session.add(user)
... db.session.commit()
This seems to work fine but when I attempt to access an element in the versions attribute using:
>>> user.versions[0]
I get the following error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/dynamic.py", line 254, in __getitem__
attributes.PASSIVE_NO_INITIALIZE).indexed(index)
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/dynamic.py", line 359, in indexed
return list(self.added_items)[index]
IndexError: list index out of range
The command:
>>> user.versions
returns:
<sqlalchemy.orm.dynamic.AppenderQuery object at 0x7f6515d3a898>
This doesn't seem to be the expected behaviour of the versions attribute, as specified in the sqlalchemy-continuum docs. Any ideas as to what I've done wrong?
This error could happen in two cases, at least :
UPDATE
orINSERT
app
contextIn your example from the flask shell, all statements should be inside the context block :
Outside the context,
user
is still living, the variable stays in memory, but the session connected to the database is lost.Note : if the 2nd commit is missing,
user.versions[1]
will be available only in the actual context session. So if you exit this context and checkuser.versions[1]
, you will face the same error.