Multitenant Flask-SQLAlchemy

Just one flask. The multiple databases are not pictured.

Just one flask. The multiple databases are not pictured.

So you’re writing a web backend with Flask and Flask-SQLAlchemy. Now you want to make the same backend connect to different databases based on the request parameters. What do you do?

Flask-SQLAlchemy supports multiple databases through the bind mechanism. The binds allow you to specify in which database each table lives. What I want to do is to choose the database for all the tables in one go but on per-request basis.

I couldn’t find a definitive solution from the Internet, so I’ll share what I came up with. Here is a small extension of Flask-SQLAlchemy that allows you to (ab)use binds for this:

from flask import g
from flask_sqlalchemy import SQLAlchemy


class MultiTenantSQLAlchemy(SQLAlchemy):
    def choose_tenant(self, bind_key):
        if hasattr(g, 'tenant'):
            raise RuntimeError('Switching tenant in the middle of the request.')
        g.tenant = bind_key

    def get_engine(self, app=None, bind=None):
        if bind is None:
            if not hasattr(g, 'tenant'):
                raise RuntimeError('No tenant chosen.')
            bind = g.tenant
        return super().get_engine(app=app, bind=bind)

We essentially have a per-request default bind for all the tables without a bind key. Now, before you do any database queries, do db.choose_tenant(name). This tells SQLAlchemy which bind to use. For example, you could implement the tenant choosing logic in the @app.before_request hook:

app = Flask(__name__)
app.config['SQLALCHEMY_BINDS'] = {
    'test1': 'sqlite:///test1.db',
    'test2': 'sqlite:///test2.db'
}
db = MultiTenantSQLAlchemy(app)


@app.before_request
def before_request():
    # Just use the query parameter "tenant"
    db.choose_tenant(request.args['tenant'])

Now http://localhost:5000/?tenant=test1 goes to test1.db and http://localhost:5000/?tenant=test2 goes to test2.db.

It was surprisingly simple to make this work. Making Alembic work with this is left as an exercise for the reader.

The full source for the demo is available.


Comments or questions? Tweet to me or send me an e-mail.