How to mitigate `import` hell in Flask

March 02, 2019

I’m sincerely baffled as to why every Flask tutorial recommends an app initialization pattern essentially guaranteed to cause circular import issues. After experimenting with a few different approaches, I’ve finally found a pattern that seems to work, which I’m documenting here for future-me. Future-me, you’re welcome.

By way of background

Here is an example of the wretched app init pattern that has never worked for me (this one is adapted from the Flask-SQLAlchemy 2.3 quickstart guide, 2019 March 2):

# File:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)

The recommendation is then to access the database instance like so:
# File:

from app import db

class User(db.Model):
    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)

    def __repr__(self):
        return '<User %r>' % self.username

The issue is that the base file needs to register routing, handlers, and everything else to make the app run, ultimately something in there needs to access the database connection or models and tries to do from app import db, as in the above code snippet, and voila, circular import errors!

A better way

After much gnashing of the teeth and pulling of the hair, and many desperate, usually futile attempts to move import calls inside function definitions, I have arrived upon the following pattern for my Flask apps.

Directory structure

I still have an file in charge of initializing the Flask app, but it imports all the extensions it needs from a separate file. As for route handlers in different files, they are saved on blueprints that are also then explicitly registered by

This sample directory structure is for an app that implements both web views as well as API endpoints.



Drumroll please… the file.

# File:

from flask import Flask

from config import config

def create_app():
    app = Flask(__name___)
    return app

def register_extensions(app):
    from extensions import bootstrap
    from extensions import db
    from extensions import jwt_manager
    from extensions import login_manager
    from extensions import migrate


    migrate.init_app(app, db)


    login_manager.login_view = 'webapp.login'

def register_blueprints(app):
    from api import api_bp
    from webapp import webapp_bp


app = create_app()

The file takes care of instantiating all the extensions to be added to the Flask app, including the database. This means that, for example, if any other callers need to access the database connection, they can call from extensions import db instead of having to do from app import db, which was such a source of heartache in the prior setup.

# File:

from flask_bootstrap import Bootstrap
from flask_jwt_extended import JWTManager
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

bootstrap = Bootstrap()
jwt_manager = JWTManager()
login_manager = LoginManager()
migrate = Migrate()
db = SQLAlchemy()

The files that register the API and webapp routes look something like this:

# File:

from flask import Blueprint
from flask_jwt_extended import jwt_required

from graphql_api import graphql_schema

api_bp = Blueprint('api', __name__)

@api_bp.route('/api/graphql/', methods=['POST'])
def graphql():
    view = GraphQLView.as_view(
    return view()

# File:

from flask import Blueprint
from flask import redirect
from flask import render_template
from flask_login import current_user
from flask_login import login_user

from webapp_forms import LoginForm  # Some custom form.

webapp_bp = Blueprint('webapp', __name__)

def home():
    if current_user.is_authenticated:
        return render_template('home.html')
        return render_template('unauth_home.html')

@webapp_bp.route('/login/', methods=['GET', 'POST'])
def login():
    from models.user_model import User

    if current_user.is_authenticated:
        return redirect(url_for('webapp.home'))

    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(
        if user is None or not user.check_password(
            error = 'Invalid credentials. Please try again.'
            return render_template('login.html', form=form, error=error)
        return redirect(url_for('webapp.home'))

    return render_template('login.html', form=form)

For completeness, here is a sample config file:
# File:

class Config:
    JWT_SECRET_KEY = os.environ['JWT_SECRET_KEY']
    SECRET_KEY = os.environ['SECRET_KEY']

config = Config

That’s all

Some day I’ll properly understand how Python imports work, but this is good enough for now.

P.S. I thought about titling this post “How to avoid `import` hell in Flask”, but I realized I could not promise to avoid it, only to try to mitigate it, hence my mitigated language. Hah.

Tracy Chou

Written by Tracy Chou, who spends a lot of time on Twitter.