Usage¶
The sections below detail how to fully use this module, along with context for design decisions made during development of the plugin.
Setup¶
Obviously, this plugin requires the use of SQLAlchemy for model definitions. However, there are two common patterns for how SQLAlchemy models are configured for a Flask application:
- Using the Flask-SQLAlchemy plugin for simplifying boilerplate associated with configuring a SQLAlchemy-backed Flask application (recommended).
- Using SQLAlchemy directly with the declarative system for defining models in your application.
If you’re using the Flask-SQLAlchemy
plugin, you can configure this plugin by passing the db
parameter into the extension:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_continuum import Continuum
db = SQLAlchemy()
continuum = Continuum(db=db)
app = Flask(__name__)
db.init_app(app)
continuum.init_app(app)
If you’re using SQLAlchemy directly, you need to pass the SQLAlchemy engine
to the plugin. See the SQLAlchemy documentation for more context on setting up the engine
:
from flask import Flask
from sqlalchemy import create_engine
from flask_continuum import Continuum
engine = create_engine('postgresql://admin:password@localhost:5432/my-database')
continuum = Continuum(engine=engine)
app = Flask(__name__)
continuum.init_app(app)
Aside from the plugin configuration detailed above, there is no additional steps required for configuring mappers or setting up sqlalchemy-continuum
. SQLAlchemy mappers for versioning tables will be set up when the first connection to the application database is made. For more information on additional configuration options, see the Other Customizations section below.
Mixins¶
In order to add versioning support to models in your application, you can either:
- Use the
VersioningMixin
from this package to add versioning support and additional helper methods (recommended). - Add a
__versioned__ = {}
property to model classes.
With the VersioningMixin
, you can add versioning to a model via:
class Article(db.Model, VersioningMixin):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode(255))
content = db.Column(db.UnicodeText)
updated_at = db.Column(db.DateTime, default=datetime.now)
created_at = db.Column(db.DateTime, onupdate=datetime.now)
Additionally, if you only want to track specific fields in the database (for more efficient changeset processing), you can use the following syntax:
class Article(db.Model, VersioningMixin):
__versioned__ = {
'include': ['name', 'content']
}
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode(255))
content = db.Column(db.UnicodeText)
updated_at = db.Column(db.DateTime, default=datetime.now)
created_at = db.Column(db.DateTime, onupdate=datetime.now)
For more details on what the __versioned__
property can encode, see the SQLAlchemy-Continuum
documentation. If you have no need for the VersioningMixin
, you can take route (2) like so:
class Article(db.Model):
__versioned__ = {}
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode(255))
content = db.Column(db.UnicodeText)
Migrations¶
If you’re using alembic or Flask-Migrate alongside this tool, you need to make sure a flask application context is pushed before you create new migrations. Otherwise, database fields dynamically added by the Mixins above won’t be picked up by the migration tool.
If you’re using alembic directly, you’ll need to manually configure mappers in your app script or create_app
factory after models are declared:
app = Flask(__name__)
db = SQLAlchemy(app)
continuum = Continuum(app, db)
class Article(db.Model, VersioningMixin):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode(255))
continuum.configure()
If you’re using Flask-Migrate
to manage migrations, you don’t need to manually configure the orm with versioning extensions. You can simply pass an instantiated Flask-Migrate
plugin to Flask-Continuum
:
app = Flask(__name__)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
continuum = Continuum(app, db, migrate)
class Article(db.Model, VersioningMixin):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode(255))
This will automatically configure mappers before Flask-Migrate
performs any migration tasks.
Troubleshooting¶
>>> article = Article()
>>> db.session.add(article)
>>> db.session.commit()
...
OperationalError: no such table: transaction
This is usually an error caused when database tables haven’t been created before a commit is made. Make sure you create database tables with db.create_all()
before trying to commit any data to the database.
~$ flask db migrate
...
INFO [alembic.env] No changes in schema detected.
This alembic message is produced when alembic tries to create a new database migration but doesn’t detect any changes in SQLAlchemy models when trying to auto-generate the migration. It’s usually caused by an application context not being pushed before migrations take place. See the Migrations section for information on resolving this issue.
Other Customizations¶
As detailed in the Overview section of the documentation, the plugin can be customized with specific triggers. The following detail what can be customized:
user_cls
- The name of the user table to associate with content changes.current_user
- A function for returning the current user issuing a request. By default, this is determined from theFlask-Login
plugin, but can be overwritten.engine
- A SQLAlchemy engine to connect to the database. This parameter can be used if the application doesn’t require the use ofFlask-SQLAlchemy
.
The code below details how you can override all of these configuration options:
from flask import Flask
from flask_continuum import Continuum
from sqlalchemy import create_engine
app = Flask(__name__)
engine = create_engine('postgresql://...')
continuum = Continuum(
engine=engine,
user_cls='Users',
current_user=lambda: g.user
)
continuum.init_app(app)
For even more in-depth information on the module and the tools it provides, see the API section of the documentation.